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.
11 KiB
Changelog
All notable changes to move are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
-
Random pattern selection:
-r/--random, andrandomas a value for--patternand thepatternconfig key. Every time a sweep is triggered, a different movement pattern is chosen, so the motion varies across the day instead of repeating one shape. Two rules keep it predictable: the same pattern is never chosen twice in a row, and the pick happens once per trigger — in loop mode it holds for the whole loop run rather than changing mid-run. The pick is a real strategy, so--verboselogs the concrete pattern name and a pick with an infinite loop path (line,diagonal) still bounces edge-to-edge under--loop.-ris defined as sugar for--pattern random, so passing both is rejected (exit2) unless they agree:move -r -p arcis an error, whilemove -r -p randomis a no-op. There is norandomboolean config key — the file spells it"pattern": "random".randomis deliberately not a registry entry: it has no path of its own, and the keeper resolves it to a real strategy per sweep.PATTERN_NAMEStherefore still lists only real generators, with the newSELECTABLE_PATTERN_NAMEScovering what a user may select.
Changed
parseCliArgsnow takes its argument list as an optional parameter (defaulting to the real command line), so the flag surface is unit-testable without touchingprocess.argv. Addstests/cli.test.ts, which previously had no coverage.
1.4.0 - 2026-08-17
Added
- Loop mode:
-l/--loop(and theloopconfig key) keep the mouse moving after a sweep is triggered until real user activity is detected, instead of firing a single sweep. In loop mode the cursor is never restored between iterations, solineanddiagonalbounce edge-to-edge across the screen (a roaming-DVD effect) rather than stopping at the first edge. Patterns with a finite path (jitter,walk,arc,figureEight) chain that path cycle after cycle. Interruption remains mouse-movement only.
Changed
- Simplified on-screen confinement to a single policy: the executor now
reflects every pattern's out-of-range coordinates back inside the screen.
The
abortandclampbounds policies (and the per-strategyboundsfield) were removed.aborttruncated a sweep at the first edge andclampcould park the cursor against an edge — both counter to keeping the cursor moving — whilereflectbounces and keeps going. Behavior is unchanged for every pattern at normal cursor positions; the only differences are at a screen edge, where motion now bounces instead of stopping. No config keys, flags, or pattern names changed.
1.3.3 - 2026-08-17
Changed
install.shnow installs the newest published tag by default instead of tracking themasterbranch, so the plaincurl ... | shone-liner installs a real release and reports its version (e.g.v1.3.2). The tag is resolved from the Gitea tags API; if that lookup fails (offline, API unreachable, or no tags yet) it falls back tomaster, preserving the old behavior. SetMOVE_VERSIONto pin an explicit branch or tag as before.
1.3.2 - 2026-08-17
Added
install.shis now interactive. When it finds an existing install and a controlling terminal is available, it reports what's there and asks before replacing it, instead of leavingMOVE_FORCEas the only control. If a config file already exists it asks separately whether to reseed it from the shipped defaults. Both questions are asked before anything is downloaded or deleted, so declining changes nothing.MOVE_RESEED_CONFIG=1overwrites the user config with the shipped defaults without prompting, for unattended use. The previous file is kept asconfig.json.bak; the same backup is written when reseeding is confirmed at the prompt.
Changed
MOVE_FORCE=1now means "skip every prompt and reinstall unconditionally". It deliberately does not touch the user config, so automation that reinstalls the CLI can't take customizations down with it.- Existing-install detection looks for the install tree and the wrapper, not
just the
.installed-versionmarker, so a half-finished or hand-moved install is caught rather than silently overwritten.
Fixed
- Installing a different version over an existing one used to wipe and replace it with no warning; only an exact version match was ever reported. That case now prompts. With no terminal (CI, cron, container builds) the previous non-interactive behavior is preserved exactly: an identical version is a no-op, a different version is replaced.
1.3.1 - 2026-08-14
Added
docs/execution-happy-path.md: a sequence diagram (plus invariants) tracing a clean idle-triggered sweep end to end, linked from the README.
Fixed
- Stale comments corrected to match the current code: the
install.sh/uninstall.shheader curl URLs pointed at a nonexistent repo-root path (they live underscripts/), so the documented command 404'd;move.ts's module list omittededitor.tsand the--editstep; andconfig.tsstill described a removed pixel unit.
1.3.0 - 2026-08-14
Added
- Pluggable movement strategies. New
-p, --pattern <name>flag andpatternconfig key select how the cursor moves:line(default, unchanged behavior),diagonal,jitter,walk,arc,figureEight. Each pattern owns its own size and step count as constants; there is no user knob for sweep magnitude. - Pattern names are matched leniently: case and separators are ignored, so
figureEight,figure-eight,figure_eight, andFIGUREEIGHTare all accepted (on the CLI and in the config file) and resolve to the canonical name. src/device.ts: injectableDeviceseam over nut.js, enabling unit tests for movement without the native binary or a real screen.src/strategies.ts: pure, per-pattern path generators plus the registry and name validation.src/executor.ts: singleexecutePathdriver owning bounds policy (abort/clamp/reflect), pacing, interrupt detection, and restore.- Test suites for strategies, the executor (all bounds policies, rounding, interrupt), and the keeper loop.
Changed
simulateActivityno longer hardcodes a straight-line sweep; it selects a strategy from the registry and delegates execution toexecutePath. The defaultlinepattern is byte-for-byte the previous behavior.- Interrupt detection now compares against the last commanded (rounded) point rather than an ideal target, so fractional/curved paths don't self-trip.
mouse.config.autoDelayMs = 0moved fromrunKeeperintocreateNutDevice— the single place nut.js is wired up.runKeeper(config, device?)accepts an injected device for testing.- Interrupt detection tolerates a small (2px) gap between the commanded and
read-back cursor position, and the
clamp/reflectpatterns stay a few pixels off the screen edge. Together these avoid false "user activity" aborts from sub-pixel cursor placement on scaled or multi-monitor setups, which the new edge-seeking patterns would otherwise hit.line(policyabort) is unaffected.
Removed
-n, --step-countflag and thestepCount/stepSizeconfig keys. Sweep size and step count are now intrinsic to each movement pattern, not user knobs. Config files that still contain these keys keep working: the loader ignores them with a one-line notice instead of rejecting them, so existing installs (all seeded withstepCount) don't break on upgrade. The removed CLI flag, however, is a hard error like any other unknown option.
1.2.0 - 2026-06-17
Added
-e, --editflag opens the resolved config file in$EDITOR.
Changed
keeper.ts(and@nut-tree-fork/nut-js) is lazy-imported, so--helpand--versionskip the nut.js load and start ~10x faster.
1.1.1 - 2026-06-17
Changed
- Tests moved from
src/to a top-leveltests/directory. tsconfig.jsonsets"types": ["bun"]so VS Code resolvesbun:test.
Fixed
package.jsonversion now matches the published tag.
1.1.0 - 2026-06-17
Added
- JSON config file support at
${XDG_CONFIG_HOME:-~/.config}/move/config.json. Precedence: CLI flags > config file > defaults. Strict validation. -C, --config <path>to override the default config path.- Installer seeds
config.jsonwith project defaults on fresh install only. - Bun test suite for
resolveConfigandloadConfigFile.
Changed
- Installer scripts now live in
scripts/. verboseis now a first-classConfigfield;resolveVerboseremoved.CliErrorextracted intosrc/errors.ts.defaultConfigPath()throws when both$XDG_CONFIG_HOMEand$HOMEare unset.mouse.config.autoDelayMs = 0moved intorunKeeper(no module-load side effect).- Runtime errors go through a
failRuntimehelper that mirrorsfailUser. keeper.tsdeclares a namedLoggerinterface.dev-setup.shis now POSIXsh.tsconfig.json: enablednoUncheckedIndexedAccessandresolveJsonModule.
1.0.1 - 2026-06-17
Added
- End-user
install.shrunnable viacurl ... | sh. XDG-respecting, idempotent. uninstall.shremoves the wrapper and install tree; leaves Bun and user config alone.dev-setup.shfor contributors.
Removed
DISTRIBUTION-PLAN.md(design notes, superseded by the implementation).
1.0.0 - 2026-06-15
Initial release.
Added
moveCLI for keeping presence-tracking apps marked Available by nudging the cursor after a configurable idle period.- Flags:
-h/--help,-v/--version,-m/--move-interval,-c/--check-interval,-d/--step-delay,-n/--step-count,-V/--verbose. - Quiet-by-default logging.
- Source split into
src/{move,cli,config,keeper}.ts. binentry + shebang sobun linkregistersmoveglobally.