Move verbose into Config; drop resolveVerbose
Verbose was awkwardly a separate runKeeper argument with a separate resolveVerbose helper, even though it's just another tunable on the same precedence ladder as the numeric fields. This commit collapses it. Changes: - Config gains 'readonly verbose: boolean'. DEFAULT_CONFIG sets it to false. - resolveConfig now returns the full Config including verbose. Verbose layers with the same 'first defined value wins' precedence as the numeric fields. - resolveVerbose is gone. - ParsedCliArgs.verbose becomes boolean | undefined: undefined when -V was not passed, true when it was. parseCliArgs maps accordingly. This lets the layered resolver treat verbose uniformly. - runKeeper takes a single Config arg. The internal logger reads from config.verbose at the top of runKeeper. - move.ts no longer plumbs verbose separately; the single resolved Config drives everything. - README How-it-works and Files-table entries updated to match. Behavior verified for all five verbose-precedence cases (no file/no flag, no file/-V, file:true/no flag, file:false/-V, file:false/no flag) and config-error scenarios remain intact.
This commit is contained in:
@@ -166,13 +166,13 @@ modules:
|
|||||||
|
|
||||||
- `src/move.ts` is a thin entry point: parses args, dispatches `--help` /
|
- `src/move.ts` is a thin entry point: parses args, dispatches `--help` /
|
||||||
`--version`, loads the config file, resolves the layered runtime
|
`--version`, loads the config file, resolves the layered runtime
|
||||||
config, and calls `runKeeper(config, verbose)`.
|
config, and calls `runKeeper(config)`.
|
||||||
- `src/cli.ts` owns argument parsing, validation, and help/version output.
|
- `src/cli.ts` owns argument parsing, validation, and help/version output.
|
||||||
- `src/configFile.ts` owns optional JSON config-file loading + strict
|
- `src/configFile.ts` owns optional JSON config-file loading + strict
|
||||||
schema validation.
|
schema validation.
|
||||||
- `src/config.ts` exports the `Config` type, `DEFAULT_CONFIG`,
|
- `src/config.ts` exports the `Config` type (which carries every tunable
|
||||||
`defaultConfigPath`, and the layered `resolveConfig` / `resolveVerbose`
|
including `verbose`), `DEFAULT_CONFIG`, `defaultConfigPath`, and the
|
||||||
overlay functions.
|
layered `resolveConfig` overlay function.
|
||||||
- `src/keeper.ts` owns the synthetic-activity sweep and the idle-watch loop.
|
- `src/keeper.ts` owns the synthetic-activity sweep and the idle-watch loop.
|
||||||
|
|
||||||
Defaults live in `src/config.ts` as `DEFAULT_CONFIG`:
|
Defaults live in `src/config.ts` as `DEFAULT_CONFIG`:
|
||||||
@@ -257,7 +257,7 @@ move --help
|
|||||||
| `scripts/dev-setup.sh` | Contributor bootstrap (verify Bun + `bun install`). |
|
| `scripts/dev-setup.sh` | Contributor bootstrap (verify Bun + `bun install`). |
|
||||||
| `src/move.ts` | CLI entry point: parses args, dispatches help/version, starts the loop. |
|
| `src/move.ts` | CLI entry point: parses args, dispatches help/version, starts the loop. |
|
||||||
| `src/cli.ts` | Argument parsing, validation, and help/version output. |
|
| `src/cli.ts` | Argument parsing, validation, and help/version output. |
|
||||||
| `src/config.ts` | `Config` type, `DEFAULT_CONFIG`, `defaultConfigPath`, and the layered `resolveConfig` / `resolveVerbose` overlay. |
|
| `src/config.ts` | `Config` type (carries every tunable, including `verbose`), `DEFAULT_CONFIG`, `defaultConfigPath`, and the layered `resolveConfig` overlay. |
|
||||||
| `src/configFile.ts` | Optional JSON config-file loader with strict schema validation. |
|
| `src/configFile.ts` | Optional JSON config-file loader with strict schema validation. |
|
||||||
| `src/keeper.ts` | Synthetic-activity sweep and idle-watch loop. |
|
| `src/keeper.ts` | Synthetic-activity sweep and idle-watch loop. |
|
||||||
| `package.json` | Bun project manifest. Single runtime dep: `@nut-tree-fork/nut-js`. |
|
| `package.json` | Bun project manifest. Single runtime dep: `@nut-tree-fork/nut-js`. |
|
||||||
|
|||||||
+8
-2
@@ -49,7 +49,13 @@ export interface ParsedCliArgs {
|
|||||||
checkInterval: number | undefined; // seconds
|
checkInterval: number | undefined; // seconds
|
||||||
stepDelay: number | undefined; // milliseconds
|
stepDelay: number | undefined; // milliseconds
|
||||||
stepCount: number | undefined; // pixels
|
stepCount: number | undefined; // pixels
|
||||||
verbose: boolean;
|
/**
|
||||||
|
* `true` when `-V`/`--verbose` was passed; `undefined` when it was not.
|
||||||
|
* `undefined` (not `false`) lets the layered resolver distinguish "user
|
||||||
|
* did not specify" from a hypothetical "user explicitly turned off",
|
||||||
|
* even though the CLI has no off-switch today.
|
||||||
|
*/
|
||||||
|
verbose: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +111,7 @@ export function parseCliArgs(): ParsedCliArgs {
|
|||||||
checkInterval: parsePositiveNumber("check-interval", values["check-interval"] as string | undefined),
|
checkInterval: parsePositiveNumber("check-interval", values["check-interval"] as string | undefined),
|
||||||
stepDelay: parsePositiveNumber("step-delay", values["step-delay"] as string | undefined),
|
stepDelay: parsePositiveNumber("step-delay", values["step-delay"] as string | undefined),
|
||||||
stepCount: parsePositiveNumber("step-count", values["step-count"] as string | undefined),
|
stepCount: parsePositiveNumber("step-count", values["step-count"] as string | undefined),
|
||||||
verbose: Boolean(values.verbose),
|
verbose: values.verbose === true ? true : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-36
@@ -4,16 +4,16 @@
|
|||||||
* Runtime configuration types, defaults, the layered resolver, and the
|
* Runtime configuration types, defaults, the layered resolver, and the
|
||||||
* default config-file path.
|
* default config-file path.
|
||||||
*
|
*
|
||||||
* The keeper is driven by a single `Config` object that carries the four
|
* The keeper is driven by a single `Config` object that carries every
|
||||||
* tunables it cares about. Defaults live in `DEFAULT_CONFIG`; CLI and
|
* tunable it cares about, including the `verbose` flag. Defaults live in
|
||||||
* config-file overrides are layered on top by `resolveConfig` rather than
|
* `DEFAULT_CONFIG`; CLI and config-file overrides are layered on top by
|
||||||
* mutating the defaults, so the defaults stay genuinely constant and the
|
* `resolveConfig` rather than mutating the defaults, so the defaults stay
|
||||||
* resolved config stays structurally typed.
|
* genuinely constant and the resolved config stays structurally typed.
|
||||||
*
|
*
|
||||||
* All `Config` fields are in their internal units (ms, pixels). The CLI and
|
* All numeric `Config` fields are in their internal units (ms, pixels).
|
||||||
* config file expose the time-valued fields in seconds for ergonomics;
|
* The CLI and config file expose the time-valued fields in seconds for
|
||||||
* `resolveConfig` performs the seconds->ms conversion at the boundary so
|
* ergonomics; `resolveConfig` performs the seconds->ms conversion at the
|
||||||
* downstream code never has to think about it.
|
* boundary so downstream code never has to think about it.
|
||||||
*
|
*
|
||||||
* Layering precedence (highest wins):
|
* Layering precedence (highest wins):
|
||||||
* CLI overrides > file overrides > DEFAULT_CONFIG
|
* CLI overrides > file overrides > DEFAULT_CONFIG
|
||||||
@@ -33,12 +33,15 @@ import { join } from "node:path";
|
|||||||
* a sweep. Also the window in which the user can
|
* a sweep. Also the window in which the user can
|
||||||
* "interrupt" by moving the cursor. Milliseconds.
|
* "interrupt" by moving the cursor. Milliseconds.
|
||||||
* - `stepCount` — number of pixel-steps in a single sweep. Pixels.
|
* - `stepCount` — number of pixel-steps in a single sweep. Pixels.
|
||||||
|
* - `verbose` — whether per-sweep / interrupt / bounds events are
|
||||||
|
* logged. The startup banner is always printed.
|
||||||
*/
|
*/
|
||||||
export interface Config {
|
export interface Config {
|
||||||
readonly moveInterval: number;
|
readonly moveInterval: number;
|
||||||
readonly checkInterval: number;
|
readonly checkInterval: number;
|
||||||
readonly stepDelay: number;
|
readonly stepDelay: number;
|
||||||
readonly stepCount: number;
|
readonly stepCount: number;
|
||||||
|
readonly verbose: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +53,7 @@ export const DEFAULT_CONFIG: Config = {
|
|||||||
checkInterval: 10 * 1000, // Check every 10 seconds
|
checkInterval: 10 * 1000, // Check every 10 seconds
|
||||||
stepDelay: 50, // ms between mouse steps
|
stepDelay: 50, // ms between mouse steps
|
||||||
stepCount: 250, // pixels to move per sweep
|
stepCount: 250, // pixels to move per sweep
|
||||||
|
verbose: false, // quiet by default; startup banner still prints
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,9 +67,14 @@ export const DEFAULT_CONFIG: Config = {
|
|||||||
* stepDelay — milliseconds
|
* stepDelay — milliseconds
|
||||||
* stepCount — pixels
|
* stepCount — pixels
|
||||||
*
|
*
|
||||||
* `verbose` is included so both layers can share a single shape, even
|
* `verbose` is `boolean | undefined` like the numeric fields, so all five
|
||||||
* though `verbose` isn't part of `Config` itself (it's plumbed through to
|
* fields share the same "first defined value wins" precedence logic.
|
||||||
* `runKeeper` as a separate parameter).
|
*
|
||||||
|
* For the CLI specifically, `verbose` is `undefined` when `-V/--verbose`
|
||||||
|
* was not passed and `true` when it was. There is no CLI off-switch
|
||||||
|
* today, so CLI `false` doesn't occur — a file-set `verbose: true` cannot
|
||||||
|
* be overridden back to false from the command line (see the Configuration
|
||||||
|
* section of the README).
|
||||||
*/
|
*/
|
||||||
export interface ConfigOverrides {
|
export interface ConfigOverrides {
|
||||||
readonly moveInterval: number | undefined;
|
readonly moveInterval: number | undefined;
|
||||||
@@ -96,8 +105,8 @@ export function defaultConfigPath(): string {
|
|||||||
/**
|
/**
|
||||||
* Overlay file overrides (lowest priority) and CLI overrides (highest)
|
* Overlay file overrides (lowest priority) and CLI overrides (highest)
|
||||||
* on top of `DEFAULT_CONFIG` and return a resolved `Config`. Time-valued
|
* on top of `DEFAULT_CONFIG` and return a resolved `Config`. Time-valued
|
||||||
* inputs are in seconds; this is where they're converted to milliseconds
|
* numeric inputs are in seconds; this is where they're converted to
|
||||||
* for internal use.
|
* milliseconds for internal use.
|
||||||
*
|
*
|
||||||
* For each field, the first layer that supplies a defined value wins:
|
* For each field, the first layer that supplies a defined value wins:
|
||||||
* CLI -> file -> DEFAULT_CONFIG
|
* CLI -> file -> DEFAULT_CONFIG
|
||||||
@@ -113,11 +122,11 @@ export function resolveConfig(file: ConfigOverrides | null, cli: ConfigOverrides
|
|||||||
return fallbackMs;
|
return fallbackMs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pickRaw = (
|
const pickRaw = <T>(
|
||||||
cliVal: number | undefined,
|
cliVal: T | undefined,
|
||||||
fileVal: number | undefined,
|
fileVal: T | undefined,
|
||||||
fallback: number,
|
fallback: T,
|
||||||
): number => {
|
): T => {
|
||||||
if (cliVal !== undefined) return cliVal;
|
if (cliVal !== undefined) return cliVal;
|
||||||
if (fileVal !== undefined) return fileVal;
|
if (fileVal !== undefined) return fileVal;
|
||||||
return fallback;
|
return fallback;
|
||||||
@@ -128,22 +137,6 @@ export function resolveConfig(file: ConfigOverrides | null, cli: ConfigOverrides
|
|||||||
checkInterval: pickSeconds(cli.checkInterval, file?.checkInterval, DEFAULT_CONFIG.checkInterval),
|
checkInterval: pickSeconds(cli.checkInterval, file?.checkInterval, DEFAULT_CONFIG.checkInterval),
|
||||||
stepDelay: pickRaw(cli.stepDelay, file?.stepDelay, DEFAULT_CONFIG.stepDelay),
|
stepDelay: pickRaw(cli.stepDelay, file?.stepDelay, DEFAULT_CONFIG.stepDelay),
|
||||||
stepCount: pickRaw(cli.stepCount, file?.stepCount, DEFAULT_CONFIG.stepCount),
|
stepCount: pickRaw(cli.stepCount, file?.stepCount, DEFAULT_CONFIG.stepCount),
|
||||||
|
verbose: pickRaw(cli.verbose, file?.verbose, DEFAULT_CONFIG.verbose),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the effective `verbose` flag from the same layered overrides.
|
|
||||||
*
|
|
||||||
* `--verbose` on the CLI is a presence-only switch (no `--no-verbose`),
|
|
||||||
* so this is strictly an "or-up" merge: CLI true wins outright, otherwise
|
|
||||||
* the file's value (or false if absent) is used.
|
|
||||||
*
|
|
||||||
* Known limitation: a user with `"verbose": true` in their config file
|
|
||||||
* cannot force quiet mode from the CLI. They must edit the file (or use
|
|
||||||
* `--config` to point at a different one).
|
|
||||||
*/
|
|
||||||
export function resolveVerbose(file: ConfigOverrides | null, cli: ConfigOverrides): boolean {
|
|
||||||
if (cli.verbose === true) return true;
|
|
||||||
if (file?.verbose === true) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|||||||
+5
-5
@@ -11,9 +11,9 @@
|
|||||||
* Logging policy:
|
* Logging policy:
|
||||||
* - The startup banner in `runKeeper` is unconditional so the user always
|
* - The startup banner in `runKeeper` is unconditional so the user always
|
||||||
* sees confirmation that the process is alive.
|
* sees confirmation that the process is alive.
|
||||||
* - Every per-sweep / interrupt / bounds log is gated by `verbose` so the
|
* - Every per-sweep / interrupt / bounds log is gated by `config.verbose`
|
||||||
* default is quiet. Errors stay on `console.error` (unconditional, raised
|
* so the default is quiet. Errors stay on `console.error` (unconditional,
|
||||||
* by the entry point on unhandled rejection).
|
* raised by the entry point on unhandled rejection).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mouse, Point, screen } from "@nut-tree-fork/nut-js";
|
import { mouse, Point, screen } from "@nut-tree-fork/nut-js";
|
||||||
@@ -144,8 +144,8 @@ async function simulateActivity(config: Config, log: ReturnType<typeof makeLogge
|
|||||||
* next position check matches), and on a user-interrupted sweep the next
|
* next position check matches), and on a user-interrupted sweep the next
|
||||||
* iteration sees the user's new position and correctly resets the clock.
|
* iteration sees the user's new position and correctly resets the clock.
|
||||||
*/
|
*/
|
||||||
export async function runKeeper(config: Config, verbose: boolean): Promise<void> {
|
export async function runKeeper(config: Config): Promise<void> {
|
||||||
const log = makeLogger(verbose);
|
const log = makeLogger(config.verbose);
|
||||||
log.info("Teams Status Keeper started. Press Ctrl+C to stop.");
|
log.info("Teams Status Keeper started. Press Ctrl+C to stop.");
|
||||||
|
|
||||||
let lastPos: Point = await mouse.getPosition();
|
let lastPos: Point = await mouse.getPosition();
|
||||||
|
|||||||
+5
-4
@@ -15,7 +15,9 @@
|
|||||||
* 2. `--help` / `--version` short-circuit before any I/O or mouse work.
|
* 2. `--help` / `--version` short-circuit before any I/O or mouse work.
|
||||||
* 3. Load + validate the config file (default XDG path, or `--config
|
* 3. Load + validate the config file (default XDG path, or `--config
|
||||||
* <path>` if supplied). Validation failures share the exit-2 path.
|
* <path>` if supplied). Validation failures share the exit-2 path.
|
||||||
* 4. Resolve config (CLI > file > DEFAULT_CONFIG) and the verbose flag.
|
* 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.
|
* 5. Run keeper. Any unhandled rejection exits 1.
|
||||||
*
|
*
|
||||||
* Runtime: Bun (uses `@nut-tree-fork/nut-js` via `keeper.ts`). The shebang
|
* Runtime: Bun (uses `@nut-tree-fork/nut-js` via `keeper.ts`). The shebang
|
||||||
@@ -24,7 +26,7 @@
|
|||||||
|
|
||||||
import { parseCliArgs, printHelp, VERSION } from "./cli.ts";
|
import { parseCliArgs, printHelp, VERSION } from "./cli.ts";
|
||||||
import type { ParsedCliArgs } from "./cli.ts";
|
import type { ParsedCliArgs } from "./cli.ts";
|
||||||
import { resolveConfig, resolveVerbose } from "./config.ts";
|
import { resolveConfig } from "./config.ts";
|
||||||
import type { ConfigOverrides } from "./config.ts";
|
import type { ConfigOverrides } from "./config.ts";
|
||||||
import { loadConfigFile } from "./configFile.ts";
|
import { loadConfigFile } from "./configFile.ts";
|
||||||
import { runKeeper } from "./keeper.ts";
|
import { runKeeper } from "./keeper.ts";
|
||||||
@@ -71,9 +73,8 @@ const cliOverrides: ConfigOverrides = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const config = resolveConfig(fileOverrides, cliOverrides);
|
const config = resolveConfig(fileOverrides, cliOverrides);
|
||||||
const verbose: boolean = resolveVerbose(fileOverrides, cliOverrides);
|
|
||||||
|
|
||||||
runKeeper(config, verbose).catch((err: unknown): void => {
|
runKeeper(config).catch((err: unknown): void => {
|
||||||
console.error("Error:", err);
|
console.error("Error:", err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user