feat(stats): add v1 immersion stats dashboard (#19)

This commit is contained in:
2026-03-20 02:43:28 -07:00
committed by GitHub
parent 42abdd1268
commit 6749ff843c
555 changed files with 46356 additions and 2553 deletions

View File

@@ -122,12 +122,20 @@ export function createDefaultArgs(launcherConfig: LauncherYoutubeSubgenConfig):
jellyfinPlay: false,
jellyfinDiscovery: false,
dictionary: false,
stats: false,
statsBackground: false,
statsStop: false,
statsCleanup: false,
statsCleanupVocab: false,
statsCleanupLifetime: false,
doctor: false,
doctorRefreshKnownWords: false,
configPath: false,
configShow: false,
mpvIdle: false,
mpvSocket: false,
mpvStatus: false,
mpvArgs: '',
appPassthrough: false,
appArgs: [],
jellyfinServer: '',
@@ -183,15 +191,23 @@ export function applyRootOptionsToArgs(
if (options.rofi === true) parsed.useRofi = true;
if (options.startOverlay === true) parsed.autoStartOverlay = true;
if (options.texthooker === false) parsed.useTexthooker = false;
if (typeof options.args === 'string') parsed.mpvArgs = options.args;
if (typeof rootTarget === 'string' && rootTarget) ensureTarget(rootTarget, parsed);
}
export function applyInvocationsToArgs(parsed: Args, invocations: CliInvocations): void {
if (invocations.dictionaryTriggered) parsed.dictionary = true;
if (invocations.statsTriggered) parsed.stats = true;
if (invocations.statsBackground) parsed.statsBackground = true;
if (invocations.statsStop) parsed.statsStop = true;
if (invocations.statsCleanup) parsed.statsCleanup = true;
if (invocations.statsCleanupVocab) parsed.statsCleanupVocab = true;
if (invocations.statsCleanupLifetime) parsed.statsCleanupLifetime = true;
if (invocations.dictionaryTarget) {
parsed.dictionaryTarget = parseDictionaryTarget(invocations.dictionaryTarget);
}
if (invocations.doctorTriggered) parsed.doctor = true;
if (invocations.doctorRefreshKnownWords) parsed.doctorRefreshKnownWords = true;
if (invocations.texthookerTriggered) parsed.texthookerOnly = true;
if (invocations.jellyfinInvocation) {
@@ -256,6 +272,9 @@ export function applyInvocationsToArgs(parsed: Args, invocations: CliInvocations
if (invocations.dictionaryLogLevel) {
parsed.logLevel = parseLogLevel(invocations.dictionaryLogLevel);
}
if (invocations.statsLogLevel) {
parsed.logLevel = parseLogLevel(invocations.statsLogLevel);
}
if (invocations.doctorLogLevel) parsed.logLevel = parseLogLevel(invocations.doctorLogLevel);
if (invocations.texthookerLogLevel)

View File

@@ -40,8 +40,16 @@ export interface CliInvocations {
dictionaryTriggered: boolean;
dictionaryTarget: string | null;
dictionaryLogLevel: string | null;
statsTriggered: boolean;
statsBackground: boolean;
statsStop: boolean;
statsCleanup: boolean;
statsCleanupVocab: boolean;
statsCleanupLifetime: boolean;
statsLogLevel: string | null;
doctorTriggered: boolean;
doctorLogLevel: string | null;
doctorRefreshKnownWords: boolean;
texthookerTriggered: boolean;
texthookerLogLevel: string | null;
}
@@ -50,6 +58,7 @@ function applyRootOptions(program: Command): void {
program
.option('-b, --backend <backend>', 'Display backend')
.option('-d, --directory <dir>', 'Directory to browse')
.option('-a, --args <args>', 'Pass arguments to MPV')
.option('-r, --recursive', 'Search directories recursively')
.option('-p, --profile <profile>', 'MPV profile')
.option('--start', 'Explicitly start overlay')
@@ -87,6 +96,7 @@ function getTopLevelCommand(argv: string[]): { name: string; index: number } | n
'mpv',
'dictionary',
'dict',
'stats',
'texthooker',
'app',
'bin',
@@ -95,6 +105,8 @@ function getTopLevelCommand(argv: string[]): { name: string; index: number } | n
const optionsWithValue = new Set([
'-b',
'--backend',
'-a',
'--args',
'-d',
'--directory',
'-p',
@@ -137,7 +149,15 @@ export function parseCliPrograms(
let dictionaryTriggered = false;
let dictionaryTarget: string | null = null;
let dictionaryLogLevel: string | null = null;
let statsTriggered = false;
let statsBackground = false;
let statsStop = false;
let statsCleanup = false;
let statsCleanupVocab = false;
let statsCleanupLifetime = false;
let statsLogLevel: string | null = null;
let doctorLogLevel: string | null = null;
let doctorRefreshKnownWords = false;
let texthookerLogLevel: string | null = null;
let doctorTriggered = false;
let texthookerTriggered = false;
@@ -241,13 +261,63 @@ export function parseCliPrograms(
dictionaryLogLevel = typeof options.logLevel === 'string' ? options.logLevel : null;
});
commandProgram
.command('stats')
.description('Launch the local immersion stats dashboard')
.argument('[action]', 'cleanup|rebuild|backfill')
.option('-b, --background', 'Start the stats server in the background')
.option('-s, --stop', 'Stop the background stats server')
.option('-v, --vocab', 'Clean vocabulary rows in the stats database')
.option('-l, --lifetime', 'Rebuild lifetime summary rows from retained data')
.option('--log-level <level>', 'Log level')
.action((action: string | undefined, options: Record<string, unknown>) => {
statsTriggered = true;
const normalizedAction = (action || '').toLowerCase();
statsBackground = options.background === true;
statsStop = options.stop === true;
if (statsBackground && statsStop) {
throw new Error('Stats background and stop flags cannot be combined.');
}
if (
normalizedAction &&
normalizedAction !== 'cleanup' &&
normalizedAction !== 'rebuild' &&
normalizedAction !== 'backfill'
) {
throw new Error(
'Invalid stats action. Valid values are cleanup, rebuild, or backfill.',
);
}
if (normalizedAction && (statsBackground || statsStop)) {
throw new Error('Stats background and stop flags cannot be combined with stats actions.');
}
if (
normalizedAction !== 'cleanup' &&
(options.vocab === true || options.lifetime === true)
) {
throw new Error('Stats --vocab and --lifetime flags require the cleanup action.');
}
if (normalizedAction === 'cleanup') {
statsCleanup = true;
statsCleanupLifetime = options.lifetime === true;
statsCleanupVocab = statsCleanupLifetime ? false : options.vocab !== false;
} else if (normalizedAction === 'rebuild' || normalizedAction === 'backfill') {
statsCleanup = true;
statsCleanupLifetime = true;
statsCleanupVocab = false;
}
statsLogLevel = typeof options.logLevel === 'string' ? options.logLevel : null;
});
commandProgram
.command('doctor')
.description('Run dependency and environment checks')
.option('--refresh-known-words', 'Refresh known words cache')
.option('--log-level <level>', 'Log level')
.action((options: Record<string, unknown>) => {
doctorTriggered = true;
doctorLogLevel = typeof options.logLevel === 'string' ? options.logLevel : null;
doctorRefreshKnownWords = options.refreshKnownWords === true;
});
commandProgram
@@ -319,8 +389,16 @@ export function parseCliPrograms(
dictionaryTriggered,
dictionaryTarget,
dictionaryLogLevel,
statsTriggered,
statsBackground,
statsStop,
statsCleanup,
statsCleanupVocab,
statsCleanupLifetime,
statsLogLevel,
doctorTriggered,
doctorLogLevel,
doctorRefreshKnownWords,
texthookerTriggered,
texthookerLogLevel,
},