@aiyu-ayaan/nonap 0.0.0-stage → 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aiyu-ayaan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,61 @@
1
- # Temporary Holding Version
1
+ # NoNap
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Tiny, zero-dependency CLI mouse wiggler. Keeps your machine awake.
4
+
5
+ Each wiggle moves the cursor a pixel or so and straight back, so the final position never changes and the movement is hard to notice.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g @aiyu-ayaan/nonap
11
+ ```
12
+
13
+ Or from a clone: `npm link`.
14
+
15
+ ## Usage
16
+
17
+ ```sh
18
+ nonap --start # or: nonap -s
19
+ nonap -s -i 20 -t 2h # wiggle every ~20s, stop after 2h
20
+ nonap -s -p random -d 3 # random direction, up to 3px
21
+ nonap --help # or: nonap -h
22
+ ```
23
+
24
+ ## Options
25
+
26
+ | Flag | Long | Meaning | Default |
27
+ | --- | --- | --- | --- |
28
+ | `-s` | `--start` | Start wiggling until Ctrl+C | |
29
+ | `-i` | `--interval <time>` | Time between wiggles | `30s` |
30
+ | `-j` | `--jitter <time>` | Random ± added to the interval, so timing is not robotic | `5s` |
31
+ | `-d` | `--distance <px>` | Max pixels per wiggle, 1-50 | `1` |
32
+ | `-p` | `--pattern <name>` | `nudge` (fixed distance) or `random` (1 to distance) | `nudge` |
33
+ | `-t` | `--duration <time>` | Stop after this long, `0` = never | `0` |
34
+ | `-q` | `--quiet` | No output while running | |
35
+ | `-h` | `--help` | Show help | |
36
+ | `-v` | `--version` | Show version | |
37
+
38
+ Time values: `90`, `90s`, `30m`, `2h`, `1h30m`.
39
+
40
+ ## Config
41
+
42
+ Save defaults so you do not retype flags. Stored in `~/.nonap/config.json`. Flags override saved values.
43
+
44
+ ```sh
45
+ nonap config # show
46
+ nonap config set interval 45
47
+ nonap config set distance 2
48
+ nonap config reset
49
+ ```
50
+
51
+ Keys: `interval`, `jitter`, `distance`, `pattern`, `duration`.
52
+
53
+ ## Platform support
54
+
55
+ - **Linux:** needs `xdotool` (X11) or `ydotool` (Wayland).
56
+ - **Windows:** works out of the box (PowerShell).
57
+ - **macOS:** works out of the box; grant Accessibility permission to your terminal.
58
+
59
+ ## License
60
+
61
+ [MIT](LICENSE)
package/bin/nonap.js ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { parseArgs } = require('util');
5
+ const cfg = require('../src/config');
6
+ const { createMouse } = require('../src/mouse');
7
+ const { wiggle, nextDelay, sleep } = require('../src/wiggle');
8
+ const HELP = require('../src/help');
9
+ const pkg = require('../package.json');
10
+
11
+ const options = {
12
+ start: { type: 'boolean', short: 's' },
13
+ help: { type: 'boolean', short: 'h' },
14
+ version: { type: 'boolean', short: 'v' },
15
+ quiet: { type: 'boolean', short: 'q' },
16
+ interval: { type: 'string', short: 'i' },
17
+ jitter: { type: 'string', short: 'j' },
18
+ distance: { type: 'string', short: 'd' },
19
+ pattern: { type: 'string', short: 'p' },
20
+ duration: { type: 'string', short: 't' },
21
+ };
22
+
23
+ function fail(msg) {
24
+ console.error(`nonap: ${msg}\nTry "nonap --help".`);
25
+ process.exit(1);
26
+ }
27
+
28
+ function configCommand(args) {
29
+ const [sub, key, value] = args;
30
+ if (!sub || sub === 'show') {
31
+ console.log(`${cfg.CONFIG_PATH}\n${JSON.stringify(cfg.load(), null, 2)}`);
32
+ } else if (sub === 'set') {
33
+ if (!key || value === undefined) fail('usage: nonap config set <key> <value>');
34
+ const next = cfg.load();
35
+ next[key] = cfg.coerce(key, value);
36
+ cfg.save(next);
37
+ console.log(`saved ${key} = ${next[key]}`);
38
+ } else if (sub === 'reset') {
39
+ cfg.save({ ...cfg.DEFAULTS });
40
+ console.log('config reset to defaults');
41
+ } else {
42
+ fail(`unknown config command "${sub}"`);
43
+ }
44
+ }
45
+
46
+ async function run(settings, quiet) {
47
+ const mouse = createMouse();
48
+ const log = quiet ? () => {} : (m) => console.log(m);
49
+ const startedAt = Date.now();
50
+ const endAt = settings.duration ? startedAt + settings.duration * 1000 : Infinity;
51
+ let stopping = false;
52
+ let count = 0;
53
+
54
+ const stop = () => {
55
+ stopping = true;
56
+ };
57
+ process.on('SIGINT', stop);
58
+ process.on('SIGTERM', stop);
59
+
60
+ log(
61
+ `NoNap running: every ~${cfg.format(settings.interval)} (+/-${cfg.format(settings.jitter)}), ` +
62
+ `${settings.pattern} ${settings.distance}px` +
63
+ (settings.duration ? `, for ${cfg.format(settings.duration)}` : '') +
64
+ '. Ctrl+C to stop.',
65
+ );
66
+
67
+ while (!stopping && Date.now() < endAt) {
68
+ // Sleep in 250ms slices so Ctrl+C and the duration limit react quickly.
69
+ const wakeAt = Date.now() + nextDelay(settings) * 1000;
70
+ while (!stopping && Date.now() < Math.min(wakeAt, endAt)) await sleep(250);
71
+ if (stopping || Date.now() >= endAt) break;
72
+ await wiggle(mouse, settings);
73
+ count++;
74
+ log(`[${new Date().toLocaleTimeString()}] wiggle #${count}`);
75
+ }
76
+
77
+ log(`Stopped after ${cfg.format((Date.now() - startedAt) / 1000)}, ${count} wiggles.`);
78
+ }
79
+
80
+ async function main() {
81
+ let parsed;
82
+ try {
83
+ parsed = parseArgs({ options, allowPositionals: true });
84
+ } catch (e) {
85
+ fail(e.message);
86
+ }
87
+ const { values, positionals } = parsed;
88
+
89
+ if (values.help) return console.log(HELP);
90
+ if (values.version) return console.log(pkg.version);
91
+ if (positionals[0] === 'config') {
92
+ try {
93
+ return configCommand(positionals.slice(1));
94
+ } catch (e) {
95
+ fail(e.message);
96
+ }
97
+ }
98
+ if (positionals.length) fail(`unknown command "${positionals[0]}"`);
99
+ if (!values.start) return console.log(HELP);
100
+
101
+ const settings = cfg.load();
102
+ try {
103
+ for (const key of ['interval', 'jitter', 'distance', 'pattern', 'duration']) {
104
+ if (values[key] !== undefined) settings[key] = cfg.coerce(key, values[key]);
105
+ }
106
+ await run(settings, values.quiet);
107
+ } catch (e) {
108
+ fail(e.message);
109
+ }
110
+ }
111
+
112
+ main();
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "@aiyu-ayaan/nonap",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "Tiny, zero-dependency CLI mouse wiggler that keeps your machine awake",
5
+ "author": "aiyu-ayaan",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/aiyu-ayaan/Nonap.git"
9
+ },
10
+ "homepage": "https://github.com/aiyu-ayaan/Nonap#readme",
11
+ "bugs": "https://github.com/aiyu-ayaan/Nonap/issues",
12
+ "bin": {
13
+ "nonap": "bin/nonap.js"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "src"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "keywords": [
23
+ "mouse",
24
+ "jiggler",
25
+ "wiggler",
26
+ "keep-awake",
27
+ "cli"
28
+ ],
29
+ "license": "MIT"
30
+ }
package/src/config.js ADDED
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const CONFIG_PATH = path.join(os.homedir(), '.nonap', 'config.json');
8
+
9
+ const DEFAULTS = {
10
+ interval: 30, // seconds between wiggles
11
+ jitter: 5, // random +/- seconds added to interval
12
+ distance: 1, // max pixels moved per wiggle
13
+ pattern: 'nudge', // nudge | random
14
+ duration: 0, // seconds to run, 0 = until stopped
15
+ };
16
+
17
+ const PATTERNS = ['nudge', 'random'];
18
+
19
+ function load() {
20
+ try {
21
+ return { ...DEFAULTS, ...JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) };
22
+ } catch {
23
+ return { ...DEFAULTS };
24
+ }
25
+ }
26
+
27
+ function save(cfg) {
28
+ fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
29
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + '\n');
30
+ }
31
+
32
+ // "90", "90s", "30m", "2h", "1h30m" -> seconds
33
+ function parseDuration(value) {
34
+ if (typeof value === 'number') return value;
35
+ const str = String(value).trim().toLowerCase();
36
+ if (/^\d+(\.\d+)?$/.test(str)) return Number(str);
37
+ const re = /(\d+(?:\.\d+)?)([hms])/g;
38
+ const units = { h: 3600, m: 60, s: 1 };
39
+ let total = 0;
40
+ let matched = '';
41
+ let m;
42
+ while ((m = re.exec(str))) {
43
+ total += Number(m[1]) * units[m[2]];
44
+ matched += m[0];
45
+ }
46
+ if (!matched || matched !== str) throw new Error(`invalid duration: "${value}" (try 90, 30m, 2h, 1h30m)`);
47
+ return total;
48
+ }
49
+
50
+ function format(seconds) {
51
+ const h = Math.floor(seconds / 3600);
52
+ const m = Math.floor((seconds % 3600) / 60);
53
+ const s = Math.floor(seconds % 60);
54
+ return [h && `${h}h`, m && `${m}m`, (s || (!h && !m)) && `${s}s`].filter(Boolean).join('');
55
+ }
56
+
57
+ // Validate + normalise a single key/value pair.
58
+ function coerce(key, value) {
59
+ switch (key) {
60
+ case 'interval':
61
+ case 'jitter':
62
+ case 'duration': {
63
+ const n = parseDuration(value);
64
+ if (n < 0 || (key === 'interval' && n < 1)) throw new Error(`${key} out of range`);
65
+ return n;
66
+ }
67
+ case 'distance': {
68
+ const n = Number(value);
69
+ if (!Number.isInteger(n) || n < 1 || n > 50) throw new Error('distance must be an integer 1-50');
70
+ return n;
71
+ }
72
+ case 'pattern':
73
+ if (!PATTERNS.includes(value)) throw new Error(`pattern must be one of: ${PATTERNS.join(', ')}`);
74
+ return value;
75
+ default:
76
+ throw new Error(`unknown key "${key}" (keys: ${Object.keys(DEFAULTS).join(', ')})`);
77
+ }
78
+ }
79
+
80
+ module.exports = { CONFIG_PATH, DEFAULTS, PATTERNS, load, save, coerce, parseDuration, format };
package/src/help.js ADDED
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const { DEFAULTS } = require('./config');
4
+
5
+ module.exports = `
6
+ NoNap - tiny CLI mouse wiggler. Keeps your machine awake.
7
+
8
+ Usage
9
+ nonap --start [options] start wiggling (alias: -s)
10
+ nonap config show saved config
11
+ nonap config set <key> <val> save a default (interval, jitter, distance, pattern, duration)
12
+ nonap config reset restore defaults
13
+
14
+ Options
15
+ -s, --start start wiggling until Ctrl+C
16
+ -i, --interval <time> time between wiggles (default ${DEFAULTS.interval}s)
17
+ -j, --jitter <time> random +/- on the interval (default ${DEFAULTS.jitter}s)
18
+ -d, --distance <px> max pixels per wiggle, 1-50 (default ${DEFAULTS.distance})
19
+ -p, --pattern <name> nudge | random (default ${DEFAULTS.pattern})
20
+ -t, --duration <time> stop after this long, 0 = never (default 0)
21
+ -q, --quiet no output while running
22
+ -h, --help show this help
23
+ -v, --version show version
24
+
25
+ Time values: 90, 90s, 30m, 2h, 1h30m
26
+
27
+ Examples
28
+ nonap -s
29
+ nonap -s -i 20 -t 2h
30
+ nonap config set interval 45
31
+
32
+ Requires xdotool or ydotool on Linux. Works out of the box on Windows and macOS
33
+ (macOS: grant Accessibility permission to your terminal).
34
+ `.trimStart();
package/src/mouse.js ADDED
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync, spawnSync } = require('child_process');
4
+
5
+ function has(cmd) {
6
+ return spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], { stdio: 'ignore' }).status === 0;
7
+ }
8
+
9
+ // Each backend exposes move(dx, dy): relative cursor move in pixels.
10
+ function linux() {
11
+ if (has('xdotool') && process.env.DISPLAY) {
12
+ return (dx, dy) => execFileSync('xdotool', ['mousemove_relative', '--', String(dx), String(dy)], { stdio: 'ignore' });
13
+ }
14
+ if (has('ydotool')) {
15
+ return (dx, dy) => execFileSync('ydotool', ['mousemove', '-x', String(dx), '-y', String(dy)], { stdio: 'ignore' });
16
+ }
17
+ throw new Error('need `xdotool` (X11) or `ydotool` (Wayland): e.g. sudo apt install xdotool');
18
+ }
19
+
20
+ function windows() {
21
+ const ps = (dx, dy) =>
22
+ "Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern void mouse_event(int f,int x,int y,int d,int e);' " +
23
+ `-Name M -Namespace N; [N.M]::mouse_event(1,${dx},${dy},0,0)`;
24
+ return (dx, dy) => execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', ps(dx, dy)], { stdio: 'ignore' });
25
+ }
26
+
27
+ function mac() {
28
+ const js = (dx, dy) =>
29
+ "ObjC.import('CoreGraphics');" +
30
+ "var p=$.CGEventGetLocation($.CGEventCreate($()));" +
31
+ `var e=$.CGEventCreateMouseEvent($(),$.kCGEventMouseMoved,{x:p.x+(${dx}),y:p.y+(${dy})},0);` +
32
+ '$.CGEventPost($.kCGHIDEventTap,e);';
33
+ return (dx, dy) => execFileSync('osascript', ['-l', 'JavaScript', '-e', js(dx, dy)], { stdio: 'ignore' });
34
+ }
35
+
36
+ function createMouse() {
37
+ switch (process.platform) {
38
+ case 'linux':
39
+ return { move: linux() };
40
+ case 'win32':
41
+ return { move: windows() };
42
+ case 'darwin':
43
+ return { move: mac() };
44
+ default:
45
+ throw new Error(`unsupported platform: ${process.platform}`);
46
+ }
47
+ }
48
+
49
+ module.exports = { createMouse };
package/src/wiggle.js ADDED
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
4
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5
+
6
+ const DIRS = [
7
+ [1, 0],
8
+ [-1, 0],
9
+ [0, 1],
10
+ [0, -1],
11
+ ];
12
+
13
+ // One wiggle: step away, then straight back, so the cursor ends where it began.
14
+ async function wiggle(mouse, { pattern, distance }) {
15
+ const [ux, uy] = DIRS[rand(0, DIRS.length - 1)];
16
+ const step = pattern === 'random' ? rand(1, distance) : distance;
17
+ const dx = ux * step;
18
+ const dy = uy * step;
19
+ mouse.move(dx, dy);
20
+ await sleep(rand(30, 90));
21
+ mouse.move(-dx, -dy);
22
+ return { dx, dy };
23
+ }
24
+
25
+ // Seconds until the next wiggle, interval +/- jitter (never below 1s).
26
+ function nextDelay({ interval, jitter }) {
27
+ return Math.max(1, interval + rand(-jitter, jitter));
28
+ }
29
+
30
+ module.exports = { wiggle, nextDelay, sleep };