diff --git a/.pi/themes/feynman.json b/.pi/themes/feynman.json index f7e0d7e..555fcb4 100644 --- a/.pi/themes/feynman.json +++ b/.pi/themes/feynman.json @@ -2,60 +2,56 @@ "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", "name": "feynman", "vars": { - "ink": "#d9d3c7", - "paper": "#181614", - "paper2": "#1c1917", - "paper3": "#221f1c", - "panel": "#27231f", - "moss": "#24332c", - "moss2": "#202c26", - "stone": "#aaa79d", - "ash": "#909d91", - "darkAsh": "#4f4a44", - "oxide": "#b76e4c", - "gold": "#d0a85c", - "sage": "#86d8a4", - "teal": "#69d6c4", - "rose": "#c97b84", - "violet": "#a98dc6", - "selection": "#302b27", - "successBg": "#1d2520", - "errorBg": "#2b1f21" + "ink": "#d3c6aa", + "paper": "#2d353b", + "paper2": "#343f44", + "paper3": "#3a464c", + "panel": "#374247", + "stone": "#9da9a0", + "ash": "#859289", + "darkAsh": "#5c6a72", + "sage": "#a7c080", + "teal": "#7fbbb3", + "rose": "#e67e80", + "violet": "#d699b6", + "selection": "#425047", + "successBg": "#2f3b32", + "errorBg": "#3b3135" }, "colors": { "accent": "sage", "border": "stone", - "borderAccent": "sage", + "borderAccent": "teal", "borderMuted": "darkAsh", "success": "sage", "error": "rose", - "warning": "sage", + "warning": "stone", "muted": "stone", "dim": "ash", "text": "ink", - "thinkingText": "sage", + "thinkingText": "stone", "selectedBg": "selection", - "userMessageBg": "moss", + "userMessageBg": "panel", "userMessageText": "", - "customMessageBg": "moss2", + "customMessageBg": "paper2", "customMessageText": "", - "customMessageLabel": "sage", + "customMessageLabel": "stone", "toolPendingBg": "paper2", "toolSuccessBg": "successBg", "toolErrorBg": "errorBg", - "toolTitle": "sage", + "toolTitle": "ink", "toolOutput": "stone", "mdHeading": "sage", "mdLink": "teal", - "mdLinkUrl": "stone", + "mdLinkUrl": "ash", "mdCode": "teal", "mdCodeBlock": "ink", - "mdCodeBlockBorder": "ash", + "mdCodeBlockBorder": "stone", "mdQuote": "stone", - "mdQuoteBorder": "ash", - "mdHr": "ash", + "mdQuoteBorder": "stone", + "mdHr": "darkAsh", "mdListBullet": "sage", "toolDiffAdded": "sage", @@ -82,8 +78,8 @@ "bashMode": "sage" }, "export": { - "pageBg": "#141210", - "cardBg": "#1c1917", - "infoBg": "#27221d" + "pageBg": "#2d353b", + "cardBg": "#343f44", + "infoBg": "#374247" } } diff --git a/scripts/patch-embedded-pi.mjs b/scripts/patch-embedded-pi.mjs index d4a2cdd..b74b29f 100644 --- a/scripts/patch-embedded-pi.mjs +++ b/scripts/patch-embedded-pi.mjs @@ -9,7 +9,9 @@ const piPackageRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-codi const packageJsonPath = resolve(piPackageRoot, "package.json"); const cliPath = resolve(piPackageRoot, "dist", "cli.js"); const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js"); -const footerPath = resolve(piPackageRoot, "dist", "modes", "interactive", "components", "footer.js"); +const interactiveThemePath = resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js"); +const piTuiRoot = resolve(appRoot, "node_modules", "@mariozechner", "pi-tui"); +const editorPath = resolve(piTuiRoot, "dist", "components", "editor.js"); const workspaceRoot = resolve(appRoot, ".pi", "npm", "node_modules"); const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts"); const sessionSearchIndexerPath = resolve( @@ -103,40 +105,187 @@ if (existsSync(interactiveModePath)) { } } -if (existsSync(footerPath)) { - const footerSource = readFileSync(footerPath, "utf8"); - const footerOriginal = [ - ' // Add thinking level indicator if model supports reasoning', - ' let rightSideWithoutProvider = modelName;', - ' if (state.model?.reasoning) {', - ' const thinkingLevel = state.thinkingLevel || "off";', - ' rightSideWithoutProvider =', - ' thinkingLevel === "off" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;', - ' }', - ' // Prepend the provider in parentheses if there are multiple providers and there\'s enough room', - ' let rightSide = rightSideWithoutProvider;', - ' if (this.footerData.getAvailableProviderCount() > 1 && state.model) {', - ' rightSide = `(${state.model.provider}) ${rightSideWithoutProvider}`;', +if (existsSync(interactiveThemePath)) { + let themeSource = readFileSync(interactiveThemePath, "utf8"); + const desiredGetEditorTheme = [ + "export function getEditorTheme() {", + " return {", + ' borderColor: (text) => " ".repeat(text.length),', + ' bgColor: (text) => theme.bg("userMessageBg", text),', + ' placeholderText: "Ask Feynman to research anything",', + ' placeholder: (text) => theme.fg("dim", text),', + " selectList: getSelectListTheme(),", + " };", + "}", ].join("\n"); - const footerReplacement = [ - ' // Add thinking level indicator if model supports reasoning', - ' const modelLabel = theme.fg("accent", modelName);', - ' let rightSideWithoutProvider = modelLabel;', - ' if (state.model?.reasoning) {', - ' const thinkingLevel = state.thinkingLevel || "off";', - ' const separator = theme.fg("dim", " • ");', - ' rightSideWithoutProvider = thinkingLevel === "off"', - ' ? `${modelLabel}${separator}${theme.fg("muted", "thinking off")}`', - ' : `${modelLabel}${separator}${theme.getThinkingBorderColor(thinkingLevel)(thinkingLevel)}`;', - ' }', - ' // Prepend the provider in parentheses if there are multiple providers and there\'s enough room', - ' let rightSide = rightSideWithoutProvider;', - ' if (this.footerData.getAvailableProviderCount() > 1 && state.model) {', - ' rightSide = `${theme.fg("muted", `(${state.model.provider})`)} ${rightSideWithoutProvider}`;', - ].join("\n"); - if (footerSource.includes(footerOriginal)) { - writeFileSync(footerPath, footerSource.replace(footerOriginal, footerReplacement), "utf8"); + themeSource = themeSource.replace( + /export function getEditorTheme\(\) \{[\s\S]*?\n\}\nexport function getSettingsListTheme\(\) \{/m, + `${desiredGetEditorTheme}\nexport function getSettingsListTheme() {`, + ); + writeFileSync(interactiveThemePath, themeSource, "utf8"); +} + +if (existsSync(editorPath)) { + let editorSource = readFileSync(editorPath, "utf8"); + const importOriginal = 'import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";'; + const importReplacement = 'import { applyBackgroundToLine, getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";'; + if (!editorSource.includes("applyBackgroundToLine") && editorSource.includes(importOriginal)) { + editorSource = editorSource.replace(importOriginal, importReplacement); } + const desiredRender = [ + " render(width) {", + " const maxPadding = Math.max(0, Math.floor((width - 1) / 2));", + " const paddingX = Math.min(this.paddingX, maxPadding);", + " const contentWidth = Math.max(1, width - paddingX * 2);", + " // Layout width: with padding the cursor can overflow into it,", + " // without padding we reserve 1 column for the cursor.", + " const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));", + " // Store for cursor navigation (must match wrapping width)", + " this.lastWidth = layoutWidth;", + ' const horizontal = this.borderColor("─");', + " const bgColor = this.theme.bgColor;", + " // Layout the text", + " const layoutLines = this.layoutText(layoutWidth);", + " // Calculate max visible lines: 30% of terminal height, minimum 5 lines", + " const terminalRows = this.tui.terminal.rows;", + " const maxVisibleLines = Math.max(5, Math.floor(terminalRows * 0.3));", + " // Find the cursor line index in layoutLines", + " let cursorLineIndex = layoutLines.findIndex((line) => line.hasCursor);", + " if (cursorLineIndex === -1)", + " cursorLineIndex = 0;", + " // Adjust scroll offset to keep cursor visible", + " if (cursorLineIndex < this.scrollOffset) {", + " this.scrollOffset = cursorLineIndex;", + " }", + " else if (cursorLineIndex >= this.scrollOffset + maxVisibleLines) {", + " this.scrollOffset = cursorLineIndex - maxVisibleLines + 1;", + " }", + " // Clamp scroll offset to valid range", + " const maxScrollOffset = Math.max(0, layoutLines.length - maxVisibleLines);", + " this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScrollOffset));", + " // Get visible lines slice", + " const visibleLines = layoutLines.slice(this.scrollOffset, this.scrollOffset + maxVisibleLines);", + " const result = [];", + ' const leftPadding = " ".repeat(paddingX);', + " const rightPadding = leftPadding;", + " // Render top padding row. When background fill is active, mimic the user-message block", + " // instead of the stock editor chrome.", + " if (bgColor) {", + " if (this.scrollOffset > 0) {", + " const indicator = ` ↑ ${this.scrollOffset} more`;", + " result.push(applyBackgroundToLine(indicator, width, bgColor));", + " }", + " else {", + ' result.push(applyBackgroundToLine("", width, bgColor));', + " }", + " }", + " else if (this.scrollOffset > 0) {", + " const indicator = `─── ↑ ${this.scrollOffset} more `;", + " const remaining = width - visibleWidth(indicator);", + ' result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));', + " }", + " else {", + " result.push(horizontal.repeat(width));", + " }", + " // Render each visible layout line", + " // Emit hardware cursor marker only when focused and not showing autocomplete", + " const emitCursorMarker = this.focused && !this.autocompleteState;", + " const showPlaceholder = this.state.lines.length === 1 &&", + ' this.state.lines[0] === "" &&', + ' typeof this.theme.placeholderText === "string" &&', + " this.theme.placeholderText.length > 0;", + " for (let visibleIndex = 0; visibleIndex < visibleLines.length; visibleIndex++) {", + " const layoutLine = visibleLines[visibleIndex];", + " const isFirstLayoutLine = this.scrollOffset + visibleIndex === 0;", + " let displayText = layoutLine.text;", + " let lineVisibleWidth = visibleWidth(layoutLine.text);", + " let cursorInPadding = false;", + " const isPlaceholderLine = showPlaceholder && isFirstLayoutLine;", + " // Add cursor if this line has it", + " if (isPlaceholderLine) {", + " const marker = emitCursorMarker ? CURSOR_MARKER : \"\";", + " const rawPlaceholder = this.theme.placeholderText;", + " const graphemes = [...segmenter.segment(rawPlaceholder)];", + ' const firstGrapheme = graphemes[0]?.segment ?? " ";', + " const restRaw = rawPlaceholder.slice(firstGrapheme.length);", + ' const restStyled = typeof this.theme.placeholder === "function"', + " ? this.theme.placeholder(restRaw)", + " : restRaw;", + ' displayText = marker + `\\x1b[7m${firstGrapheme}\\x1b[27m` + restStyled;', + " lineVisibleWidth = visibleWidth(rawPlaceholder);", + " }", + " else if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {", + " const before = displayText.slice(0, layoutLine.cursorPos);", + " const after = displayText.slice(layoutLine.cursorPos);", + " // Hardware cursor marker (zero-width, emitted before fake cursor for IME positioning)", + ' const marker = emitCursorMarker ? CURSOR_MARKER : "";', + " if (after.length > 0) {", + " // Cursor is on a character (grapheme) - replace it with highlighted version", + " // Get the first grapheme from 'after'", + " const afterGraphemes = [...segmenter.segment(after)];", + ' const firstGrapheme = afterGraphemes[0]?.segment || "";', + " const restAfter = after.slice(firstGrapheme.length);", + ' const cursor = `\\x1b[7m${firstGrapheme}\\x1b[27m`;', + " displayText = before + marker + cursor + restAfter;", + " // lineVisibleWidth stays the same - we're replacing, not adding", + " }", + " else {", + " // Cursor is at the end - add highlighted space", + ' const cursor = "\\x1b[7m \\x1b[27m";', + " displayText = before + marker + cursor;", + " lineVisibleWidth = lineVisibleWidth + 1;", + " // If cursor overflows content width into the padding, flag it", + " if (lineVisibleWidth > contentWidth && paddingX > 0) {", + " cursorInPadding = true;", + " }", + " }", + " }", + " // Calculate padding based on actual visible width", + ' const padding = " ".repeat(Math.max(0, contentWidth - lineVisibleWidth));', + " const lineRightPadding = cursorInPadding ? rightPadding.slice(1) : rightPadding;", + " const renderedLine = `${leftPadding}${displayText}${padding}${lineRightPadding}`;", + " result.push(bgColor ? applyBackgroundToLine(renderedLine, width, bgColor) : renderedLine);", + " }", + " // Render bottom padding row. When background fill is active, mimic the user-message block", + " // instead of the stock editor chrome.", + " const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);", + " if (bgColor) {", + " if (linesBelow > 0) {", + " const indicator = ` ↓ ${linesBelow} more`;", + " result.push(applyBackgroundToLine(indicator, width, bgColor));", + " }", + " else {", + ' result.push(applyBackgroundToLine("", width, bgColor));', + " }", + " }", + " else if (linesBelow > 0) {", + " const indicator = `─── ↓ ${linesBelow} more `;", + " const remaining = width - visibleWidth(indicator);", + ' const bottomLine = this.borderColor(indicator + "─".repeat(Math.max(0, remaining)));', + " result.push(bottomLine);", + " }", + " else {", + " const bottomLine = horizontal.repeat(width);", + " result.push(bottomLine);", + " }", + " // Add autocomplete list if active", + " if (this.autocompleteState && this.autocompleteList) {", + " const autocompleteResult = this.autocompleteList.render(contentWidth);", + " for (const line of autocompleteResult) {", + " const lineWidth = visibleWidth(line);", + ' const linePadding = " ".repeat(Math.max(0, contentWidth - lineWidth));', + " const autocompleteLine = `${leftPadding}${line}${linePadding}${rightPadding}`;", + " result.push(bgColor ? applyBackgroundToLine(autocompleteLine, width, bgColor) : autocompleteLine);", + " }", + " }", + " return result;", + " }", + ].join("\n"); + editorSource = editorSource.replace( + / render\(width\) \{[\s\S]*?\n handleInput\(data\) \{/m, + `${desiredRender}\n handleInput(data) {`, + ); + writeFileSync(editorPath, editorSource, "utf8"); } if (existsSync(webAccessPath)) { diff --git a/src/index.ts b/src/index.ts index fb1132e..30aad68 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,190 +23,6 @@ import { import { buildFeynmanSystemPrompt } from "./feynman-prompt.js"; type ThinkingLevel = "off" | "low" | "medium" | "high"; -type Rgb = { r: number; g: number; b: number }; -type ThemeColorValue = string | number; -type ThemeJson = { - $schema?: string; - name: string; - vars?: Record; - colors: Record; - export?: Record; -}; - -const OSC11_QUERY = "\u001b]11;?\u0007"; -const OSC11_RESPONSE_PATTERN = - /\u001b]11;(?:rgb:([0-9a-fA-F]{2,4})\/([0-9a-fA-F]{2,4})\/([0-9a-fA-F]{2,4})|#?([0-9a-fA-F]{6}))(?:\u0007|\u001b\\)/; -const DEFAULT_SAGE_RGB: Rgb = { r: 0x88, g: 0xa8, b: 0x8a }; - -function parseHexComponent(component: string): number { - const value = Number.parseInt(component, 16); - if (Number.isNaN(value)) { - throw new Error(`Invalid OSC 11 component: ${component}`); - } - if (component.length === 2) { - return value; - } - return Math.round(value / ((1 << (component.length * 4)) - 1) * 255); -} - -function parseHexColor(color: string): Rgb | undefined { - const match = color.trim().match(/^#?([0-9a-fA-F]{6})$/); - if (!match) { - return undefined; - } - - return { - r: Number.parseInt(match[1].slice(0, 2), 16), - g: Number.parseInt(match[1].slice(2, 4), 16), - b: Number.parseInt(match[1].slice(4, 6), 16), - }; -} - -function rgbToHex(rgb: Rgb): string { - return `#${[rgb.r, rgb.g, rgb.b] - .map((value) => Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0")) - .join("")}`; -} - -function blendRgb(base: Rgb, tint: Rgb, alpha: number): Rgb { - const mix = (baseChannel: number, tintChannel: number) => - baseChannel + (tintChannel - baseChannel) * alpha; - return { - r: mix(base.r, tint.r), - g: mix(base.g, tint.g), - b: mix(base.b, tint.b), - }; -} - -function isLightRgb(rgb: Rgb): boolean { - const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255; - return luminance >= 0.6; -} - -function resolveThemeColorValue( - value: ThemeColorValue | undefined, - vars: Record | undefined, - visited = new Set(), -): ThemeColorValue | undefined { - if (value === undefined || typeof value === "number" || value === "" || value.startsWith("#")) { - return value; - } - if (!vars || !(value in vars) || visited.has(value)) { - return value; - } - visited.add(value); - return resolveThemeColorValue(vars[value], vars, visited); -} - -function resolveThemeRgb( - value: ThemeColorValue | undefined, - vars: Record | undefined, -): Rgb | undefined { - const resolved = resolveThemeColorValue(value, vars); - return typeof resolved === "string" ? parseHexColor(resolved) : undefined; -} - -function deriveMessageBackgrounds(themeJson: ThemeJson, terminalBackgroundHex: string): Pick | undefined { - const terminalBackground = parseHexColor(terminalBackgroundHex); - if (!terminalBackground) { - return undefined; - } - - const tint = - resolveThemeRgb(themeJson.colors.accent, themeJson.vars) ?? - resolveThemeRgb(themeJson.vars?.sage, themeJson.vars) ?? - DEFAULT_SAGE_RGB; - const lightBackground = isLightRgb(terminalBackground); - const userAlpha = lightBackground ? 0.15 : 0.23; - const customAlpha = lightBackground ? 0.11 : 0.17; - - return { - userMessageBg: rgbToHex(blendRgb(terminalBackground, tint, userAlpha)), - customMessageBg: rgbToHex(blendRgb(terminalBackground, tint, customAlpha)), - }; -} - -async function probeTerminalBackgroundHex(timeoutMs = 120): Promise { - if (typeof process.env.FEYNMAN_TERMINAL_BG === "string" && process.env.FEYNMAN_TERMINAL_BG.trim()) { - return process.env.FEYNMAN_TERMINAL_BG.trim(); - } - if (typeof process.env.PI_TERMINAL_BG === "string" && process.env.PI_TERMINAL_BG.trim()) { - return process.env.PI_TERMINAL_BG.trim(); - } - if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") { - return undefined; - } - - const wasRaw = "isRaw" in input ? Boolean((input as typeof input & { isRaw?: boolean }).isRaw) : false; - const wasFlowing = "readableFlowing" in input - ? (input as typeof input & { readableFlowing?: boolean | null }).readableFlowing - : null; - - return await new Promise((resolve) => { - let settled = false; - let buffer = ""; - - const finish = (value: string | undefined) => { - if (settled) { - return; - } - settled = true; - clearTimeout(timer); - input.off("data", onData); - try { - if (!wasRaw) { - input.setRawMode(false); - } - } catch { - // Ignore raw mode restore failures and return best-effort detection. - } - if (wasFlowing !== true) { - input.pause(); - } - resolve(value); - }; - - const onData = (chunk: string | Buffer) => { - buffer += chunk.toString("utf8"); - const match = buffer.match(OSC11_RESPONSE_PATTERN); - if (!match) { - if (buffer.length > 512) { - finish(undefined); - } - return; - } - - if (match[4]) { - finish(`#${match[4].toLowerCase()}`); - return; - } - - try { - finish( - rgbToHex({ - r: parseHexComponent(match[1]), - g: parseHexComponent(match[2]), - b: parseHexComponent(match[3]), - }), - ); - } catch { - finish(undefined); - } - }; - - const timer = setTimeout(() => finish(undefined), timeoutMs); - input.on("data", onData); - - try { - if (!wasRaw) { - input.setRawMode(true); - } - output.write(OSC11_QUERY); - } catch { - finish(undefined); - } - }); -} function printHelp(): void { console.log(`Feynman commands: @@ -316,7 +132,6 @@ function patchEmbeddedPiBranding(piPackageRoot: string): void { const packageJsonPath = resolve(piPackageRoot, "package.json"); const cliPath = resolve(piPackageRoot, "dist", "cli.js"); const interactiveModePath = resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js"); - const footerPath = resolve(piPackageRoot, "dist", "modes", "interactive", "components", "footer.js"); if (existsSync(packageJsonPath)) { const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { @@ -350,42 +165,6 @@ function patchEmbeddedPiBranding(piPackageRoot: string): void { ); } } - - if (existsSync(footerPath)) { - const footerSource = readFileSync(footerPath, "utf8"); - const footerOriginal = [ - ' // Add thinking level indicator if model supports reasoning', - ' let rightSideWithoutProvider = modelName;', - ' if (state.model?.reasoning) {', - ' const thinkingLevel = state.thinkingLevel || "off";', - ' rightSideWithoutProvider =', - ' thinkingLevel === "off" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;', - ' }', - ' // Prepend the provider in parentheses if there are multiple providers and there\'s enough room', - ' let rightSide = rightSideWithoutProvider;', - ' if (this.footerData.getAvailableProviderCount() > 1 && state.model) {', - ' rightSide = `(${state.model.provider}) ${rightSideWithoutProvider}`;', - ].join("\n"); - const footerReplacement = [ - ' // Add thinking level indicator if model supports reasoning', - ' const modelLabel = theme.fg("accent", modelName);', - ' let rightSideWithoutProvider = modelLabel;', - ' if (state.model?.reasoning) {', - ' const thinkingLevel = state.thinkingLevel || "off";', - ' const separator = theme.fg("dim", " • ");', - ' rightSideWithoutProvider = thinkingLevel === "off"', - ' ? `${modelLabel}${separator}${theme.fg("muted", "thinking off")}`', - ' : `${modelLabel}${separator}${theme.getThinkingBorderColor(thinkingLevel)(thinkingLevel)}`;', - ' }', - ' // Prepend the provider in parentheses if there are multiple providers and there\'s enough room', - ' let rightSide = rightSideWithoutProvider;', - ' if (this.footerData.getAvailableProviderCount() > 1 && state.model) {', - ' rightSide = `${theme.fg("muted", `(${state.model.provider})`)} ${rightSideWithoutProvider}`;', - ].join("\n"); - if (footerSource.includes(footerOriginal)) { - writeFileSync(footerPath, footerSource.replace(footerOriginal, footerReplacement), "utf8"); - } - } } function patchPackageWorkspace(appRoot: string): void { @@ -495,6 +274,9 @@ function normalizeFeynmanSettings( if (!settings.defaultThinkingLevel) { settings.defaultThinkingLevel = defaultThinkingLevel; } + if (settings.editorPaddingX === undefined) { + settings.editorPaddingX = 1; + } settings.theme = "feynman"; settings.quietStartup = true; settings.collapseChangelog = true; @@ -749,7 +531,7 @@ function syncDirectory(sourceDir: string, targetDir: string): void { } } -function syncFeynmanTheme(appRoot: string, agentDir: string, terminalBackgroundHex?: string): void { +function syncFeynmanTheme(appRoot: string, agentDir: string): void { const sourceThemePath = resolve(appRoot, ".pi", "themes", "feynman.json"); const targetThemeDir = resolve(agentDir, "themes"); const targetThemePath = resolve(targetThemeDir, "feynman.json"); @@ -759,32 +541,7 @@ function syncFeynmanTheme(appRoot: string, agentDir: string, terminalBackgroundH } mkdirSync(targetThemeDir, { recursive: true }); - - const sourceTheme = readFileSync(sourceThemePath, "utf8"); - if (!terminalBackgroundHex) { - writeFileSync(targetThemePath, sourceTheme, "utf8"); - return; - } - - try { - const parsedTheme = JSON.parse(sourceTheme) as ThemeJson; - const derivedBackgrounds = deriveMessageBackgrounds(parsedTheme, terminalBackgroundHex); - if (!derivedBackgrounds) { - writeFileSync(targetThemePath, sourceTheme, "utf8"); - return; - } - - const generatedTheme: ThemeJson = { - ...parsedTheme, - colors: { - ...parsedTheme.colors, - ...derivedBackgrounds, - }, - }; - writeFileSync(targetThemePath, JSON.stringify(generatedTheme, null, 2) + "\n", "utf8"); - } catch { - writeFileSync(targetThemePath, sourceTheme, "utf8"); - } + writeFileSync(targetThemePath, readFileSync(sourceThemePath, "utf8"), "utf8"); } function syncFeynmanAgents(appRoot: string, agentDir: string): void { @@ -826,10 +583,9 @@ async function main(): Promise { const workingDir = resolve(values.cwd ?? process.cwd()); const sessionDir = resolve(values["session-dir"] ?? resolve(homedir(), ".feynman", "sessions")); - const terminalBackgroundHex = await probeTerminalBackgroundHex(); mkdirSync(sessionDir, { recursive: true }); mkdirSync(feynmanAgentDir, { recursive: true }); - syncFeynmanTheme(appRoot, feynmanAgentDir, terminalBackgroundHex); + syncFeynmanTheme(appRoot, feynmanAgentDir); syncFeynmanAgents(appRoot, feynmanAgentDir); const feynmanSettingsPath = resolve(feynmanAgentDir, "settings.json"); const feynmanAuthPath = resolve(feynmanAgentDir, "auth.json"); @@ -926,8 +682,6 @@ async function main(): Promise { ...process.env, PI_CODING_AGENT_DIR: feynmanAgentDir, FEYNMAN_CODING_AGENT_DIR: feynmanAgentDir, - FEYNMAN_TERMINAL_BG: terminalBackgroundHex, - PI_TERMINAL_BG: terminalBackgroundHex, FEYNMAN_PI_NPM_ROOT: resolve(appRoot, ".pi", "npm", "node_modules"), FEYNMAN_SESSION_DIR: sessionDir, PI_SESSION_DIR: sessionDir,