win-nice 0.1.0
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-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +356 -0
- package/bin/abovenormal +11 -0
- package/bin/abovenormal.bat +13 -0
- package/bin/abovenormal.ps1 +193 -0
- package/bin/admin +11 -0
- package/bin/admin.bat +12 -0
- package/bin/admin.ps1 +259 -0
- package/bin/belownormal +11 -0
- package/bin/belownormal.bat +13 -0
- package/bin/belownormal.ps1 +192 -0
- package/bin/cap +11 -0
- package/bin/cap.bat +10 -0
- package/bin/cap.ps1 +269 -0
- package/bin/cx +11 -0
- package/bin/cx.bat +8 -0
- package/bin/cx.ps1 +185 -0
- package/bin/cy +11 -0
- package/bin/cy.bat +8 -0
- package/bin/cy.ps1 +185 -0
- package/bin/high +11 -0
- package/bin/high.bat +12 -0
- package/bin/high.ps1 +193 -0
- package/bin/idle +11 -0
- package/bin/idle.bat +12 -0
- package/bin/idle.ps1 +192 -0
- package/bin/pint +11 -0
- package/bin/pint.bat +8 -0
- package/bin/pint.ps1 +270 -0
- package/bin/realtime +11 -0
- package/bin/realtime.bat +13 -0
- package/bin/realtime.ps1 +197 -0
- package/bin/uiup +11 -0
- package/bin/uiup.bat +4 -0
- package/bin/uiup.ps1 +41 -0
- package/install/cli.js +62 -0
- package/install/install.js +79 -0
- package/install/manifest.js +29 -0
- package/install/paths.js +141 -0
- package/install/skill.js +68 -0
- package/install/uninstall.js +76 -0
- package/package.json +48 -0
- package/skills/win-nice/SKILL.md +132 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const paths = require('./paths');
|
|
5
|
+
const manifest = require('./manifest');
|
|
6
|
+
|
|
7
|
+
// Rejects any candidate that doesn't resolve inside expectedDir - guards against a
|
|
8
|
+
// corrupted/tampered manifest (absolute paths, "../.." traversal) pointing deletion
|
|
9
|
+
// outside the install directory.
|
|
10
|
+
function isInsideDir(candidate, expectedDir) {
|
|
11
|
+
const relative = path.relative(path.resolve(expectedDir), path.resolve(candidate));
|
|
12
|
+
return relative !== '' && relative !== '..' && !relative.startsWith('..' + path.sep) && !path.isAbsolute(relative);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function removeManagedFile(filePath, expectedDir, { requireMarker }) {
|
|
16
|
+
if (!isInsideDir(filePath, expectedDir)) {
|
|
17
|
+
return { file: filePath, removed: false, reason: 'rejected (escapes install directory)' };
|
|
18
|
+
}
|
|
19
|
+
if (!fs.existsSync(filePath)) return { file: filePath, removed: false, reason: 'missing' };
|
|
20
|
+
if (requireMarker && !manifest.hasMarker(filePath)) {
|
|
21
|
+
return { file: filePath, removed: false, reason: 'marker missing (modified by user?)' };
|
|
22
|
+
}
|
|
23
|
+
fs.unlinkSync(filePath);
|
|
24
|
+
return { file: filePath, removed: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function uninstall({ updatePath = true } = {}) {
|
|
28
|
+
const dir = paths.binDir();
|
|
29
|
+
const manifestFile = paths.manifestPath();
|
|
30
|
+
const data = manifest.read(manifestFile);
|
|
31
|
+
|
|
32
|
+
let candidates;
|
|
33
|
+
let requireMarker;
|
|
34
|
+
if (data && Array.isArray(data.files)) {
|
|
35
|
+
// Files tracked by a valid manifest are owned by the package - remove them
|
|
36
|
+
// regardless of local edits (reinstall is expected to replace them anyway).
|
|
37
|
+
candidates = data.files.map((name) => path.join(data.binDir || dir, name));
|
|
38
|
+
requireMarker = false;
|
|
39
|
+
} else if (fs.existsSync(dir)) {
|
|
40
|
+
// Manifest missing/corrupt - fall back to scanning the known install dir. This
|
|
41
|
+
// scan can hit files we didn't put there, so only remove ones that still carry
|
|
42
|
+
// the marker.
|
|
43
|
+
candidates = fs.readdirSync(dir).map((name) => path.join(dir, name));
|
|
44
|
+
requireMarker = true;
|
|
45
|
+
} else {
|
|
46
|
+
candidates = [];
|
|
47
|
+
requireMarker = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const results = candidates.map((candidate) => removeManagedFile(candidate, dir, { requireMarker }));
|
|
51
|
+
for (const r of results) {
|
|
52
|
+
console.log(r.removed ? `removed ${r.file}` : `skipped ${r.file} (${r.reason})`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
|
|
56
|
+
if (fs.existsSync(manifestFile)) fs.unlinkSync(manifestFile);
|
|
57
|
+
|
|
58
|
+
// Leaves %LOCALAPPDATA%\win-nice itself behind otherwise - only bin/ and the
|
|
59
|
+
// manifest were ever tracked. Only remove it if uninstall left it truly empty;
|
|
60
|
+
// a user-added file there must survive.
|
|
61
|
+
const root = paths.installRoot();
|
|
62
|
+
if (fs.existsSync(root) && fs.readdirSync(root).length === 0) fs.rmdirSync(root);
|
|
63
|
+
|
|
64
|
+
if (updatePath) {
|
|
65
|
+
const current = paths.readUserPath();
|
|
66
|
+
const next = paths.removeFromPathString(current, dir);
|
|
67
|
+
if (next !== current) {
|
|
68
|
+
paths.writeUserPath(next);
|
|
69
|
+
console.log(`Removed ${dir} from your PATH.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { uninstall, removeManagedFile };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "win-nice",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Windows nice/renice/cpulimit for parallel AI coding agents - process priority (idle/belownormal/abovenormal/high/realtime), hard CPU quotas and thread affinity via Job Objects, elevation (admin), a UI responsiveness boost, and an optional Claude Code/Codex reference skill, with no dependencies.",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Marat K",
|
|
8
|
+
"url": "https://github.com/PHPCraftdream"
|
|
9
|
+
},
|
|
10
|
+
"os": [
|
|
11
|
+
"win32"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/PHPCraftdream/win-nice.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/PHPCraftdream/win-nice#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/PHPCraftdream/win-nice/issues"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"win-nice": "install/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"install",
|
|
27
|
+
"skills",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE-MIT",
|
|
30
|
+
"LICENSE-APACHE"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"postinstall": "node install/cli.js install",
|
|
34
|
+
"test": "node --test test/cli.test.js test/docs-sync.test.js test/gitbash-shims.test.js test/install-uninstall.test.js test/manifest.test.js test/paths.test.js test/skill.test.js"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"windows",
|
|
38
|
+
"priority",
|
|
39
|
+
"cpu",
|
|
40
|
+
"job-object",
|
|
41
|
+
"nice",
|
|
42
|
+
"cpulimit",
|
|
43
|
+
"agents"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: win-nice
|
|
3
|
+
description: Reference for win-nice's Windows CLI tools for process priority, hard CPU quotas, and CPU affinity (idle, belownormal, abovenormal, high, realtime, cap, pint, uiup, admin). Use when the user asks how to limit CPU usage, priority, or thread/core affinity for a command on Windows, wants to avoid a build/test freezing the desktop, or mentions any of these tool names.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- win-nice: managed-skill -->
|
|
7
|
+
|
|
8
|
+
# win-nice
|
|
9
|
+
|
|
10
|
+
Windows equivalent of the unix `nice` / `renice` / `cpulimit` family. Built for
|
|
11
|
+
running several parallel builds/tests/AI coding agents on one Windows box without
|
|
12
|
+
pegging every core and freezing the desktop (mouse, keyboard, window dragging,
|
|
13
|
+
audio - all of it).
|
|
14
|
+
|
|
15
|
+
Every tool below runs `<command> [args...]`, blocks until it exits, and
|
|
16
|
+
propagates its exit code. None of them need Node.js at runtime.
|
|
17
|
+
|
|
18
|
+
## Priority tools
|
|
19
|
+
|
|
20
|
+
Windows priority classes, lowest to highest. `idle`/`belownormal` propagate to
|
|
21
|
+
the *whole* process tree (Windows inherits `IDLE`/`BELOW_NORMAL` into child
|
|
22
|
+
processes by default). `abovenormal`/`high`/`realtime` do **not** propagate -
|
|
23
|
+
only the directly wrapped process gets the boosted priority; anything it spawns
|
|
24
|
+
runs at ordinary `Normal` priority. Confirmed empirically, not just from docs.
|
|
25
|
+
|
|
26
|
+
- `idle <command> [args...]` — `IDLE_PRIORITY_CLASS`.
|
|
27
|
+
- `belownormal <command> [args...]` — `BELOW_NORMAL_PRIORITY_CLASS`, a lighter
|
|
28
|
+
touch than `idle`.
|
|
29
|
+
- `abovenormal <command> [args...]` — `ABOVE_NORMAL_PRIORITY_CLASS`. Single
|
|
30
|
+
process only (see above) — pick this over `high`/`realtime` when you just need
|
|
31
|
+
a mild boost for one foreground process.
|
|
32
|
+
- `high <command> [args...]` — `HIGH_PRIORITY_CLASS`. Single process only.
|
|
33
|
+
- `realtime <command> [args...]` — `REALTIME_PRIORITY_CLASS`. **Dangerous**:
|
|
34
|
+
outranks the OS's own input/audio/UI threads: a busy realtime process can
|
|
35
|
+
freeze the whole desktop, the exact failure mode win-nice otherwise exists to
|
|
36
|
+
prevent. Needs `SeIncreaseBasePriorityPrivilege` (elevated processes have it
|
|
37
|
+
by default) — without it, Windows silently downgrades the request to `HIGH`
|
|
38
|
+
instead of erroring. Single process only, same as `high`.
|
|
39
|
+
|
|
40
|
+
## Resource limits
|
|
41
|
+
|
|
42
|
+
Job-Object-based; cover the *whole* process tree from the wrapped command's
|
|
43
|
+
first instruction (created suspended, assigned to the job, only then resumed —
|
|
44
|
+
no race window), including anything it spawns, recursively.
|
|
45
|
+
|
|
46
|
+
- `cap <percent 1-100> <command> [args...]` — hard CPU quota
|
|
47
|
+
(`JOBOBJECT_CPU_RATE_CONTROL_INFORMATION`, hard cap). A real ceiling on total
|
|
48
|
+
CPU%, not just scheduling priority — holds even when nothing else is
|
|
49
|
+
contending for CPU. Example: `cap 50 npm run build`.
|
|
50
|
+
- `pint <thread-count> <command> [args...]` — short for "pin threads": restricts
|
|
51
|
+
the whole tree to the first N *logical processors* via process affinity
|
|
52
|
+
(`JOB_OBJECT_LIMIT_AFFINITY`). Threads, not physical cores — on
|
|
53
|
+
Hyper-Threading/SMT hardware, N logical processors can be fewer physical
|
|
54
|
+
cores. `<thread-count>` must be between 1 and
|
|
55
|
+
`min([Environment]::ProcessorCount, 63)`. Example: `pint 4 npm run build`.
|
|
56
|
+
|
|
57
|
+
**A limit sticks to any daemon the wrapped command leaves running**, for that
|
|
58
|
+
daemon's whole lifetime, not just the one `cap`/`pint` call — Job Object
|
|
59
|
+
membership is permanent once assigned. Build tools that reuse a background
|
|
60
|
+
process to skip cold-start cost (`dotnet build`'s VBCSCompiler/MSBuild node
|
|
61
|
+
reuse, a Gradle daemon, `npm run watch`-style file watchers) can leave a
|
|
62
|
+
*later, uncapped-looking* invocation actually running inside an earlier
|
|
63
|
+
`cap`/`pint` call's job. Escape hatches: `dotnet build
|
|
64
|
+
-p:UseSharedCompilation=false`, `gradle --no-daemon` — or accept the daemon
|
|
65
|
+
stays limited until it's killed.
|
|
66
|
+
|
|
67
|
+
## Elevation / desktop responsiveness
|
|
68
|
+
|
|
69
|
+
- `admin <command> [args...]` — runs elevated (as Administrator); triggers the
|
|
70
|
+
standard UAC consent prompt if the calling shell isn't already elevated, runs
|
|
71
|
+
inline with no extra prompt if it is. The elevated equivalent of `idle`.
|
|
72
|
+
- `uiup` (no arguments) — one-shot `HIGH` priority boost for the live
|
|
73
|
+
shell/UI/audio processes (`explorer`, `dwm`, `sihost`,
|
|
74
|
+
`ShellExperienceHost`, `StartMenuExperienceHost`, `StartMenu`, `SearchApp`,
|
|
75
|
+
`audiodg`) so the desktop stays responsive while heavy background work runs
|
|
76
|
+
underneath. Self-elevates via UAC. Does **not** affect apps launched from
|
|
77
|
+
Explorer afterward (`HIGH` isn't inherited by default).
|
|
78
|
+
|
|
79
|
+
## Argument safety
|
|
80
|
+
|
|
81
|
+
Every tool tries to launch the wrapped command directly first (no shell
|
|
82
|
+
involved) and only falls back to `cmd.exe /c` when the target is a `.bat`/
|
|
83
|
+
`.cmd` file or a cmd.exe builtin that genuinely needs one.
|
|
84
|
+
|
|
85
|
+
**Direct-launch path** (the common case: a real `.exe`): `&`, `|`, `<`, `>`,
|
|
86
|
+
`^`, `%`, quotes, spaces, and empty strings all pass through exactly as
|
|
87
|
+
given — cmd.exe is never invoked, so there's nothing to expand.
|
|
88
|
+
|
|
89
|
+
**`cmd.exe /c` fallback path** (`.bat`/`.cmd` targets or cmd.exe builtins
|
|
90
|
+
only): `&|<>^`/quotes/spaces/empty strings are still fully protected. A
|
|
91
|
+
literal `%` used to be able to trigger environment-variable expansion here;
|
|
92
|
+
there's no reliable per-character escape for that at the `cmd.exe /c` level.
|
|
93
|
+
This path now **fails closed** instead: if any argument contains `%`, the
|
|
94
|
+
tool refuses to run, prints an error to stderr, and exits with code `1` — the
|
|
95
|
+
command never reaches cmd.exe.
|
|
96
|
+
|
|
97
|
+
**Separately, and unaffected by the fail-closed fix above:** each tool ships
|
|
98
|
+
as up to three files, and which one a bare `name ...` invocation resolves to
|
|
99
|
+
depends on the calling shell:
|
|
100
|
+
|
|
101
|
+
| Calling shell | Resolves to | `%` handling |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| PowerShell | `name.ps1` | full argument safety (see above) |
|
|
104
|
+
| cmd.exe, or PATHEXT-based resolution (e.g. Node's `child_process` — `PATHEXT` doesn't include `.PS1` by default) | `name.bat` | corrupted before `.ps1` ever runs |
|
|
105
|
+
| POSIX shell (Git Bash only — ignores `PATHEXT`; WSL not supported) | `name` (extensionless shim) | full argument safety — `exec`s straight into `name.ps1` with MSYS argument conversion disabled, same as PowerShell |
|
|
106
|
+
|
|
107
|
+
The `.bat` file corrupts any literal `%` in its arguments before the command,
|
|
108
|
+
and before `.ps1` (and its fail-closed `%` check), ever runs at all
|
|
109
|
+
(cmd.exe's own batch-parameter substitution rescanning for `%...%` patterns
|
|
110
|
+
while parsing the `.bat` entry point itself; not fixable from inside a
|
|
111
|
+
`.bat`). Every other special character survives that hop untouched.
|
|
112
|
+
|
|
113
|
+
**PowerShell execution policy:** Windows client editions default to
|
|
114
|
+
`Restricted`, which blocks a bare `.ps1` invoked directly by PowerShell with
|
|
115
|
+
"running scripts is disabled on this system". One-time fix:
|
|
116
|
+
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. `.bat` files and the
|
|
117
|
+
Git Bash shims are unaffected — both pass `-ExecutionPolicy Bypass`
|
|
118
|
+
themselves (each shim runs powershell with that flag explicitly). Group
|
|
119
|
+
Policy can still override this in managed environments.
|
|
120
|
+
|
|
121
|
+
## Install / manage
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
npm install -g win-nice # puts the tools above on PATH
|
|
125
|
+
npx win-nice status # what's installed, where, which version
|
|
126
|
+
npx win-nice reinstall # re-copy from the current package version
|
|
127
|
+
npx win-nice uninstall # remove files + PATH entry
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Not covered here: `cy`/`cx`, unrelated one-line launchers for
|
|
131
|
+
`claude --dangerously-skip-permissions` / `codex --dangerously-bypass-approvals-and-sandbox`
|
|
132
|
+
that happen to ship alongside these tools.
|