Initial commit: move CLI (milestone 2)
- src/move.ts entry point with CLI parsing, --help, --version - src/cli.ts: parseCliArgs, printHelp, ParsedCliArgs, CliError, VERSION - src/config.ts: Config type, DEFAULT_CONFIG, resolveConfig - src/keeper.ts: synthetic-activity sweep + idle-watch loop - package.json bin entry + shebang for 'bun link' global install - install.sh: contributor bootstrap (will be repurposed; see DISTRIBUTION-PLAN.md for the end-user installer design) - DISTRIBUTION-PLAN.md captures the tabled end-user distribution work
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# Teams Status Keeper
|
||||
|
||||
Keeps Microsoft Teams (or any presence-tracking app) from marking you as "Away"
|
||||
by nudging the mouse cursor when the machine has been idle long enough to
|
||||
trigger an idle timeout.
|
||||
|
||||
Real user movement always wins: the script never fires while the user is
|
||||
actively using the mouse, and any synthetic sweep aborts the moment the
|
||||
cursor leaves the position the script just commanded.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Bun](https://bun.sh) >= 1.0.0
|
||||
- macOS, Linux, or Windows (relies on
|
||||
[`@nut-tree-fork/nut-js`](https://github.com/nut-tree-fork/nut.js) for
|
||||
cross-platform mouse + screen control)
|
||||
- On macOS: Accessibility permission for the terminal running Bun
|
||||
(System Settings > Privacy & Security > Accessibility)
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
The installer verifies `bun` is on `PATH` and runs `bun install`. It does
|
||||
not auto-install Bun; if Bun is missing it prints a hard error pointing at
|
||||
<https://bun.sh>.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
bun run start
|
||||
```
|
||||
|
||||
Or directly:
|
||||
|
||||
```sh
|
||||
bun run src/move.ts
|
||||
```
|
||||
|
||||
Or install it as a global `move` command via Bun's bin-linking:
|
||||
|
||||
```sh
|
||||
bun link
|
||||
move
|
||||
```
|
||||
|
||||
`bun link` registers the `bin.move` entry from `package.json` (`./src/move.ts`,
|
||||
which carries `#!/usr/bin/env bun`) as an executable on your `PATH`.
|
||||
|
||||
Stop with `Ctrl+C`. If `SIGINT` arrives mid-sweep, the cursor stays at
|
||||
whichever step was last commanded — by design.
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
Usage: move [options]
|
||||
|
||||
Options:
|
||||
-h, --help Show this help and exit.
|
||||
-v, --version Print version and exit.
|
||||
-m, --move-interval <seconds> Idle time before a sweep fires. Default: 240.
|
||||
-c, --check-interval <seconds> Cursor poll cadence. Default: 10.
|
||||
-d, --step-delay <ms> Pause between synthetic steps. Default: 50.
|
||||
-n, --step-count <pixels> Steps per sweep. Default: 250.
|
||||
-V, --verbose Log every sweep, interrupt, and bounds event
|
||||
(default prints only the startup banner).
|
||||
```
|
||||
|
||||
Run `bun run src/move.ts --help` for the full text.
|
||||
|
||||
All flags are wired. Numeric overrides are layered onto the defaults via
|
||||
`resolveConfig` in `src/config.ts`; time-valued inputs (`-m`, `-c`) are
|
||||
expressed in seconds at the CLI boundary and converted to milliseconds
|
||||
internally.
|
||||
|
||||
Logging is **quiet by default**: only the startup banner ("Teams Status
|
||||
Keeper started…") and any error from an unhandled rejection print on a
|
||||
default run. `-V` / `--verbose` opens up per-sweep, user-interrupt, and
|
||||
out-of-bounds events.
|
||||
|
||||
Invalid input (unknown flag, missing value, non-positive number) prints an
|
||||
error to `stderr` and exits with code `2`.
|
||||
|
||||
## How it works
|
||||
|
||||
The source lives under `src/`, split into an entry point plus three logic
|
||||
modules:
|
||||
|
||||
- `src/move.ts` is a thin entry point: parses args, dispatches `--help` /
|
||||
`--version`, resolves the runtime config, and calls
|
||||
`runKeeper(config, verbose)`.
|
||||
- `src/cli.ts` owns argument parsing, validation, and help/version output.
|
||||
- `src/config.ts` exports the `Config` type, `DEFAULT_CONFIG`, and the
|
||||
`resolveConfig` overlay function.
|
||||
- `src/keeper.ts` owns the synthetic-activity sweep and the idle-watch loop.
|
||||
|
||||
Defaults live in `src/config.ts` as `DEFAULT_CONFIG`:
|
||||
|
||||
| Field | Default | CLI flag | Purpose |
|
||||
| --------------- | ------------ | ------------------------- | ---------------------------------------------------------------- |
|
||||
| `moveInterval` | `4 * 60_000` | `-m`, `--move-interval` | Idle time (ms) required before a synthetic sweep fires. |
|
||||
| `checkInterval` | `10_000` | `-c`, `--check-interval` | How often (ms) the main loop polls the cursor for real activity. |
|
||||
| `stepDelay` | `50` | `-d`, `--step-delay` | Pause (ms) between individual synthetic steps in a sweep. |
|
||||
| `stepCount` | `250` | `-n`, `--step-count` | Pixel-steps per sweep. |
|
||||
|
||||
`-m` and `-c` are accepted in seconds at the CLI; `resolveConfig` converts
|
||||
to milliseconds before handing the resolved `Config` to `runKeeper`.
|
||||
|
||||
### Main loop (`runKeeper`)
|
||||
|
||||
1. Print the startup banner (unconditional).
|
||||
2. Snapshot `lastPos` and `lastActivity = now`.
|
||||
3. Every `config.checkInterval`:
|
||||
- If the cursor moved since the last check, the user is active — reset
|
||||
`lastActivity` and `lastPos`, continue.
|
||||
- Otherwise, if `now - lastActivity >= config.moveInterval`, call
|
||||
`simulateActivity` and reset the idleness clock.
|
||||
|
||||
### Synthetic sweep (`simulateActivity`)
|
||||
|
||||
1. Read the starting position and current screen dimensions.
|
||||
2. Pick a horizontal direction (`dx = +1` if there's room to the right,
|
||||
else `-1`) so the sweep stays on-screen. Vertical is `dy = 0` for now.
|
||||
3. For each of `config.stepCount` steps:
|
||||
- Compute and bounds-check the next target.
|
||||
- Move the cursor there, sleep `config.stepDelay`.
|
||||
- Re-read the cursor. If it isn't where we put it, the user moved it —
|
||||
log (when `--verbose`) and return early without snapping back.
|
||||
4. On a clean full sweep, restore the cursor to its starting position so
|
||||
the next idle-check sees "no movement" and doesn't misread the synthetic
|
||||
activity as real user input.
|
||||
|
||||
### Why `mouse.config.autoDelayMs = 0`
|
||||
|
||||
nut.js inserts a 100ms delay after every action by default. With two mouse
|
||||
calls per step that would silently more-than-double the duration of a
|
||||
sweep. The script controls cadence itself via `config.stepDelay`, so the
|
||||
implicit delay is disabled at module load (a side effect of importing
|
||||
`keeper.ts`).
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------- | ----------------------------------------------------------------------- |
|
||||
| `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/config.ts` | `Config` type, `DEFAULT_CONFIG`, and `resolveConfig` overlay. |
|
||||
| `src/keeper.ts` | Synthetic-activity sweep and idle-watch loop. |
|
||||
| `install.sh` | Bootstrap: verify Bun, `bun install`, print macOS permission hint. |
|
||||
| `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. |
|
||||
| `LICENSE` | GPLv3 license text. |
|
||||
| `CHANGES.md` | History of the post-port review fixes. |
|
||||
| `MouseMover.java` | Original Java source, kept for reference. |
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0-only. See `LICENSE`.
|
||||
Reference in New Issue
Block a user