Pick a different movement pattern every time a sweep is triggered, so the
motion varies across the day instead of repeating one shape.
- strategies.ts: add the `random` sentinel, `SELECTABLE_PATTERN_NAMES`,
`isSelectablePattern`, and `createRandomPicker`. `random` is deliberately
NOT a registry entry: it has no path of its own, so `STRATEGIES` stays a
total lookup and `PATTERN_NAMES` keeps listing only real generators. The
picker is a closure over `last`, giving a uniform draw that never returns
the same pattern twice in a row. Building CANONICAL_PATTERNS from the
selectable list makes both validation boundaries accept `random` (and
loose spellings) for free, and extends the normalization-collision
assertion to cover the sentinel.
- cli.ts: add `-r`/`--random` plus an exported `selectPattern` holding the
conflict rule. `-r` is sugar for `--pattern random`, so the two agreeing
is a no-op while `-r -p arc` is rejected as contradictory. The flag folds
into `pattern`, so ConfigOverrides, resolveConfig, and move.ts are
untouched. `parseCliArgs` now takes its argv as an optional parameter so
the flag surface is testable without process.argv.
- keeper.ts: resolve `random` via the picker once per trigger, before the
loop-mode branch, so a pick holds for a whole loop run rather than
changing mid-run. runKeeper builds one picker for the process, so the
no-repeat memory spans sweeps minutes apart. Because the pick is a real
strategy, --verbose logs the concrete pattern name and a pick with an
infinite loopPath still bounces edge-to-edge under --loop.
- config.ts / configFile.ts: accept the sentinel where a pattern is valid,
and quote the selectable list in errors. No `random` boolean config key —
the file spells it "pattern": "random".
executor.ts and move.ts needed no changes.
Tests: new tests/cli.test.ts (the file had no coverage before) covering the
flag surface and the conflict rule; picker tests pinning the no-repeat and
full-registry-coverage properties; keeper tests pinning once-per-trigger and
once-per-loop-run.
Introduce a continuous "loop" setting so a triggered sweep keeps the
cursor moving until the user moves the mouse (or Ctrl+C), instead of
firing a single sweep.
- strategies.ts: add optional `loopPath` to MovementStrategy; give `line`
and `diagonal` infinite loop generators that pick a direction once and
ramp forever (4px/step). Their finite `path` and declared `bounds` are
unchanged, so single-sweep behavior is identical.
- executor.ts: add ExecuteOptions { restore?, bounds?, loop? }. Omitting
options reproduces the original single-sweep contract exactly.
- keeper.ts: in loop mode, run an infinite loopPath once (stopped only by
interruption) or chain a finite path cycle after cycle; force `reflect`
bounds for every pattern and suppress the between-cycle restore, so
line/diagonal bounce edge-to-edge instead of stopping at the first edge.
- config plumbing: new boolean `loop` through config.default.json,
config.ts, configFile.ts, cli.ts (-l/--loop), and move.ts, mirroring
the existing `verbose` precedence.
- docs: README loop-mode section + usage/validation updates; CHANGELOG
Unreleased entry.
- tests: loopPath generators, executor options (bounds override, loop
selection, restore suppression), config/configFile loop plumbing, and
keeper-level loop behavior (ramps far vs. bounded single-sweep, chained
cycles). 79 pass.
The stepCount and stepSize knobs were two controls for one quantity users
actually care about (reach), and the number of steps is an implementation
detail nobody meaningfully tunes. Each pattern has a natural size and
resolution — a jitter is inherently small, an arc a broad curve — so those
now live as constants in each strategy rather than as global config.
- strategies.ts: each pattern defines its own step count and size; MoveContext
drops `config` down to pure geometry (start/width/height/rng), and the
module no longer imports Config at all (dissolving the type-only-import
cycle workaround). line stays byte-for-byte: 250 one-pixel steps.
- executor.ts: executePath takes `config` for pacing (stepDelay); the path
itself needs nothing from it.
- config.ts / cli.ts / move.ts / config.default.json: drop stepCount and
stepSize from the type, seed, validation, resolver, CLI flags (-n, -s),
and help. stepDelay stays as the one pacing lever.
- configFile.ts: tolerate the removed keys instead of rejecting them — every
pre-1.3.0 install seeded stepCount, so a hard "unknown key" failure on
upgrade is avoided. They're ignored with a one-line stderr notice; genuine
unknown keys still error.
The -n/--step-count CLI flag (shipped since 1.0.0) is now an unknown option;
config files degrade gracefully, command lines don't. Stays in the unpushed
1.3.0 release. 64 tests pass.
Turn the hardcoded straight-line sweep into a strategy system behind three
seams so new patterns are easy to add and, for the first time, testable
without nut.js or a real screen:
- src/device.ts: injectable Device seam over nut.js (autoDelayMs lives
here now); the only module that touches the native lib.
- src/strategies.ts: pure per-pattern path generators + registry + lenient
name resolution. Ships line, diagonal, jitter, walk,
arc, figureEight.
- src/executor.ts: single executePath driver owning bounds policy
(abort/clamp/reflect), pacing, interrupt detection, and
restore-on-clean.
keeper.ts's simulateActivity now selects a strategy and delegates to the
executor; the default `line` pattern is byte-for-byte the previous behavior.
New config surface, layered CLI > file > default with strict validation:
- -p/--pattern <name> movement strategy (names matched case/-/_-insensitive)
- -s/--step-size <px> pixels per step; stepCount is now a step *count*
Robustness for the new edge-seeking patterns: interrupt detection compares
against the last commanded (rounded) point with a 2px tolerance, and
clamp/reflect stay a couple pixels off the screen edge, so sub-pixel cursor
placement on scaled/multi-monitor displays isn't misread as user activity.
jitter's radius scales with sweep length so it moves at the default stepSize.
Tests: new suites for strategies, the executor (all bounds policies,
rounding, interrupt, tolerance, pacing), and the keeper loop; config and
configFile suites extended for pattern/stepSize. editor.test.ts moved to
tests/ for consistency. 64 pass.
src/ now contains only source code; tests live alongside it under
tests/. Bun's test runner discovers '**/*.test.ts' so no test-runner
config change is needed.
Changes:
- tests/config.test.ts (was src/config.test.ts)
- tests/configFile.test.ts (was src/configFile.test.ts)
- Imports updated: ./config.ts -> ../src/config.ts (likewise for
configFile.ts and errors.ts).
- tsconfig.json include adds 'tests/**/*.ts' so tsc type-checks
the test files too.
All 25 tests still pass; tsc clean.