Files
bare-operating-system/packages/bare-os-booter/lib/cli/bare-os-theme-presets.js
T
2026-08-18 18:11:28 -04:00

247 lines
7.8 KiB
JavaScript

/**
* Named UI themes: REPL ANSI tokens + LS_COLORS strings for Bare OS.
*/
import {
bareDefaultLsColorsString,
bareParseDircolorsDatabase,
bareSerializeLsColors
} from 'bare-os-lscolors'
/** @typedef {{ colors: Record<string, string>, lsColors?: string }} BareOsThemePreset */
/** @type {Record<string, BareOsThemePreset>} */
export const BARE_OS_THEME_PRESETS = {
default: {
colors: {
prompt: '\x1b[32m',
command: '\x1b[36m',
path: '\x1b[33m',
envset: '\x1b[35m',
envunset: '\x1b[31m',
ghost: '\x1b[90m',
search: '\x1b[36m'
}
},
dracula: {
colors: {
prompt: '\x1b[38;2;80;250;123m',
command: '\x1b[38;2;139;233;253m',
path: '\x1b[38;2;241;250;140m',
envset: '\x1b[38;2;189;147;249m',
envunset: '\x1b[38;2;255;85;85m',
ghost: '\x1b[38;2;98;114;164m',
search: '\x1b[38;2;139;233;253m'
},
lsColors:
'no=00:fi=00:di=01;36:ln=01;35:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;33'
},
nord: {
colors: {
prompt: '\x1b[38;2;163;190;140m',
command: '\x1b[38;2;136;192;208m',
path: '\x1b[38;2;235;203;139m',
envset: '\x1b[38;2;180;142;173m',
envunset: '\x1b[38;2;191;97;106m',
ghost: '\x1b[38;2;76;86;106m',
search: '\x1b[38;2;136;192;208m'
},
lsColors:
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
},
solarized_dark: {
colors: {
prompt: '\x1b[38;2;133;153;0m',
command: '\x1b[38;2;38;139;210m',
path: '\x1b[38;2;181;137;0m',
envset: '\x1b[38;2;108;113;196m',
envunset: '\x1b[38;2;220;50;47m',
ghost: '\x1b[38;2;88;110;117m',
search: '\x1b[38;2;38;139;210m'
},
lsColors:
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
},
gruvbox_dark: {
colors: {
prompt: '\x1b[38;2;184;187;38m',
command: '\x1b[38;2;131;165;152m',
path: '\x1b[38;2;250;189;47m',
envset: '\x1b[38;2;211;134;155m',
envunset: '\x1b[38;2;251;73;52m',
ghost: '\x1b[38;2;102;92;84m',
search: '\x1b[38;2;131;165;152m'
},
lsColors:
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
},
catppuccin_mocha: {
colors: {
prompt: '\x1b[38;2;166;227;161m',
command: '\x1b[38;2;137;180;250m',
path: '\x1b[38;2;249;226;175m',
envset: '\x1b[38;2;203;166;247m',
envunset: '\x1b[38;2;243;139;168m',
ghost: '\x1b[38;2;108;112;134m',
search: '\x1b[38;2;137;180;250m'
},
lsColors:
'no=00:fi=00:di=01;36:ln=01;35:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;33'
},
tokyo_night: {
colors: {
prompt: '\x1b[38;2;158;206;106m',
command: '\x1b[38;2;122;162;247m',
path: '\x1b[38;2;224;163;82m',
envset: '\x1b[38;2;187;154;247m',
envunset: '\x1b[38;2;247;118;142m',
ghost: '\x1b[38;2;86;95;137m',
search: '\x1b[38;2;122;162;247m'
},
lsColors:
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
},
github_dark: {
colors: {
prompt: '\x1b[38;2;63;185;80m',
command: '\x1b[38;2;121;192;255m',
path: '\x1b[38;2;210;153;34m',
envset: '\x1b[38;2;188;140;255m',
envunset: '\x1b[38;2;248;81;73m',
ghost: '\x1b[38;2;110;118;129m',
search: '\x1b[38;2;121;192;255m'
},
lsColors:
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
}
}
BARE_OS_THEME_PRESETS.default.lsColors = bareDefaultLsColorsString()
/**
* Downgrade truecolor REPL sequences in BARE_OS_COLOR_* for limited terminals.
* @param {Record<string, unknown>} env
*/
export function bareOsDowngradeReplColorsForDepth(env) {
if (!env || typeof env !== 'object') return
const depth = String(env.BARE_OS_COLOR_DEPTH ?? 'truecolor')
.toLowerCase()
.trim()
if (
depth === '' ||
depth === 'truecolor' ||
depth === '24bit' ||
depth === 'rgb'
)
return
const keys = Object.keys(env).filter((k) => k.startsWith('BARE_OS_COLOR_'))
for (const k of keys) {
const raw = env[k]
if (raw == null) continue
let v = String(raw)
if (depth === '256' || depth === '8bit') {
v = v.replace(
/\x1b\[38;2;(\d+);(\d+);(\d+)m/g,
(_, rs, gs, bs) => {
const r = Math.min(255, Math.max(0, Number(rs)))
const g = Math.min(255, Math.max(0, Number(gs)))
const b = Math.min(255, Math.max(0, Number(bs)))
const ri = Math.min(5, Math.round((r / 255) * 5))
const gi = Math.min(5, Math.round((g / 255) * 5))
const bi = Math.min(5, Math.round((b / 255) * 5))
const idx = 16 + 36 * ri + 6 * gi + bi
return '\x1b[38;5;' + idx + 'm'
}
)
} else if (depth === '16' || depth === '8' || depth === 'ansi') {
v = v.replace(
/\x1b\[38;2;(\d+);(\d+);(\d+)m/g,
(_, rs, gs, bs) => {
const r = Number(rs)
const g = Number(gs)
const b = Number(bs)
const lum = 0.299 * r + 0.587 * g + 0.114 * b
const bold = lum > 195 ? '1;' : ''
const max = Math.max(r, g, b)
if (max < 48) return '\x1b[90m'
if (r > g * 1.25 && r > b * 1.25) return '\x1b[' + bold + '31m'
if (g > r * 1.25 && g > b * 1.25) return '\x1b[' + bold + '32m'
if (b > r * 1.25 && b > g * 1.25) return '\x1b[' + bold + '34m'
if (r > 200 && g > 200 && b < 120) return '\x1b[' + bold + '33m'
if (g > 180 && b > 180) return '\x1b[' + bold + '36m'
if (r > 180 && b > 180) return '\x1b[' + bold + '35m'
return '\x1b[' + bold + '37m'
}
)
}
env[k] = v
}
}
/**
* @returns {string[]}
*/
export function bareOsListThemeNames() {
return Object.keys(BARE_OS_THEME_PRESETS).sort()
}
/**
* @param {string} name
* @returns {BareOsThemePreset | null}
*/
export function bareOsGetThemePreset(name) {
const k = String(name || 'default')
.toLowerCase()
.trim()
.replace(/\s+/g, '_')
return BARE_OS_THEME_PRESETS[k] || null
}
/**
* Apply active theme to ctx.vfs.env (REPL colors + LS_COLORS when not user-locked).
* @param {Record<string, unknown>} ctx
*/
export async function applyBareOsThemeFromEnv(ctx) {
const vfs = ctx.vfs
const env = vfs && vfs.env
if (!env || typeof env !== 'object') return
const rawName = env.BARE_OS_THEME
const themeKey = String(rawName != null && rawName !== '' ? rawName : 'default')
.toLowerCase()
.trim()
.replace(/\s+/g, '_')
const preset =
BARE_OS_THEME_PRESETS[themeKey] || BARE_OS_THEME_PRESETS.default
for (const [short, seq] of Object.entries(preset.colors || {})) {
env['BARE_OS_COLOR_' + short.toUpperCase()] = seq
}
const locked = env.BARE_OS_LS_COLORS_LOCKED === '1'
const hadLsColors =
env.LS_COLORS != null && String(env.LS_COLORS).trim() !== ''
const dcPath = env.BARE_OS_DIRCOLORS
if (dcPath && String(dcPath).trim() && !locked) {
try {
const buf = await vfs.readFile(dcPath)
if (buf && ctx.b4a) {
const text = ctx.b4a.toString(buf)
const term = env.TERM || ''
const db = bareParseDircolorsDatabase(text, term)
env.LS_COLORS = bareSerializeLsColors(db)
}
} catch {
/* keep existing LS_COLORS */
}
} else if (!hadLsColors && !locked) {
env.LS_COLORS =
preset.lsColors != null ? preset.lsColors : bareDefaultLsColorsString()
}
bareOsDowngradeReplColorsForDepth(env)
}