mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-04-16 06:32:39 +00:00
328 lines
13 KiB
JavaScript
328 lines
13 KiB
JavaScript
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import * as os from 'node:os';
|
|
import { getHudPluginDir } from './claude-config-dir.js';
|
|
export const DEFAULT_ELEMENT_ORDER = [
|
|
'project',
|
|
'context',
|
|
'usage',
|
|
'memory',
|
|
'environment',
|
|
'tools',
|
|
'agents',
|
|
'todos',
|
|
];
|
|
const KNOWN_ELEMENTS = new Set(DEFAULT_ELEMENT_ORDER);
|
|
export const DEFAULT_CONFIG = {
|
|
language: 'en',
|
|
lineLayout: 'expanded',
|
|
showSeparators: false,
|
|
pathLevels: 1,
|
|
elementOrder: [...DEFAULT_ELEMENT_ORDER],
|
|
gitStatus: {
|
|
enabled: true,
|
|
showDirty: true,
|
|
showAheadBehind: false,
|
|
showFileStats: false,
|
|
pushWarningThreshold: 0,
|
|
pushCriticalThreshold: 0,
|
|
},
|
|
display: {
|
|
showModel: true,
|
|
showProject: true,
|
|
showContextBar: true,
|
|
contextValue: 'percent',
|
|
showConfigCounts: false,
|
|
showCost: false,
|
|
showDuration: false,
|
|
showSpeed: false,
|
|
showTokenBreakdown: true,
|
|
showUsage: true,
|
|
usageBarEnabled: true,
|
|
showTools: false,
|
|
showAgents: false,
|
|
showTodos: false,
|
|
showSessionName: false,
|
|
showClaudeCodeVersion: false,
|
|
showMemoryUsage: false,
|
|
showSessionTokens: false,
|
|
showOutputStyle: false,
|
|
autocompactBuffer: 'enabled',
|
|
usageThreshold: 0,
|
|
sevenDayThreshold: 80,
|
|
environmentThreshold: 0,
|
|
modelFormat: 'full',
|
|
modelOverride: '',
|
|
customLine: '',
|
|
},
|
|
colors: {
|
|
context: 'green',
|
|
usage: 'brightBlue',
|
|
warning: 'yellow',
|
|
usageWarning: 'brightMagenta',
|
|
critical: 'red',
|
|
model: 'cyan',
|
|
project: 'yellow',
|
|
git: 'magenta',
|
|
gitBranch: 'cyan',
|
|
label: 'dim',
|
|
custom: 208,
|
|
},
|
|
};
|
|
export function getConfigPath() {
|
|
const homeDir = os.homedir();
|
|
return path.join(getHudPluginDir(homeDir), 'config.json');
|
|
}
|
|
function validatePathLevels(value) {
|
|
return value === 1 || value === 2 || value === 3;
|
|
}
|
|
function validateLineLayout(value) {
|
|
return value === 'compact' || value === 'expanded';
|
|
}
|
|
function validateAutocompactBuffer(value) {
|
|
return value === 'enabled' || value === 'disabled';
|
|
}
|
|
function validateContextValue(value) {
|
|
return value === 'percent' || value === 'tokens' || value === 'remaining' || value === 'both';
|
|
}
|
|
function validateLanguage(value) {
|
|
return value === 'en' || value === 'zh';
|
|
}
|
|
function validateModelFormat(value) {
|
|
return value === 'full' || value === 'compact' || value === 'short';
|
|
}
|
|
function validateColorName(value) {
|
|
return value === 'dim'
|
|
|| value === 'red'
|
|
|| value === 'green'
|
|
|| value === 'yellow'
|
|
|| value === 'magenta'
|
|
|| value === 'cyan'
|
|
|| value === 'brightBlue'
|
|
|| value === 'brightMagenta';
|
|
}
|
|
const HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
function validateColorValue(value) {
|
|
if (validateColorName(value))
|
|
return true;
|
|
if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255)
|
|
return true;
|
|
if (typeof value === 'string' && HEX_COLOR_PATTERN.test(value))
|
|
return true;
|
|
return false;
|
|
}
|
|
function validateElementOrder(value) {
|
|
if (!Array.isArray(value) || value.length === 0) {
|
|
return [...DEFAULT_ELEMENT_ORDER];
|
|
}
|
|
const seen = new Set();
|
|
const elementOrder = [];
|
|
for (const item of value) {
|
|
if (typeof item !== 'string' || !KNOWN_ELEMENTS.has(item)) {
|
|
continue;
|
|
}
|
|
const element = item;
|
|
if (seen.has(element)) {
|
|
continue;
|
|
}
|
|
seen.add(element);
|
|
elementOrder.push(element);
|
|
}
|
|
return elementOrder.length > 0 ? elementOrder : [...DEFAULT_ELEMENT_ORDER];
|
|
}
|
|
function migrateConfig(userConfig) {
|
|
const migrated = { ...userConfig };
|
|
if ('layout' in userConfig && !('lineLayout' in userConfig)) {
|
|
if (typeof userConfig.layout === 'string') {
|
|
// Legacy string migration (v0.0.x → v0.1.x)
|
|
if (userConfig.layout === 'separators') {
|
|
migrated.lineLayout = 'compact';
|
|
migrated.showSeparators = true;
|
|
}
|
|
else {
|
|
migrated.lineLayout = 'compact';
|
|
migrated.showSeparators = false;
|
|
}
|
|
}
|
|
else if (typeof userConfig.layout === 'object' && userConfig.layout !== null) {
|
|
// Object layout written by third-party tools — extract nested fields
|
|
const obj = userConfig.layout;
|
|
if (typeof obj.lineLayout === 'string')
|
|
migrated.lineLayout = obj.lineLayout;
|
|
if (typeof obj.showSeparators === 'boolean')
|
|
migrated.showSeparators = obj.showSeparators;
|
|
if (typeof obj.pathLevels === 'number')
|
|
migrated.pathLevels = obj.pathLevels;
|
|
}
|
|
delete migrated.layout;
|
|
}
|
|
return migrated;
|
|
}
|
|
function validateThreshold(value, max = 100) {
|
|
if (typeof value !== 'number')
|
|
return 0;
|
|
return Math.max(0, Math.min(max, value));
|
|
}
|
|
function validateCountThreshold(value) {
|
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
return 0;
|
|
}
|
|
return Math.max(0, Math.floor(value));
|
|
}
|
|
export function mergeConfig(userConfig) {
|
|
const migrated = migrateConfig(userConfig);
|
|
const language = validateLanguage(migrated.language)
|
|
? migrated.language
|
|
: DEFAULT_CONFIG.language;
|
|
const lineLayout = validateLineLayout(migrated.lineLayout)
|
|
? migrated.lineLayout
|
|
: DEFAULT_CONFIG.lineLayout;
|
|
const showSeparators = typeof migrated.showSeparators === 'boolean'
|
|
? migrated.showSeparators
|
|
: DEFAULT_CONFIG.showSeparators;
|
|
const pathLevels = validatePathLevels(migrated.pathLevels)
|
|
? migrated.pathLevels
|
|
: DEFAULT_CONFIG.pathLevels;
|
|
const elementOrder = validateElementOrder(migrated.elementOrder);
|
|
const gitStatus = {
|
|
enabled: typeof migrated.gitStatus?.enabled === 'boolean'
|
|
? migrated.gitStatus.enabled
|
|
: DEFAULT_CONFIG.gitStatus.enabled,
|
|
showDirty: typeof migrated.gitStatus?.showDirty === 'boolean'
|
|
? migrated.gitStatus.showDirty
|
|
: DEFAULT_CONFIG.gitStatus.showDirty,
|
|
showAheadBehind: typeof migrated.gitStatus?.showAheadBehind === 'boolean'
|
|
? migrated.gitStatus.showAheadBehind
|
|
: DEFAULT_CONFIG.gitStatus.showAheadBehind,
|
|
showFileStats: typeof migrated.gitStatus?.showFileStats === 'boolean'
|
|
? migrated.gitStatus.showFileStats
|
|
: DEFAULT_CONFIG.gitStatus.showFileStats,
|
|
pushWarningThreshold: validateCountThreshold(migrated.gitStatus?.pushWarningThreshold),
|
|
pushCriticalThreshold: validateCountThreshold(migrated.gitStatus?.pushCriticalThreshold),
|
|
};
|
|
const display = {
|
|
showModel: typeof migrated.display?.showModel === 'boolean'
|
|
? migrated.display.showModel
|
|
: DEFAULT_CONFIG.display.showModel,
|
|
showProject: typeof migrated.display?.showProject === 'boolean'
|
|
? migrated.display.showProject
|
|
: DEFAULT_CONFIG.display.showProject,
|
|
showContextBar: typeof migrated.display?.showContextBar === 'boolean'
|
|
? migrated.display.showContextBar
|
|
: DEFAULT_CONFIG.display.showContextBar,
|
|
contextValue: validateContextValue(migrated.display?.contextValue)
|
|
? migrated.display.contextValue
|
|
: DEFAULT_CONFIG.display.contextValue,
|
|
showConfigCounts: typeof migrated.display?.showConfigCounts === 'boolean'
|
|
? migrated.display.showConfigCounts
|
|
: DEFAULT_CONFIG.display.showConfigCounts,
|
|
showCost: typeof migrated.display?.showCost === 'boolean'
|
|
? migrated.display.showCost
|
|
: DEFAULT_CONFIG.display.showCost,
|
|
showDuration: typeof migrated.display?.showDuration === 'boolean'
|
|
? migrated.display.showDuration
|
|
: DEFAULT_CONFIG.display.showDuration,
|
|
showSpeed: typeof migrated.display?.showSpeed === 'boolean'
|
|
? migrated.display.showSpeed
|
|
: DEFAULT_CONFIG.display.showSpeed,
|
|
showTokenBreakdown: typeof migrated.display?.showTokenBreakdown === 'boolean'
|
|
? migrated.display.showTokenBreakdown
|
|
: DEFAULT_CONFIG.display.showTokenBreakdown,
|
|
showUsage: typeof migrated.display?.showUsage === 'boolean'
|
|
? migrated.display.showUsage
|
|
: DEFAULT_CONFIG.display.showUsage,
|
|
usageBarEnabled: typeof migrated.display?.usageBarEnabled === 'boolean'
|
|
? migrated.display.usageBarEnabled
|
|
: DEFAULT_CONFIG.display.usageBarEnabled,
|
|
showTools: typeof migrated.display?.showTools === 'boolean'
|
|
? migrated.display.showTools
|
|
: DEFAULT_CONFIG.display.showTools,
|
|
showAgents: typeof migrated.display?.showAgents === 'boolean'
|
|
? migrated.display.showAgents
|
|
: DEFAULT_CONFIG.display.showAgents,
|
|
showTodos: typeof migrated.display?.showTodos === 'boolean'
|
|
? migrated.display.showTodos
|
|
: DEFAULT_CONFIG.display.showTodos,
|
|
showSessionName: typeof migrated.display?.showSessionName === 'boolean'
|
|
? migrated.display.showSessionName
|
|
: DEFAULT_CONFIG.display.showSessionName,
|
|
showClaudeCodeVersion: typeof migrated.display?.showClaudeCodeVersion === 'boolean'
|
|
? migrated.display.showClaudeCodeVersion
|
|
: DEFAULT_CONFIG.display.showClaudeCodeVersion,
|
|
showMemoryUsage: typeof migrated.display?.showMemoryUsage === 'boolean'
|
|
? migrated.display.showMemoryUsage
|
|
: DEFAULT_CONFIG.display.showMemoryUsage,
|
|
showSessionTokens: typeof migrated.display?.showSessionTokens === 'boolean'
|
|
? migrated.display.showSessionTokens
|
|
: DEFAULT_CONFIG.display.showSessionTokens,
|
|
showOutputStyle: typeof migrated.display?.showOutputStyle === 'boolean'
|
|
? migrated.display.showOutputStyle
|
|
: DEFAULT_CONFIG.display.showOutputStyle,
|
|
autocompactBuffer: validateAutocompactBuffer(migrated.display?.autocompactBuffer)
|
|
? migrated.display.autocompactBuffer
|
|
: DEFAULT_CONFIG.display.autocompactBuffer,
|
|
usageThreshold: validateThreshold(migrated.display?.usageThreshold, 100),
|
|
sevenDayThreshold: validateThreshold(migrated.display?.sevenDayThreshold, 100),
|
|
environmentThreshold: validateThreshold(migrated.display?.environmentThreshold, 100),
|
|
modelFormat: validateModelFormat(migrated.display?.modelFormat)
|
|
? migrated.display.modelFormat
|
|
: DEFAULT_CONFIG.display.modelFormat,
|
|
modelOverride: typeof migrated.display?.modelOverride === 'string'
|
|
? migrated.display.modelOverride.slice(0, 80)
|
|
: DEFAULT_CONFIG.display.modelOverride,
|
|
customLine: typeof migrated.display?.customLine === 'string'
|
|
? migrated.display.customLine.slice(0, 80)
|
|
: DEFAULT_CONFIG.display.customLine,
|
|
};
|
|
const colors = {
|
|
context: validateColorValue(migrated.colors?.context)
|
|
? migrated.colors.context
|
|
: DEFAULT_CONFIG.colors.context,
|
|
usage: validateColorValue(migrated.colors?.usage)
|
|
? migrated.colors.usage
|
|
: DEFAULT_CONFIG.colors.usage,
|
|
warning: validateColorValue(migrated.colors?.warning)
|
|
? migrated.colors.warning
|
|
: DEFAULT_CONFIG.colors.warning,
|
|
usageWarning: validateColorValue(migrated.colors?.usageWarning)
|
|
? migrated.colors.usageWarning
|
|
: DEFAULT_CONFIG.colors.usageWarning,
|
|
critical: validateColorValue(migrated.colors?.critical)
|
|
? migrated.colors.critical
|
|
: DEFAULT_CONFIG.colors.critical,
|
|
model: validateColorValue(migrated.colors?.model)
|
|
? migrated.colors.model
|
|
: DEFAULT_CONFIG.colors.model,
|
|
project: validateColorValue(migrated.colors?.project)
|
|
? migrated.colors.project
|
|
: DEFAULT_CONFIG.colors.project,
|
|
git: validateColorValue(migrated.colors?.git)
|
|
? migrated.colors.git
|
|
: DEFAULT_CONFIG.colors.git,
|
|
gitBranch: validateColorValue(migrated.colors?.gitBranch)
|
|
? migrated.colors.gitBranch
|
|
: DEFAULT_CONFIG.colors.gitBranch,
|
|
label: validateColorValue(migrated.colors?.label)
|
|
? migrated.colors.label
|
|
: DEFAULT_CONFIG.colors.label,
|
|
custom: validateColorValue(migrated.colors?.custom)
|
|
? migrated.colors.custom
|
|
: DEFAULT_CONFIG.colors.custom,
|
|
};
|
|
return { language, lineLayout, showSeparators, pathLevels, elementOrder, gitStatus, display, colors };
|
|
}
|
|
export async function loadConfig() {
|
|
const configPath = getConfigPath();
|
|
try {
|
|
if (!fs.existsSync(configPath)) {
|
|
return mergeConfig({});
|
|
}
|
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
const userConfig = JSON.parse(content);
|
|
return mergeConfig(userConfig);
|
|
}
|
|
catch {
|
|
return mergeConfig({});
|
|
}
|
|
}
|
|
//# sourceMappingURL=config.js.map
|