Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68e64eeaef | ||
|
|
d0528b4a92 | ||
|
|
c002a6d902 | ||
|
|
41903ebaf1 |
@@ -5,6 +5,19 @@ All notable changes to `move` are documented here.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [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.sh` header curl URLs pointed at a nonexistent repo-root path
|
||||
(they live under `scripts/`), so the documented command 404'd; `move.ts`'s
|
||||
module list omitted `editor.ts` and the `--edit` step; and `config.ts` still
|
||||
described a removed pixel unit.
|
||||
|
||||
## [1.3.0] - 2026-08-14
|
||||
|
||||
### Added
|
||||
@@ -113,6 +126,7 @@ Initial release.
|
||||
- Source split into `src/{move,cli,config,keeper}.ts`.
|
||||
- `bin` entry + shebang so `bun link` registers `move` globally.
|
||||
|
||||
[1.3.1]: https://gitea.cahlen.com/nokeo08/Move/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://gitea.cahlen.com/nokeo08/Move/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://gitea.cahlen.com/nokeo08/Move/compare/v1.1.1...v1.2.0
|
||||
[1.1.1]: https://gitea.cahlen.com/nokeo08/Move/compare/v1.1.0...v1.1.1
|
||||
|
||||
@@ -206,6 +206,9 @@ file with `--config`.
|
||||
|
||||
## How it works
|
||||
|
||||
For a step-by-step trace of a clean sweep, see the
|
||||
[execution happy-path sequence diagram](docs/execution-happy-path.md).
|
||||
|
||||
The source lives under `src/`, split into an entry point plus logic
|
||||
modules:
|
||||
|
||||
@@ -358,6 +361,7 @@ move --help
|
||||
| `src/device.ts` | `Device` I/O seam over nut.js (`Point`, `createNutDevice`); the only nut.js importer. |
|
||||
| `src/strategies.ts` | Pure movement-pattern generators, the strategy registry, and name validation. |
|
||||
| `src/executor.ts` | `executePath` driver: bounds policy, pacing, interrupt detection, restore. |
|
||||
| `docs/execution-happy-path.md` | Sequence diagram + invariants for a clean sweep. |
|
||||
| `package.json` | Bun project manifest. Single runtime dep: `@nut-tree-fork/nut-js`. |
|
||||
| `tsconfig.json` | Strict TypeScript config tuned for Bun (ESNext, bundler resolution). |
|
||||
| `bun.lock` | Bun's lockfile. Commit this. |
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# Execution: the happy path
|
||||
|
||||
This traces one full idle-triggered sweep that completes cleanly — the
|
||||
"happy path" where the machine is idle long enough to fire, the configured
|
||||
pattern runs to exhaustion, and no real user activity interrupts it.
|
||||
|
||||
For the module breakdown and the three seams (`device` / `strategies` /
|
||||
`executor`), see the "How it works" section of the [README](../README.md).
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Entry as move.ts
|
||||
participant Keeper as runKeeper
|
||||
participant Sim as simulateActivity
|
||||
participant Strat as Strategy<br/>(e.g. line)
|
||||
participant Exec as executePath
|
||||
participant Dev as Device<br/>(nut.js)
|
||||
|
||||
Note over Entry: startup (args → config)
|
||||
Entry->>Entry: parseCliArgs()
|
||||
Entry->>Entry: loadConfigFile()
|
||||
Entry->>Entry: resolveConfig(file, cli)
|
||||
Entry->>Keeper: runKeeper(config)
|
||||
|
||||
Keeper->>Dev: createNutDevice()
|
||||
Note right of Dev: sets mouse.config.autoDelayMs = 0
|
||||
Keeper->>Keeper: log.info(banner)
|
||||
Keeper->>Dev: getPosition()
|
||||
Dev-->>Keeper: lastPos
|
||||
Note over Keeper: lastActivity = now
|
||||
|
||||
loop every checkInterval (until idle long enough)
|
||||
Keeper->>Dev: sleep(checkInterval)
|
||||
Keeper->>Dev: getPosition()
|
||||
Dev-->>Keeper: pos
|
||||
Note over Keeper: pos == lastPos (no user movement)<br/>now - lastActivity ≥ moveInterval → fire
|
||||
end
|
||||
|
||||
Keeper->>Sim: simulateActivity(config, log, dev)
|
||||
Sim->>Dev: getPosition()
|
||||
Dev-->>Sim: start
|
||||
Sim->>Dev: width()
|
||||
Dev-->>Sim: width
|
||||
Sim->>Dev: height()
|
||||
Dev-->>Sim: height
|
||||
Note over Sim: strategy = STRATEGIES[config.pattern]<br/>ctx = { start, width, height, rng }
|
||||
Sim->>Exec: executePath(strategy, ctx, dev, log, config)
|
||||
|
||||
Exec->>Strat: path(ctx)
|
||||
Strat-->>Exec: iterable of Points
|
||||
|
||||
loop for each target point (clean run)
|
||||
Exec->>Exec: resolveTarget(bounds, target) → point
|
||||
Exec->>Dev: setPosition(point)
|
||||
Exec->>Dev: sleep(stepDelay)
|
||||
Exec->>Dev: getPosition()
|
||||
Dev-->>Exec: current
|
||||
Note over Exec: |current - point| ≤ 2px → not the user, continue
|
||||
end
|
||||
|
||||
Note over Exec: path exhausted, no interruption
|
||||
Exec->>Dev: setPosition(round(start))
|
||||
Note right of Exec: restore cursor to origin
|
||||
Exec-->>Sim: "completed"
|
||||
Sim-->>Keeper: (done)
|
||||
|
||||
Keeper->>Dev: getPosition()
|
||||
Dev-->>Keeper: lastPos (equals start, re-synced)
|
||||
Note over Keeper: lastActivity = now<br/>loop continues
|
||||
```
|
||||
|
||||
## Invariants this path relies on
|
||||
|
||||
- **`createNutDevice()` is the only nut.js touchpoint.** It disables nut.js's
|
||||
100ms auto-delay so `executePath` owns cadence via `stepDelay`.
|
||||
- **The strategy is pure.** `path(ctx)` yields ideal points from geometry
|
||||
alone (`start` / `width` / `height` / `rng`); it never touches the device,
|
||||
which is what makes every pattern unit-testable without a screen.
|
||||
- **Every step re-reads the cursor** and compares it against the *commanded*
|
||||
point (not the strategy's ideal, possibly fractional target) within a 2px
|
||||
tolerance. On the happy path each check passes, so the loop runs to
|
||||
exhaustion. A mismatch beyond tolerance is real user activity and returns
|
||||
`"interrupted"` without restoring — the branch this diagram omits.
|
||||
- **Clean completion restores the cursor to `round(start)`.** That is why the
|
||||
follow-up `getPosition()` in `runKeeper` re-syncs `lastPos` to the origin as
|
||||
a no-op, and the next idle check sees no net movement (so the synthetic
|
||||
sweep is never mistaken for the user returning).
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "move",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"type": "module",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Curl-pipe ready:
|
||||
#
|
||||
# curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/install.sh | sh
|
||||
# curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/scripts/install.sh | sh
|
||||
#
|
||||
# What it does:
|
||||
# 1. Detect platform; bail on anything @nut-tree-fork/nut-js doesn't ship.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Curl-pipe ready:
|
||||
#
|
||||
# curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/uninstall.sh | sh
|
||||
# curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/scripts/uninstall.sh | sh
|
||||
#
|
||||
# Removes the `move` wrapper from $XDG_BIN_HOME and the install tree from
|
||||
# $XDG_DATA_HOME/move. Does NOT remove Bun — that's your runtime, not ours.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
* `resolveConfig` rather than mutating the defaults, so the defaults stay
|
||||
* genuinely constant and the resolved config stays structurally typed.
|
||||
*
|
||||
* All numeric `Config` fields are in their internal units (ms, pixels).
|
||||
* All numeric `Config` fields are in their internal units (milliseconds).
|
||||
* The CLI and config file expose the time-valued fields in seconds for
|
||||
* ergonomics; `resolveConfig` performs the seconds->ms conversion at the
|
||||
* boundary so downstream code never has to think about it.
|
||||
|
||||
+9
-5
@@ -4,23 +4,27 @@
|
||||
* -------
|
||||
* Entry point for the `move` CLI.
|
||||
*
|
||||
* Thin shim that ties the four logic modules together:
|
||||
* Thin shim that ties the 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.
|
||||
* - `editor.ts` backs `--edit` (open the config file in `$EDITOR`).
|
||||
* - `keeper.ts` owns the idle-watch loop and drives the movement
|
||||
* machinery (device / strategy / executor).
|
||||
*
|
||||
* Order of operations:
|
||||
* 1. Parse CLI args. Bad input -> stderr + usage hint, exit 2.
|
||||
* 2. `--help` / `--version` short-circuit before any I/O, config load, or
|
||||
* mouse work. `keeper.ts` is also lazy-imported (see below) so these
|
||||
* flags don't pay the cost of loading the nut.js native binary.
|
||||
* 3. Load + validate the config file (default XDG path, or `--config
|
||||
* 3. `--edit` opens the resolved config file in `$EDITOR` and is a
|
||||
* terminal action (propagates the editor's exit code).
|
||||
* 4. 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
|
||||
* 5. Resolve the full `Config` (CLI > file > DEFAULT_CONFIG) — verbose
|
||||
* lives inside `Config` and is layered with the same precedence as
|
||||
* the numeric fields.
|
||||
* 5. Lazy-import `keeper.ts` (dynamic import keeps nut.js out of the
|
||||
* 6. Lazy-import `keeper.ts` (dynamic import keeps nut.js out of the
|
||||
* `--help` / `--version` startup path) and run it. Any unhandled
|
||||
* rejection — from the import itself or from the loop — exits 1.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user