mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-02 18:22:42 -08:00
feat(core): add Electron runtime, services, and app composition
This commit is contained in:
103
src/cli/args.test.ts
Normal file
103
src/cli/args.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { hasExplicitCommand, parseArgs, shouldStartApp } from './args';
|
||||
|
||||
test('parseArgs parses booleans and value flags', () => {
|
||||
const args = parseArgs([
|
||||
'--background',
|
||||
'--start',
|
||||
'--socket',
|
||||
'/tmp/mpv.sock',
|
||||
'--backend=hyprland',
|
||||
'--port',
|
||||
'6000',
|
||||
'--log-level',
|
||||
'warn',
|
||||
'--debug',
|
||||
'--jellyfin-play',
|
||||
'--jellyfin-server',
|
||||
'http://jellyfin.local:8096',
|
||||
'--jellyfin-item-id',
|
||||
'item-123',
|
||||
'--jellyfin-audio-stream-index',
|
||||
'2',
|
||||
]);
|
||||
|
||||
assert.equal(args.background, true);
|
||||
assert.equal(args.start, true);
|
||||
assert.equal(args.socketPath, '/tmp/mpv.sock');
|
||||
assert.equal(args.backend, 'hyprland');
|
||||
assert.equal(args.texthookerPort, 6000);
|
||||
assert.equal(args.logLevel, 'warn');
|
||||
assert.equal(args.debug, true);
|
||||
assert.equal(args.jellyfinPlay, true);
|
||||
assert.equal(args.jellyfinServer, 'http://jellyfin.local:8096');
|
||||
assert.equal(args.jellyfinItemId, 'item-123');
|
||||
assert.equal(args.jellyfinAudioStreamIndex, 2);
|
||||
});
|
||||
|
||||
test('parseArgs ignores missing value after --log-level', () => {
|
||||
const args = parseArgs(['--log-level', '--start']);
|
||||
assert.equal(args.logLevel, undefined);
|
||||
assert.equal(args.start, true);
|
||||
});
|
||||
|
||||
test('hasExplicitCommand and shouldStartApp preserve command intent', () => {
|
||||
const stopOnly = parseArgs(['--stop']);
|
||||
assert.equal(hasExplicitCommand(stopOnly), true);
|
||||
assert.equal(shouldStartApp(stopOnly), false);
|
||||
|
||||
const toggle = parseArgs(['--toggle-visible-overlay']);
|
||||
assert.equal(hasExplicitCommand(toggle), true);
|
||||
assert.equal(shouldStartApp(toggle), true);
|
||||
|
||||
const noCommand = parseArgs(['--log-level', 'warn']);
|
||||
assert.equal(hasExplicitCommand(noCommand), false);
|
||||
assert.equal(shouldStartApp(noCommand), false);
|
||||
|
||||
const refreshKnownWords = parseArgs(['--refresh-known-words']);
|
||||
assert.equal(refreshKnownWords.help, false);
|
||||
assert.equal(hasExplicitCommand(refreshKnownWords), true);
|
||||
assert.equal(shouldStartApp(refreshKnownWords), false);
|
||||
|
||||
const anilistStatus = parseArgs(['--anilist-status']);
|
||||
assert.equal(anilistStatus.anilistStatus, true);
|
||||
assert.equal(hasExplicitCommand(anilistStatus), true);
|
||||
assert.equal(shouldStartApp(anilistStatus), false);
|
||||
|
||||
const anilistRetryQueue = parseArgs(['--anilist-retry-queue']);
|
||||
assert.equal(anilistRetryQueue.anilistRetryQueue, true);
|
||||
assert.equal(hasExplicitCommand(anilistRetryQueue), true);
|
||||
assert.equal(shouldStartApp(anilistRetryQueue), false);
|
||||
|
||||
const jellyfinLibraries = parseArgs(['--jellyfin-libraries']);
|
||||
assert.equal(jellyfinLibraries.jellyfinLibraries, true);
|
||||
assert.equal(hasExplicitCommand(jellyfinLibraries), true);
|
||||
assert.equal(shouldStartApp(jellyfinLibraries), false);
|
||||
|
||||
const jellyfinSetup = parseArgs(['--jellyfin']);
|
||||
assert.equal(jellyfinSetup.jellyfin, true);
|
||||
assert.equal(hasExplicitCommand(jellyfinSetup), true);
|
||||
assert.equal(shouldStartApp(jellyfinSetup), true);
|
||||
|
||||
const jellyfinPlay = parseArgs(['--jellyfin-play']);
|
||||
assert.equal(jellyfinPlay.jellyfinPlay, true);
|
||||
assert.equal(hasExplicitCommand(jellyfinPlay), true);
|
||||
assert.equal(shouldStartApp(jellyfinPlay), true);
|
||||
|
||||
const jellyfinSubtitles = parseArgs(['--jellyfin-subtitles', '--jellyfin-subtitle-urls']);
|
||||
assert.equal(jellyfinSubtitles.jellyfinSubtitles, true);
|
||||
assert.equal(jellyfinSubtitles.jellyfinSubtitleUrlsOnly, true);
|
||||
assert.equal(hasExplicitCommand(jellyfinSubtitles), true);
|
||||
assert.equal(shouldStartApp(jellyfinSubtitles), false);
|
||||
|
||||
const jellyfinRemoteAnnounce = parseArgs(['--jellyfin-remote-announce']);
|
||||
assert.equal(jellyfinRemoteAnnounce.jellyfinRemoteAnnounce, true);
|
||||
assert.equal(hasExplicitCommand(jellyfinRemoteAnnounce), true);
|
||||
assert.equal(shouldStartApp(jellyfinRemoteAnnounce), false);
|
||||
|
||||
const background = parseArgs(['--background']);
|
||||
assert.equal(background.background, true);
|
||||
assert.equal(hasExplicitCommand(background), true);
|
||||
assert.equal(shouldStartApp(background), true);
|
||||
});
|
||||
352
src/cli/args.ts
Normal file
352
src/cli/args.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
export interface CliArgs {
|
||||
background: boolean;
|
||||
start: boolean;
|
||||
stop: boolean;
|
||||
toggle: boolean;
|
||||
toggleVisibleOverlay: boolean;
|
||||
toggleInvisibleOverlay: boolean;
|
||||
settings: boolean;
|
||||
show: boolean;
|
||||
hide: boolean;
|
||||
showVisibleOverlay: boolean;
|
||||
hideVisibleOverlay: boolean;
|
||||
showInvisibleOverlay: boolean;
|
||||
hideInvisibleOverlay: boolean;
|
||||
copySubtitle: boolean;
|
||||
copySubtitleMultiple: boolean;
|
||||
mineSentence: boolean;
|
||||
mineSentenceMultiple: boolean;
|
||||
updateLastCardFromClipboard: boolean;
|
||||
refreshKnownWords: boolean;
|
||||
toggleSecondarySub: boolean;
|
||||
triggerFieldGrouping: boolean;
|
||||
triggerSubsync: boolean;
|
||||
markAudioCard: boolean;
|
||||
openRuntimeOptions: boolean;
|
||||
anilistStatus: boolean;
|
||||
anilistLogout: boolean;
|
||||
anilistSetup: boolean;
|
||||
anilistRetryQueue: boolean;
|
||||
jellyfin: boolean;
|
||||
jellyfinLogin: boolean;
|
||||
jellyfinLogout: boolean;
|
||||
jellyfinLibraries: boolean;
|
||||
jellyfinItems: boolean;
|
||||
jellyfinSubtitles: boolean;
|
||||
jellyfinSubtitleUrlsOnly: boolean;
|
||||
jellyfinPlay: boolean;
|
||||
jellyfinRemoteAnnounce: boolean;
|
||||
texthooker: boolean;
|
||||
help: boolean;
|
||||
autoStartOverlay: boolean;
|
||||
generateConfig: boolean;
|
||||
configPath?: string;
|
||||
backupOverwrite: boolean;
|
||||
socketPath?: string;
|
||||
backend?: string;
|
||||
texthookerPort?: number;
|
||||
jellyfinServer?: string;
|
||||
jellyfinUsername?: string;
|
||||
jellyfinPassword?: string;
|
||||
jellyfinLibraryId?: string;
|
||||
jellyfinItemId?: string;
|
||||
jellyfinSearch?: string;
|
||||
jellyfinLimit?: number;
|
||||
jellyfinAudioStreamIndex?: number;
|
||||
jellyfinSubtitleStreamIndex?: number;
|
||||
debug: boolean;
|
||||
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
||||
}
|
||||
|
||||
export type CliCommandSource = 'initial' | 'second-instance';
|
||||
|
||||
export function parseArgs(argv: string[]): CliArgs {
|
||||
const args: CliArgs = {
|
||||
background: false,
|
||||
start: false,
|
||||
stop: false,
|
||||
toggle: false,
|
||||
toggleVisibleOverlay: false,
|
||||
toggleInvisibleOverlay: false,
|
||||
settings: false,
|
||||
show: false,
|
||||
hide: false,
|
||||
showVisibleOverlay: false,
|
||||
hideVisibleOverlay: false,
|
||||
showInvisibleOverlay: false,
|
||||
hideInvisibleOverlay: false,
|
||||
copySubtitle: false,
|
||||
copySubtitleMultiple: false,
|
||||
mineSentence: false,
|
||||
mineSentenceMultiple: false,
|
||||
updateLastCardFromClipboard: false,
|
||||
refreshKnownWords: false,
|
||||
toggleSecondarySub: false,
|
||||
triggerFieldGrouping: false,
|
||||
triggerSubsync: false,
|
||||
markAudioCard: false,
|
||||
openRuntimeOptions: false,
|
||||
anilistStatus: false,
|
||||
anilistLogout: false,
|
||||
anilistSetup: false,
|
||||
anilistRetryQueue: false,
|
||||
jellyfin: false,
|
||||
jellyfinLogin: false,
|
||||
jellyfinLogout: false,
|
||||
jellyfinLibraries: false,
|
||||
jellyfinItems: false,
|
||||
jellyfinSubtitles: false,
|
||||
jellyfinSubtitleUrlsOnly: false,
|
||||
jellyfinPlay: false,
|
||||
jellyfinRemoteAnnounce: false,
|
||||
texthooker: false,
|
||||
help: false,
|
||||
autoStartOverlay: false,
|
||||
generateConfig: false,
|
||||
backupOverwrite: false,
|
||||
debug: false,
|
||||
};
|
||||
|
||||
const readValue = (value?: string): string | undefined => {
|
||||
if (!value) return undefined;
|
||||
if (value.startsWith('--')) return undefined;
|
||||
return value;
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (!arg || !arg.startsWith('--')) continue;
|
||||
|
||||
if (arg === '--background') args.background = true;
|
||||
else if (arg === '--start') args.start = true;
|
||||
else if (arg === '--stop') args.stop = true;
|
||||
else if (arg === '--toggle') args.toggle = true;
|
||||
else if (arg === '--toggle-visible-overlay') args.toggleVisibleOverlay = true;
|
||||
else if (arg === '--toggle-invisible-overlay') args.toggleInvisibleOverlay = true;
|
||||
else if (arg === '--settings' || arg === '--yomitan') args.settings = true;
|
||||
else if (arg === '--show') args.show = true;
|
||||
else if (arg === '--hide') args.hide = true;
|
||||
else if (arg === '--show-visible-overlay') args.showVisibleOverlay = true;
|
||||
else if (arg === '--hide-visible-overlay') args.hideVisibleOverlay = true;
|
||||
else if (arg === '--show-invisible-overlay') args.showInvisibleOverlay = true;
|
||||
else if (arg === '--hide-invisible-overlay') args.hideInvisibleOverlay = true;
|
||||
else if (arg === '--copy-subtitle') args.copySubtitle = true;
|
||||
else if (arg === '--copy-subtitle-multiple') args.copySubtitleMultiple = true;
|
||||
else if (arg === '--mine-sentence') args.mineSentence = true;
|
||||
else if (arg === '--mine-sentence-multiple') args.mineSentenceMultiple = true;
|
||||
else if (arg === '--update-last-card-from-clipboard') args.updateLastCardFromClipboard = true;
|
||||
else if (arg === '--refresh-known-words') args.refreshKnownWords = true;
|
||||
else if (arg === '--toggle-secondary-sub') args.toggleSecondarySub = true;
|
||||
else if (arg === '--trigger-field-grouping') args.triggerFieldGrouping = true;
|
||||
else if (arg === '--trigger-subsync') args.triggerSubsync = true;
|
||||
else if (arg === '--mark-audio-card') args.markAudioCard = true;
|
||||
else if (arg === '--open-runtime-options') args.openRuntimeOptions = true;
|
||||
else if (arg === '--anilist-status') args.anilistStatus = true;
|
||||
else if (arg === '--anilist-logout') args.anilistLogout = true;
|
||||
else if (arg === '--anilist-setup') args.anilistSetup = true;
|
||||
else if (arg === '--anilist-retry-queue') args.anilistRetryQueue = true;
|
||||
else if (arg === '--jellyfin') args.jellyfin = true;
|
||||
else if (arg === '--jellyfin-login') args.jellyfinLogin = true;
|
||||
else if (arg === '--jellyfin-logout') args.jellyfinLogout = true;
|
||||
else if (arg === '--jellyfin-libraries') args.jellyfinLibraries = true;
|
||||
else if (arg === '--jellyfin-items') args.jellyfinItems = true;
|
||||
else if (arg === '--jellyfin-subtitles') args.jellyfinSubtitles = true;
|
||||
else if (arg === '--jellyfin-subtitle-urls') {
|
||||
args.jellyfinSubtitles = true;
|
||||
args.jellyfinSubtitleUrlsOnly = true;
|
||||
} else if (arg === '--jellyfin-play') args.jellyfinPlay = true;
|
||||
else if (arg === '--jellyfin-remote-announce') args.jellyfinRemoteAnnounce = true;
|
||||
else if (arg === '--texthooker') args.texthooker = true;
|
||||
else if (arg === '--auto-start-overlay') args.autoStartOverlay = true;
|
||||
else if (arg === '--generate-config') args.generateConfig = true;
|
||||
else if (arg === '--backup-overwrite') args.backupOverwrite = true;
|
||||
else if (arg === '--help') args.help = true;
|
||||
else if (arg === '--debug') args.debug = true;
|
||||
else if (arg.startsWith('--log-level=')) {
|
||||
const value = arg.split('=', 2)[1]?.toLowerCase();
|
||||
if (value === 'debug' || value === 'info' || value === 'warn' || value === 'error') {
|
||||
args.logLevel = value;
|
||||
}
|
||||
} else if (arg === '--log-level') {
|
||||
const value = readValue(argv[i + 1])?.toLowerCase();
|
||||
if (value === 'debug' || value === 'info' || value === 'warn' || value === 'error') {
|
||||
args.logLevel = value;
|
||||
}
|
||||
} else if (arg.startsWith('--config-path=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.configPath = value;
|
||||
} else if (arg === '--config-path') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.configPath = value;
|
||||
} else if (arg.startsWith('--socket=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.socketPath = value;
|
||||
} else if (arg === '--socket') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.socketPath = value;
|
||||
} else if (arg.startsWith('--backend=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.backend = value;
|
||||
} else if (arg === '--backend') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.backend = value;
|
||||
} else if (arg.startsWith('--port=')) {
|
||||
const value = Number(arg.split('=', 2)[1]);
|
||||
if (!Number.isNaN(value)) args.texthookerPort = value;
|
||||
} else if (arg === '--port') {
|
||||
const value = Number(readValue(argv[i + 1]));
|
||||
if (!Number.isNaN(value)) args.texthookerPort = value;
|
||||
} else if (arg.startsWith('--jellyfin-server=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.jellyfinServer = value;
|
||||
} else if (arg === '--jellyfin-server') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.jellyfinServer = value;
|
||||
} else if (arg.startsWith('--jellyfin-username=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.jellyfinUsername = value;
|
||||
} else if (arg === '--jellyfin-username') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.jellyfinUsername = value;
|
||||
} else if (arg.startsWith('--jellyfin-password=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.jellyfinPassword = value;
|
||||
} else if (arg === '--jellyfin-password') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.jellyfinPassword = value;
|
||||
} else if (arg.startsWith('--jellyfin-library-id=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.jellyfinLibraryId = value;
|
||||
} else if (arg === '--jellyfin-library-id') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.jellyfinLibraryId = value;
|
||||
} else if (arg.startsWith('--jellyfin-item-id=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.jellyfinItemId = value;
|
||||
} else if (arg === '--jellyfin-item-id') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.jellyfinItemId = value;
|
||||
} else if (arg.startsWith('--jellyfin-search=')) {
|
||||
const value = arg.split('=', 2)[1];
|
||||
if (value) args.jellyfinSearch = value;
|
||||
} else if (arg === '--jellyfin-search') {
|
||||
const value = readValue(argv[i + 1]);
|
||||
if (value) args.jellyfinSearch = value;
|
||||
} else if (arg.startsWith('--jellyfin-limit=')) {
|
||||
const value = Number(arg.split('=', 2)[1]);
|
||||
if (Number.isFinite(value) && value > 0) args.jellyfinLimit = Math.floor(value);
|
||||
} else if (arg === '--jellyfin-limit') {
|
||||
const value = Number(readValue(argv[i + 1]));
|
||||
if (Number.isFinite(value) && value > 0) args.jellyfinLimit = Math.floor(value);
|
||||
} else if (arg.startsWith('--jellyfin-audio-stream-index=')) {
|
||||
const value = Number(arg.split('=', 2)[1]);
|
||||
if (Number.isInteger(value) && value >= 0) args.jellyfinAudioStreamIndex = value;
|
||||
} else if (arg === '--jellyfin-audio-stream-index') {
|
||||
const value = Number(readValue(argv[i + 1]));
|
||||
if (Number.isInteger(value) && value >= 0) args.jellyfinAudioStreamIndex = value;
|
||||
} else if (arg.startsWith('--jellyfin-subtitle-stream-index=')) {
|
||||
const value = Number(arg.split('=', 2)[1]);
|
||||
if (Number.isInteger(value) && value >= 0) args.jellyfinSubtitleStreamIndex = value;
|
||||
} else if (arg === '--jellyfin-subtitle-stream-index') {
|
||||
const value = Number(readValue(argv[i + 1]));
|
||||
if (Number.isInteger(value) && value >= 0) args.jellyfinSubtitleStreamIndex = value;
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function hasExplicitCommand(args: CliArgs): boolean {
|
||||
return (
|
||||
args.background ||
|
||||
args.start ||
|
||||
args.stop ||
|
||||
args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay ||
|
||||
args.settings ||
|
||||
args.show ||
|
||||
args.hide ||
|
||||
args.showVisibleOverlay ||
|
||||
args.hideVisibleOverlay ||
|
||||
args.showInvisibleOverlay ||
|
||||
args.hideInvisibleOverlay ||
|
||||
args.copySubtitle ||
|
||||
args.copySubtitleMultiple ||
|
||||
args.mineSentence ||
|
||||
args.mineSentenceMultiple ||
|
||||
args.updateLastCardFromClipboard ||
|
||||
args.refreshKnownWords ||
|
||||
args.toggleSecondarySub ||
|
||||
args.triggerFieldGrouping ||
|
||||
args.triggerSubsync ||
|
||||
args.markAudioCard ||
|
||||
args.openRuntimeOptions ||
|
||||
args.anilistStatus ||
|
||||
args.anilistLogout ||
|
||||
args.anilistSetup ||
|
||||
args.anilistRetryQueue ||
|
||||
args.jellyfin ||
|
||||
args.jellyfinLogin ||
|
||||
args.jellyfinLogout ||
|
||||
args.jellyfinLibraries ||
|
||||
args.jellyfinItems ||
|
||||
args.jellyfinSubtitles ||
|
||||
args.jellyfinPlay ||
|
||||
args.jellyfinRemoteAnnounce ||
|
||||
args.texthooker ||
|
||||
args.generateConfig ||
|
||||
args.help
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldStartApp(args: CliArgs): boolean {
|
||||
if (args.stop && !args.start) return false;
|
||||
if (
|
||||
args.background ||
|
||||
args.start ||
|
||||
args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay ||
|
||||
args.copySubtitle ||
|
||||
args.copySubtitleMultiple ||
|
||||
args.mineSentence ||
|
||||
args.mineSentenceMultiple ||
|
||||
args.updateLastCardFromClipboard ||
|
||||
args.toggleSecondarySub ||
|
||||
args.triggerFieldGrouping ||
|
||||
args.triggerSubsync ||
|
||||
args.markAudioCard ||
|
||||
args.openRuntimeOptions ||
|
||||
args.jellyfin ||
|
||||
args.jellyfinPlay ||
|
||||
args.texthooker
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function commandNeedsOverlayRuntime(args: CliArgs): boolean {
|
||||
return (
|
||||
args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay ||
|
||||
args.show ||
|
||||
args.hide ||
|
||||
args.showVisibleOverlay ||
|
||||
args.hideVisibleOverlay ||
|
||||
args.showInvisibleOverlay ||
|
||||
args.hideInvisibleOverlay ||
|
||||
args.copySubtitle ||
|
||||
args.copySubtitleMultiple ||
|
||||
args.mineSentence ||
|
||||
args.mineSentenceMultiple ||
|
||||
args.updateLastCardFromClipboard ||
|
||||
args.toggleSecondarySub ||
|
||||
args.triggerFieldGrouping ||
|
||||
args.triggerSubsync ||
|
||||
args.markAudioCard ||
|
||||
args.openRuntimeOptions
|
||||
);
|
||||
}
|
||||
27
src/cli/help.test.ts
Normal file
27
src/cli/help.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { printHelp } from './help';
|
||||
|
||||
test('printHelp includes configured texthooker port', () => {
|
||||
const original = console.log;
|
||||
let output = '';
|
||||
console.log = (value?: unknown) => {
|
||||
output += String(value);
|
||||
};
|
||||
|
||||
try {
|
||||
printHelp(7777);
|
||||
} finally {
|
||||
console.log = original;
|
||||
}
|
||||
|
||||
assert.match(output, /--help\s+Show this help/);
|
||||
assert.match(output, /default: 7777/);
|
||||
assert.match(output, /--refresh-known-words/);
|
||||
assert.match(output, /--anilist-status/);
|
||||
assert.match(output, /--anilist-retry-queue/);
|
||||
assert.match(output, /--jellyfin\s+Open Jellyfin setup window/);
|
||||
assert.match(output, /--jellyfin-login/);
|
||||
assert.match(output, /--jellyfin-subtitles/);
|
||||
assert.match(output, /--jellyfin-play/);
|
||||
});
|
||||
80
src/cli/help.ts
Normal file
80
src/cli/help.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
export function printHelp(defaultTexthookerPort: number): void {
|
||||
const tty = process.stdout?.isTTY ?? false;
|
||||
const B = tty ? '\x1b[1m' : '';
|
||||
const D = tty ? '\x1b[2m' : '';
|
||||
const R = tty ? '\x1b[0m' : '';
|
||||
|
||||
console.log(`
|
||||
${B}SubMiner${R} — Japanese sentence mining with mpv + Yomitan
|
||||
|
||||
${B}Usage:${R} subminer ${D}[command] [options]${R}
|
||||
|
||||
${B}Session${R}
|
||||
--background Start in tray/background mode
|
||||
--start Connect to mpv and launch overlay
|
||||
--stop Stop the running instance
|
||||
--texthooker Start texthooker server only ${D}(no overlay)${R}
|
||||
|
||||
${B}Overlay${R}
|
||||
--toggle-visible-overlay Toggle subtitle overlay
|
||||
--toggle-invisible-overlay Toggle interactive overlay ${D}(Yomitan lookup)${R}
|
||||
--show-visible-overlay Show subtitle overlay
|
||||
--hide-visible-overlay Hide subtitle overlay
|
||||
--show-invisible-overlay Show interactive overlay
|
||||
--hide-invisible-overlay Hide interactive overlay
|
||||
--settings Open Yomitan settings window
|
||||
--auto-start-overlay Auto-hide mpv subs, show overlay on connect
|
||||
|
||||
${B}Mining${R}
|
||||
--mine-sentence Create Anki card from current subtitle
|
||||
--mine-sentence-multiple Select multiple lines, then mine
|
||||
--copy-subtitle Copy current subtitle to clipboard
|
||||
--copy-subtitle-multiple Enter multi-line copy mode
|
||||
--update-last-card-from-clipboard Update last Anki card from clipboard
|
||||
--mark-audio-card Mark last card as audio-only
|
||||
--trigger-field-grouping Run Kiku field grouping
|
||||
--trigger-subsync Run subtitle sync
|
||||
--toggle-secondary-sub Cycle secondary subtitle mode
|
||||
--refresh-known-words Refresh known words cache
|
||||
--open-runtime-options Open runtime options palette
|
||||
|
||||
${B}AniList${R}
|
||||
--anilist-setup Open AniList authentication flow
|
||||
--anilist-status Show token and retry queue status
|
||||
--anilist-logout Clear stored AniList token
|
||||
--anilist-retry-queue Retry next queued update
|
||||
|
||||
${B}Jellyfin${R}
|
||||
--jellyfin Open Jellyfin setup window
|
||||
--jellyfin-login Authenticate and store session token
|
||||
--jellyfin-logout Clear stored session data
|
||||
--jellyfin-libraries List available libraries
|
||||
--jellyfin-items List items from a library
|
||||
--jellyfin-subtitles List subtitle tracks for an item
|
||||
--jellyfin-subtitle-urls Print subtitle download URLs only
|
||||
--jellyfin-play Stream an item in mpv
|
||||
--jellyfin-remote-announce Broadcast cast-target capability
|
||||
|
||||
${D}Jellyfin options:${R}
|
||||
--jellyfin-server ${D}URL${R} Server URL ${D}(overrides config)${R}
|
||||
--jellyfin-username ${D}NAME${R} Username for login
|
||||
--jellyfin-password ${D}PASS${R} Password for login
|
||||
--jellyfin-library-id ${D}ID${R} Library to browse
|
||||
--jellyfin-item-id ${D}ID${R} Item to play or inspect
|
||||
--jellyfin-search ${D}QUERY${R} Filter items by search term
|
||||
--jellyfin-limit ${D}N${R} Max items returned
|
||||
--jellyfin-audio-stream-index ${D}N${R} Audio stream override
|
||||
--jellyfin-subtitle-stream-index ${D}N${R} Subtitle stream override
|
||||
|
||||
${B}Options${R}
|
||||
--socket ${D}PATH${R} mpv IPC socket path
|
||||
--backend ${D}BACKEND${R} Window tracker ${D}(auto, hyprland, sway, x11, macos)${R}
|
||||
--port ${D}PORT${R} Texthooker server port ${D}(default: ${defaultTexthookerPort})${R}
|
||||
--log-level ${D}LEVEL${R} ${D}debug | info | warn | error${R}
|
||||
--debug Enable debug mode ${D}(alias: --dev)${R}
|
||||
--generate-config Write default config.jsonc
|
||||
--config-path ${D}PATH${R} Target path for --generate-config
|
||||
--backup-overwrite Backup existing config before overwrite
|
||||
--help Show this help
|
||||
`);
|
||||
}
|
||||
Reference in New Issue
Block a user