Files
Move/src/move.ts
T
nokeo08 5af253283e Introduce failRuntime() to mirror failUser() in move.ts
The two stderr-fatal paths used to be asymmetric: failUser() is a named
helper with 'move:' prefix and a 'Try --help' hint, while the runKeeper
catch was an inline 'console.error("Error:", err)' + process.exit(1).

failRuntime() now sits next to failUser(), so the entry-point reads as
'one of two well-named fatal paths.' It also prefers err.stack when
available, giving better diagnostics for nut.js / Accessibility-permission
failures than the previous formatter.

Same observable behavior on the success path; failure messages on the
runtime path are now more debuggable.
2026-06-17 16:32:29 -05:00

102 lines
3.5 KiB
TypeScript
Executable File

#!/usr/bin/env bun
/**
* move.ts
* -------
* Entry point for the `move` CLI.
*
* Thin shim that ties the four logic modules together:
* - `cli.ts` parses and validates `process.argv`.
* - `configFile.ts` loads and validates the JSON config file.
* - `config.ts` holds defaults and the layered `resolveConfig` overlay.
* - `keeper.ts` owns the synthetic-activity sweep and idle-watch loop.
*
* Order of operations:
* 1. Parse CLI args. Bad input -> stderr + usage hint, exit 2.
* 2. `--help` / `--version` short-circuit before any I/O or mouse work.
* 3. Load + validate the config file (default XDG path, or `--config
* <path>` if supplied). Validation failures share the exit-2 path.
* 4. Resolve the full `Config` (CLI > file > DEFAULT_CONFIG) — verbose
* lives inside `Config` and is layered with the same precedence as
* the numeric fields.
* 5. Run keeper. Any unhandled rejection exits 1.
*
* Runtime: Bun (uses `@nut-tree-fork/nut-js` via `keeper.ts`). The shebang
* above lets this file run as a real CLI once linked via `bun link`.
*/
import { parseCliArgs, printHelp, VERSION } from "./cli.ts";
import type { ParsedCliArgs } from "./cli.ts";
import { resolveConfig } from "./config.ts";
import type { ConfigOverrides } from "./config.ts";
import { loadConfigFile } from "./configFile.ts";
import { runKeeper } from "./keeper.ts";
/**
* Print a user-error message and exit 2. Used for anything that comes from
* invalid input: unknown CLI flags, bad numbers, malformed or missing
* config files, unresolvable default paths. The accompanying "Try 'move
* --help'" pointer is appropriate for these cases.
*
* Returns `never` so callers can invoke it without TypeScript flagging
* "variable might be undefined" downstream.
*/
function failUser(err: unknown): never {
const msg: string = err instanceof Error ? err.message : String(err);
process.stderr.write(`move: ${msg}\nTry 'move --help' for more information.\n`);
process.exit(2);
}
/**
* Print a runtime-failure message and exit 1. Used for anything the user
* couldn't have prevented from the command line: nut.js errors, missing
* Accessibility permission on macOS, unexpected exceptions from the
* keeper loop. Prefers the stack trace when available since these failures
* usually need a developer to interpret.
*/
function failRuntime(err: unknown): never {
const detail: string = err instanceof Error ? (err.stack ?? err.message) : String(err);
process.stderr.write(`move: runtime error: ${detail}\n`);
process.exit(1);
}
let cliArgs: ParsedCliArgs;
try {
cliArgs = parseCliArgs();
} catch (err: unknown) {
failUser(err);
}
if (cliArgs.help) {
// printHelp() resolves defaultConfigPath(), which can throw CliError
// when neither $XDG_CONFIG_HOME nor $HOME is set.
try {
printHelp();
} catch (err: unknown) {
failUser(err);
}
process.exit(0);
}
if (cliArgs.version) {
process.stdout.write(`move ${VERSION}\n`);
process.exit(0);
}
let fileOverrides: ConfigOverrides | null;
try {
fileOverrides = loadConfigFile(cliArgs.config);
} catch (err: unknown) {
failUser(err);
}
const cliOverrides: ConfigOverrides = {
moveInterval: cliArgs.moveInterval,
checkInterval: cliArgs.checkInterval,
stepDelay: cliArgs.stepDelay,
stepCount: cliArgs.stepCount,
verbose: cliArgs.verbose,
};
const config = resolveConfig(fileOverrides, cliOverrides);
runKeeper(config).catch(failRuntime);