import { type Box, type EditorTheme, type Input, type MarkdownTheme, type SelectListTheme, type SettingsListTheme } from "@kolisachint/hoocode-tui"; import type { SourceInfo } from "../../../core/source-info.js"; export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "agent1" | "agent2" | "agent3" | "agent4" | "agent5" | "agent6" | "mcp" | "brandText" | "paperShadow" | "halftone" | "headlineText" | "tapeText"; /** The agent identity palette, in hash order. */ export declare const AGENT_COLOR_TOKENS: readonly ["agent1", "agent2", "agent3", "agent4", "agent5", "agent6"]; /** * Stable color token for an agent type ("explore", "plan", a custom agent…). * The name hashes into the agent palette, so the same type renders in the same * color everywhere it appears — chat tool line, task panel rows/roster, and * TaskOutput — for the whole session and across sessions. Distinct types spread * over 6 hues; collisions are possible but the `[name]`/`#N` labels stay the * primary identifier, color is the at-a-glance cue. */ export declare function agentColorFor(agentType: string): ThemeColor; /** * The colour token for a session's slot (1-6). Sessions share the agent identity * palette: a session chip and an agent tag never appear on the same surface, so a * collision costs nothing but a coincidence, and reusing the tokens keeps every * theme file untouched. A dedicated `session1…6` palette can be layered on later * without breaking anything, since this is the only place the mapping lives. */ export declare function sessionColorToken(slot: number): ThemeColor; export type ThemeBg = "selectedBg" | "userMessageBg" | "customMessageBg" | "toolPendingBg" | "toolSuccessBg" | "toolErrorBg" | "warningBg" | "brandBg" | "headlineBg" | "tapeBg" | "activeToolBg"; type ColorMode = "truecolor" | "256color"; /** * The hue family a chip keeps white ink for. * * Slot 4 is magenta in every shipped theme (`/color magenta`, and the slot * order is a contract — see `docs/themes.md`), and magenta with a dark name on * it reads as pink with small print: the lift that makes dark ink legible is * exactly what bleaches the hue out of it. A deep magenta with a white name is * both more legible and more itself, so a magenta fill is deepened — never * lifted — until white clears the same bars every other fill answers to. When * no deep variant clears, the lift below still runs: legibility outranks ink. * * The window covers every shipped slot-4 token (325°–340°) with room on both * sides, and stops short of purple (nearest 293°) and red. The saturation gate * keeps out grays, whose hue is noise. */ export declare const MAGENTA_HUE_MIN = 305; export declare const MAGENTA_HUE_MAX = 355; export declare const MIN_MAGENTA_SATURATION = 0.25; export declare class Theme { readonly name?: string; readonly sourcePath?: string; sourceInfo?: SourceInfo; private fgColors; private bgColors; /** Foreground tokens usable as a chip fill: background ANSI plus a legible ink. */ private fillColors; private mode; constructor(fgColors: Record, bgColors: Record, mode: ColorMode, options?: { name?: string; sourcePath?: string; sourceInfo?: SourceInfo; }); fg(color: ThemeColor, text: string): string; bg(color: ThemeBg, text: string): string; /** * Paint `text` as a filled chip in a *foreground* token — the palette lives on * the foreground side, so `bg()`, which only knows the handful of surface * tokens, cannot do this. The ink comes from the fill's own luminance, which * is what lets one call render correctly on both a dark theme's bright hues * and a light theme's deep ones. * * Falls back to coloured text when the token is not fillable (see `canFill`): * a chip that loses its fill still reads, and callers that want the quieter * outline form should ask `canFill` first. */ fill(color: ThemeColor, text: string): string; /** Whether `color` can be laid down as a fill with legible ink over it. */ canFill(color: ThemeColor): boolean; /** Whether the theme defines an optional token, so callers can pick a * fallback rendering instead of letting fg()/bg() throw. */ has(color: ThemeColor): boolean; hasBg(color: ThemeBg): boolean; bold(text: string): string; blink(text: string): string; italic(text: string): string; underline(text: string): string; inverse(text: string): string; strikethrough(text: string): string; getFgAnsi(color: ThemeColor): string; getBgAnsi(color: ThemeBg): string; getColorMode(): ColorMode; getThinkingBorderColor(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): (str: string) => string; getBashModeBorderColor(): (str: string) => string; } /** The theme a retired name now stands for, or undefined if the name is not retired. */ export declare function successorThemeFor(name: string): string | undefined; /** * The theme `name` actually names today: itself if anything still defines it, * its successor if we retired it, and itself again if it is simply unknown — * the caller's own not-found handling is the right answer for that. */ export declare function resolveThemeName(name: string): string; export declare function getAvailableThemes(): string[]; /** * One-line description from a theme file, shown next to the name in the theme * picker. Undefined for themes that don't declare one, and for themes * registered as instances by extensions. */ export declare function getThemeDescription(name: string): string | undefined; export interface ThemeInfo { name: string; path: string | undefined; } export declare function getAvailableThemesWithPaths(): ThemeInfo[]; export declare function loadThemeFromPath(themePath: string, mode?: ColorMode): Theme; export declare function getThemeByName(name: string): Theme | undefined; export declare const theme: Theme; export declare function setRegisteredThemes(themes: Theme[]): void; export declare function initTheme(themeName?: string, enableWatcher?: boolean): void; export declare function setTheme(requested: string, enableWatcher?: boolean): { success: boolean; error?: string; }; export declare function setThemeInstance(themeInstance: Theme): void; export declare function onThemeChange(callback: () => void): void; export declare function stopThemeWatcher(): void; /** * Get resolved theme colors as CSS-compatible hex strings. * Used by HTML export to generate CSS custom properties. */ export declare function getResolvedThemeColors(themeName?: string): Record; /** * Get explicit export colors from theme JSON, if specified. * Returns undefined for each color that isn't explicitly set. */ export declare function getThemeExportColors(themeName?: string): { pageBg?: string; cardBg?: string; infoBg?: string; }; /** * Highlight code with syntax coloring based on file extension or language. * Returns array of highlighted lines. */ export declare function highlightCode(code: string, lang?: string): string[]; /** * Get language identifier from file path extension. */ export declare function getLanguageFromPath(filePath: string): string | undefined; /** * The `[branch]` / `[skill]` / `[extension]` tag that heads a message block. * * Five call sites had written the same bold-brackets-in-customMessageLabel * string by hand, which is why they are here now: a theme that sets the tape * pair renders the tag as a strip laid across the block instead, and that only * reads as one device if every tag agrees. Themes without the pair get exactly * the string the call sites used to build. */ export declare function messageLabel(name: string): string; /** * The shadow pass for a filled block, or nothing when the theme has no * `paperShadow`. Every block that reads as a pasted sheet — a user message, an * extension message, an error or warning notice — takes its shadow from here so * they cannot drift apart. */ export declare function getPaperShadowFn(): ((text: string) => string) | undefined; /** * How far a sheet holds back from the right margin, leaving a gutter of page. * * One column, which is the whole of what the treatment needs and no more. The * gutter exists so the sheet has a right edge to show and somewhere to put the * shadow's column; the column is `▏`, a *left one-eighth* block, so it paints * the sheet's edge as a hairline at the left of that one cell and leaves the * rest as page. Three columns bought nothing the first one does not already * give and cost two columns of every message, on every line, forever. */ export declare const PAPER_INSET = 1; /** * Give a filled block the paper treatment: a gutter of page at its right, and a * shadow down that edge and along the bottom. * * The two arrive together because they are one decision. A block that runs edge * to edge has no right edge to show and nowhere to cast a shadow, so a theme * asking for paper is asking for the gutter that makes paper possible. * A theme without `paperShadow` gets the full-width band it has always drawn. * * The decision is handed to the box as a function, not as an answer, because * blocks outlive themes: the messages already on screen when the user switches * theme keep the boxes they were built with. Answering once meant a message * pasted under a cut-out theme went on asking a plain theme for `paperShadow`, * a token that theme never defined — which threw rather than rendering. */ export declare function applyPaperSheet(box: Box): void; /** * The fills that make a *block* — a pasted sheet of message — as opposed to a * chip or a row stroke. * * `selectedBg` and `activeToolBg` mark part of a row and `brandBg`, `headlineBg` * and `tapeBg` are chips laid on top of one; none of them is a sheet and none * takes a shadow. These four are, and every one of them has to look like the * others or the page stops reading as paper. */ export type BlockFill = "userMessageBg" | "customMessageBg" | "warningBg" | "toolErrorBg"; /** * Fill a message block, and give it the edge that goes with the fill. * * These are one decision, so they are made in one call. Kept apart they drifted * exactly as you would expect: `[skill]`, `[compaction]` and `[branch]` blocks * were each written to use "the same background colour as custom messages for * visual consistency" and each stopped there, so under a cut-out theme they * rendered as flat full-width bands beside the sheets they were copying — * same paint, no gutter, no edge, no shadow, in the same transcript. * * A theme with no `paperShadow` still gets the full-width band it always had: * `applyPaperSheet` resolves that per frame, so this is safe on every theme and * survives a theme switch under blocks already on screen. */ export declare function applyBlockFill(box: Box, fill: BlockFill): void; export declare function getMarkdownTheme(): MarkdownTheme; /** * The cursor every picker marks its selected row with, trailing space included. * Pickers used to pick their own — `→`, `›` and `>` were all in use, and the * shared `SelectList` hardcoded a fourth — so which glyph you saw depended on * which list you had opened. */ export declare const SELECT_CURSOR = "\u203A "; /** * Dress an input line's caret in the session theme. Every place you can type — * a picker's query, the login dialog, an extension's prompt, a rename — is the * same signal as the main prompt, so it wears the prompt's `❯` (the `Input` * default) rather than a glyph of its own. Muted, because an input line usually * sits a row or two under an accent `›` marking a selected row and must not * out-shout it. * * Mutates rather than returning a theme object because `Input` takes no theme; * the colour is resolved at render, so a theme swap follows. Returns the input * so it can wrap a `new Input()` in a field initializer. */ export declare function styleInput(input: Input): Input; /** * The blank gutter an unselected row is indented by. Derived from the cursor * rather than hardcoded, so a cursor of a different width keeps the column it * marks in the same place instead of shifting every unselected row by one. */ export declare const SELECT_GUTTER: string; /** * Paint a selected row as a filled band, padding it to the full width first so * the highlight reaches the right edge instead of stopping wherever the text * happens to stop. For components that render their own rows; `SelectList` and * `SettingsList` get the same treatment through their `selectedRow` theme hook. * * Pass a row that already fits the width. A row long enough to have been * truncated carries a reset at the cut, which ends the band on the ellipsis. */ export declare function paintSelectedRow(line: string, width: number): string; export declare function getSelectListTheme(): SelectListTheme; export declare function getEditorTheme(): EditorTheme; export declare function getSettingsListTheme(): SettingsListTheme; export {}; //# sourceMappingURL=theme.d.ts.map