ccc136f727
The static usage example in README.md was added in milestone 1 and hand-maintained. When -C/--config landed in the JSON-config-file commit, printHelp() was updated but this block wasn't. This commit brings them back in sync and adds the precedence line.
271 lines
10 KiB
Markdown
271 lines
10 KiB
Markdown
# 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 or Linux (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
|
|
curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/scripts/install.sh | sh
|
|
```
|
|
|
|
This fetches the latest `master` from Gitea, runs `bun install --production`
|
|
under the install dir, and drops a `move` wrapper on your bin dir.
|
|
|
|
The installer respects the XDG Base Directory Specification:
|
|
|
|
- Source lives at `$XDG_DATA_HOME/move` (default `~/.local/share/move`).
|
|
- Wrapper goes to `$XDG_BIN_HOME/move` (default `~/.local/bin/move`).
|
|
`XDG_BIN_HOME` is the widely-recognized de facto convention; XDG itself
|
|
doesn't standardize a user bin dir.
|
|
|
|
Env vars (all optional):
|
|
|
|
| Var | Default | Purpose |
|
|
| --- | ------- | ------- |
|
|
| `MOVE_VERSION` | `master` | Branch or tag to install. Pin with e.g. `v1.0.0`. |
|
|
| `MOVE_FORCE` | unset | Set to `1` to reinstall when the same version is already present. |
|
|
| `XDG_DATA_HOME` | `$HOME/.local/share` | Where the source tree is installed (under `move/`). |
|
|
| `XDG_BIN_HOME` | `$HOME/.local/bin` | Where the `move` wrapper is placed. |
|
|
|
|
Bun must already be installed; the installer fails with a clear pointer
|
|
to <https://bun.sh> if it isn't.
|
|
|
|
If your bin dir isn't on `PATH`, the installer prints the line to add to
|
|
your shell rc. It will not modify rc files for you.
|
|
|
|
## Run
|
|
|
|
```sh
|
|
move
|
|
move --help
|
|
```
|
|
|
|
Stop with `Ctrl+C`. If `SIGINT` arrives mid-sweep, the cursor stays at
|
|
whichever step was last commanded — by design.
|
|
|
|
## Uninstall
|
|
|
|
```sh
|
|
curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/scripts/uninstall.sh | sh
|
|
```
|
|
|
|
Removes the wrapper at `$XDG_BIN_HOME/move` and the install tree at
|
|
`$XDG_DATA_HOME/move`. Bun stays — it's your runtime, not ours.
|
|
|
|
## Usage
|
|
|
|
```text
|
|
Usage: move [options]
|
|
|
|
Options:
|
|
-h, --help Show this help and exit.
|
|
-v, --version Print version and exit.
|
|
-C, --config <path> Load defaults from a JSON config file.
|
|
Default path: see the Configuration section.
|
|
-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).
|
|
|
|
Precedence (highest wins): CLI flags > config file > built-in defaults.
|
|
```
|
|
|
|
Run `move --help` for the resolved default config-file path on your
|
|
system. 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`.
|
|
|
|
## Configuration
|
|
|
|
`move` reads an optional JSON config file at:
|
|
|
|
```
|
|
${XDG_CONFIG_HOME:-$HOME/.config}/move/config.json
|
|
```
|
|
|
|
If the file is missing, defaults are used (no config file is required).
|
|
Pass `-C` / `--config <path>` to point at a different file; in that mode
|
|
the file must exist.
|
|
|
|
### Precedence
|
|
|
|
```
|
|
CLI flags > config file > built-in defaults
|
|
```
|
|
|
|
CLI flags always win. The config file fills in any flag the user didn't
|
|
pass on the command line. Built-in defaults fill in anything the file
|
|
doesn't set.
|
|
|
|
### Example
|
|
|
|
```jsonc
|
|
{
|
|
"moveInterval": 240,
|
|
"checkInterval": 10,
|
|
"stepDelay": 50,
|
|
"stepCount": 250,
|
|
"verbose": false
|
|
}
|
|
```
|
|
|
|
All keys are optional; supply only the ones you want to override. Keys
|
|
and units mirror the CLI flags exactly: `moveInterval` and
|
|
`checkInterval` are seconds, `stepDelay` is milliseconds, `stepCount` is
|
|
pixels, `verbose` is a boolean.
|
|
|
|
### Validation
|
|
|
|
The loader is strict:
|
|
|
|
- Root must be a JSON object.
|
|
- Unknown keys are rejected (catches typos like `"movInterval"`).
|
|
- Numeric values must be finite and strictly positive.
|
|
- `verbose` must be a boolean.
|
|
|
|
Any validation failure prints a message naming the file and the offending
|
|
key to `stderr` and exits `2`.
|
|
|
|
### Known limitation: `verbose` can be turned on but not off from the CLI
|
|
|
|
`--verbose` is a presence-only flag (there is no `--no-verbose`). If the
|
|
config file sets `"verbose": true`, the CLI cannot force quiet mode in
|
|
that invocation. Workarounds: edit the file, or point at a different
|
|
file with `--config`.
|
|
|
|
## How it works
|
|
|
|
The source lives under `src/`, split into an entry point plus four logic
|
|
modules:
|
|
|
|
- `src/move.ts` is a thin entry point: parses args, dispatches `--help` /
|
|
`--version`, loads the config file, resolves the layered runtime
|
|
config, and calls `runKeeper(config, verbose)`.
|
|
- `src/cli.ts` owns argument parsing, validation, and help/version output.
|
|
- `src/configFile.ts` owns optional JSON config-file loading + strict
|
|
schema validation.
|
|
- `src/config.ts` exports the `Config` type, `DEFAULT_CONFIG`,
|
|
`defaultConfigPath`, and the layered `resolveConfig` / `resolveVerbose`
|
|
overlay functions.
|
|
- `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`).
|
|
|
|
## For contributors
|
|
|
|
Clone the repo and bootstrap a dev environment:
|
|
|
|
```sh
|
|
git clone https://gitea.cahlen.com/nokeo08/Move.git
|
|
cd Move
|
|
./scripts/dev-setup.sh
|
|
```
|
|
|
|
`scripts/dev-setup.sh` verifies Bun is installed and runs `bun install`
|
|
(with devDependencies, unlike the end-user `scripts/install.sh`). It
|
|
operates at the repo root regardless of the CWD you invoke it from.
|
|
|
|
Run from the source tree:
|
|
|
|
```sh
|
|
bun run start # via the package.json script
|
|
bun run src/move.ts # direct
|
|
bun run src/move.ts --help
|
|
```
|
|
|
|
Or install a global `move` pointed at your checkout:
|
|
|
|
```sh
|
|
bun link
|
|
move --help
|
|
```
|
|
|
|
## Files
|
|
|
|
| File | Purpose |
|
|
| ------------------- | ----------------------------------------------------------------------------- |
|
|
| `scripts/install.sh` | End-user installer; curl-pipeable from Gitea. |
|
|
| `scripts/uninstall.sh` | End-user uninstaller; curl-pipeable from Gitea. |
|
|
| `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/cli.ts` | Argument parsing, validation, and help/version output. |
|
|
| `src/config.ts` | `Config` type, `DEFAULT_CONFIG`, `defaultConfigPath`, and the layered `resolveConfig` / `resolveVerbose` overlay. |
|
|
| `src/configFile.ts` | Optional JSON config-file loader with strict schema validation. |
|
|
| `src/keeper.ts` | Synthetic-activity sweep and idle-watch loop. |
|
|
| `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. |
|
|
|
|
## License
|
|
|
|
GPL-3.0-only. See `LICENSE`.
|