moshcode 0.91.0 → 0.92.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/README.md +33 -0
- package/package.json +1 -1
- package/src/cli-schema.mjs +14 -0
- package/src/engines.mjs +16 -0
- package/src/nice.mjs +199 -0
- package/src/tui.mjs +59 -0
package/README.md
CHANGED
|
@@ -1010,6 +1010,39 @@ an equity's score, and each response ships the `caveats` that say so. Prices are
|
|
|
1010
1010
|
Alpaca's US venue alone and can differ materially from other exchanges. Research
|
|
1011
1011
|
aid, not advice — and like `stocks`, nothing under `crypto` can place an order.
|
|
1012
1012
|
|
|
1013
|
+
### Throttle (`/nice`)
|
|
1014
|
+
|
|
1015
|
+
The pit's job is starting other people's programs, and some of them are not shy.
|
|
1016
|
+
Run a few engines at once on a box you also want to type on and you get the
|
|
1017
|
+
failure everyone knows: nothing crashed, but the machine stops answering.
|
|
1018
|
+
|
|
1019
|
+
```text
|
|
1020
|
+
/nice on # nice -n10 + ionice -c2 -n7 for every engine started after
|
|
1021
|
+
/nice mem 2G # a ceiling, so a runaway dies alone
|
|
1022
|
+
/nice cpu 15 # yield more (nice takes -20..19)
|
|
1023
|
+
/nice # what it is set to
|
|
1024
|
+
/nice off # back to normal priority (the default)
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
It is off by default — a throttle nobody asked for is a slow engine nobody can
|
|
1028
|
+
explain — and it applies to engines started *after* you turn it on.
|
|
1029
|
+
|
|
1030
|
+
**`nice` alone is half a fix, and it is worth knowing which half.** It reorders
|
|
1031
|
+
CPU, so it buys back the part of a freeze you could have waited out. It does
|
|
1032
|
+
nothing about memory, and memory is the stall that actually costs you a session:
|
|
1033
|
+
once free RAM runs out the kernel reclaims, reclaim goes to disk, and no
|
|
1034
|
+
scheduling priority makes that faster. That is why `/nice mem` exists — it puts
|
|
1035
|
+
the engine in a systemd scope with a hard ceiling, so the one runaway process
|
|
1036
|
+
gets killed instead of the whole box going unresponsive.
|
|
1037
|
+
|
|
1038
|
+
The ceiling is the one setting that can silently not apply: `systemd-run --user`
|
|
1039
|
+
needs a systemd user session, and an ssh login without lingering has none.
|
|
1040
|
+
`/nice` says so in its status line rather than pretending, and the CPU and I/O
|
|
1041
|
+
halves still work. On a box with no `nice` or `ionice` at all, and on Windows,
|
|
1042
|
+
the whole thing is a no-op — engines spawn exactly as they did before.
|
|
1043
|
+
|
|
1044
|
+
Settings live in `~/.moshcode/nice.json`, owner-only like the history file.
|
|
1045
|
+
|
|
1013
1046
|
### Aliases (`/alias`)
|
|
1014
1047
|
|
|
1015
1048
|
The pit is a prompt you sit at all day, so it lets you name the lines you keep
|
package/package.json
CHANGED
package/src/cli-schema.mjs
CHANGED
|
@@ -1596,6 +1596,20 @@ export const PIT_COMMANDS = [
|
|
|
1596
1596
|
description: "show the current dir + git repo/branch/origin" },
|
|
1597
1597
|
{ name: "shell", aliases: ["sh"], args: "[cmd]", pitOnly: true,
|
|
1598
1598
|
description: "drop into $SHELL (exit → back to the pit); also !cmd" },
|
|
1599
|
+
{ name: "nice", aliases: ["throttle"], args: "on | off | cpu <n> | io <n> | mem <size>", pitOnly: true,
|
|
1600
|
+
description: "run engines at low priority so the box stays usable",
|
|
1601
|
+
synopsis: [
|
|
1602
|
+
["/nice [status]", "what the throttle is set to"],
|
|
1603
|
+
["/nice on | off", "toggle it (off by default)"],
|
|
1604
|
+
["/nice cpu <-20..19>", "nice level — higher yields more CPU"],
|
|
1605
|
+
["/nice io <0..7>", "ionice best-effort level"],
|
|
1606
|
+
["/nice mem <size> | off", "memory ceiling per engine (needs systemd)"],
|
|
1607
|
+
],
|
|
1608
|
+
examples: [
|
|
1609
|
+
["/nice on", "nice -n10 + ionice -c2 -n7 for every engine started after"],
|
|
1610
|
+
["/nice mem 2G", "the ceiling nice(1) can't give you — a runaway dies alone"],
|
|
1611
|
+
],
|
|
1612
|
+
seeAlso: ["agents", "start"] },
|
|
1599
1613
|
{ name: "alias", aliases: ["aliases"], args: 'set <name> "<cmd>" | list | get | rm | install <tool>', pitOnly: true,
|
|
1600
1614
|
description: "name a line you keep retyping; /<name> runs it",
|
|
1601
1615
|
synopsis: [
|
package/src/engines.mjs
CHANGED
|
@@ -37,6 +37,7 @@ import { homedir } from "node:os";
|
|
|
37
37
|
import path from "node:path";
|
|
38
38
|
|
|
39
39
|
import { setActiveChildInput } from "./mirror.mjs";
|
|
40
|
+
import { throttleSpec } from "./nice.mjs";
|
|
40
41
|
import { captureSpec } from "./pty.mjs";
|
|
41
42
|
|
|
42
43
|
export const ENGINES = {
|
|
@@ -338,7 +339,22 @@ function nodeShebang(file) {
|
|
|
338
339
|
}
|
|
339
340
|
}
|
|
340
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Where every CLI the pit starts is turned into a spawnable command.
|
|
344
|
+
*
|
|
345
|
+
* Both launch paths go through here — `runCmd` for installers and updaters,
|
|
346
|
+
* `openPassthrough` for the engines themselves — which makes it the one place
|
|
347
|
+
* the resource throttle has to be applied to cover all of them. It wraps
|
|
348
|
+
* *outside* resolution on purpose: `nice` needs a real executable to hand off
|
|
349
|
+
* to, and an unresolved binary must still produce its own ENOENT rather than
|
|
350
|
+
* one from a wrapper that obscures which program was actually missing.
|
|
351
|
+
* `/nice off` (the default) returns the spec untouched.
|
|
352
|
+
*/
|
|
341
353
|
function spawnSpec(bin, args = [], extraDirs = []) {
|
|
354
|
+
return throttleSpec(resolveSpec(bin, args, extraDirs));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function resolveSpec(bin, args = [], extraDirs = []) {
|
|
342
358
|
const resolved = resolveExecutable(bin, extraDirs);
|
|
343
359
|
// Unresolved, so hand the spawn the preferred name and let it produce the
|
|
344
360
|
// ENOENT — a list would be spawned as a single nonsense filename.
|
package/src/nice.mjs
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Run the CLIs the pit launches at a lower priority than everything else.
|
|
2
|
+
//
|
|
3
|
+
// The pit's whole job is starting other people's programs, and some of them are
|
|
4
|
+
// not shy: a coding engine holding a big context, a bundler, a browser under
|
|
5
|
+
// test. Start a few at once on a box you also want to type on and you get the
|
|
6
|
+
// failure everyone knows — the machine stops answering. Nothing crashed. Every
|
|
7
|
+
// core is busy, the last of the RAM went to swap, and the swap went to disk.
|
|
8
|
+
//
|
|
9
|
+
// `nice` is the classic answer to that and it is *half* of one. It reorders CPU
|
|
10
|
+
// and nothing else, so it fixes the part of the freeze you can wait out and not
|
|
11
|
+
// the part that kills a process. The stall that actually costs you an afternoon
|
|
12
|
+
// is memory: once free RAM runs out the kernel starts reclaiming, reclaim goes
|
|
13
|
+
// to disk, and no scheduling priority on earth makes that faster. So a throttle
|
|
14
|
+
// worth the name has to cover three resources, not one:
|
|
15
|
+
//
|
|
16
|
+
// CPU nice -n 10 the engine yields to whatever you are typing in
|
|
17
|
+
// I/O ionice -c 2 -n 7 its reads stop starving the rest of the box
|
|
18
|
+
// memory systemd-run scope a ceiling, so a runaway dies alone
|
|
19
|
+
//
|
|
20
|
+
// Only the first two are free. A memory ceiling needs a cgroup, which on a
|
|
21
|
+
// normal login means a systemd user session, which not every box has — an ssh
|
|
22
|
+
// login without lingering enabled is the common way to not have one. So the
|
|
23
|
+
// memory cap is opt-in (`/nice mem 2G`) rather than default: a throttle that
|
|
24
|
+
// refuses to launch anything on a box without systemd would be worse than no
|
|
25
|
+
// throttle at all. CPU and I/O work everywhere that has the binaries, and
|
|
26
|
+
// degrade to "no wrapper" where they don't.
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import os from "node:os";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { spawnSync } from "node:child_process";
|
|
31
|
+
|
|
32
|
+
/** Same 0600 as aliases and history: this file records how you run things. */
|
|
33
|
+
const FILE_MODE = 0o600;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `nice -n 10` and `ionice -c 2 -n 7` — deliberately not the extremes.
|
|
37
|
+
*
|
|
38
|
+
* nice 19 and ionice class 3 (idle) both mean "run only when nothing else
|
|
39
|
+
* wants the machine", which sounds right and is not: an engine that yields
|
|
40
|
+
* *completely* can take minutes to answer while a single background job holds
|
|
41
|
+
* the box, and a coding CLI that never finishes reads as broken rather than as
|
|
42
|
+
* polite. 10 and 7 are "last in line among normal work", which is the actual
|
|
43
|
+
* intent — you keep your terminal, the engine keeps making progress.
|
|
44
|
+
*/
|
|
45
|
+
export const DEFAULTS = Object.freeze({ cpu: 10, io: 7, memoryMax: "", memoryHigh: "" });
|
|
46
|
+
|
|
47
|
+
/** A memory size systemd would accept: 512M, 2G, 1500K, or a byte count. */
|
|
48
|
+
const MEM_RE = /^\d+(\.\d+)?[KMGT]?$/i;
|
|
49
|
+
|
|
50
|
+
/** Where the throttle setting lives. Derived per call so tests can move $HOME. */
|
|
51
|
+
export function niceFile() {
|
|
52
|
+
return path.join(os.homedir(), ".moshcode", "nice.json");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The current settings, always a complete object.
|
|
57
|
+
*
|
|
58
|
+
* Read on the spawn path for every CLI the pit starts, so a missing,
|
|
59
|
+
* unreadable, or hand-mangled file has to read as "throttle off" rather than
|
|
60
|
+
* throw. A file that says something we don't recognise loses only the field it
|
|
61
|
+
* got wrong: the point of this object is to decide how to launch a program, and
|
|
62
|
+
* one bad key must not stop the program launching.
|
|
63
|
+
*/
|
|
64
|
+
export function loadNice() {
|
|
65
|
+
let parsed;
|
|
66
|
+
try { parsed = JSON.parse(fs.readFileSync(niceFile(), "utf8")); }
|
|
67
|
+
catch { return { on: false, ...DEFAULTS }; }
|
|
68
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { on: false, ...DEFAULTS };
|
|
69
|
+
const num = (v, fallback) => (Number.isInteger(v) ? v : fallback);
|
|
70
|
+
const mem = (v) => (typeof v === "string" && MEM_RE.test(v.trim()) ? v.trim().toUpperCase() : "");
|
|
71
|
+
return {
|
|
72
|
+
on: parsed.on === true,
|
|
73
|
+
cpu: clampCpu(num(parsed.cpu, DEFAULTS.cpu)),
|
|
74
|
+
io: clampIo(num(parsed.io, DEFAULTS.io)),
|
|
75
|
+
memoryMax: mem(parsed.memoryMax),
|
|
76
|
+
memoryHigh: mem(parsed.memoryHigh),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** nice(1) accepts -20..19; anything else is a typo, not an intention. */
|
|
81
|
+
export function clampCpu(n) { return Math.min(19, Math.max(-20, n)); }
|
|
82
|
+
/** ionice best-effort levels are 0..7. */
|
|
83
|
+
export function clampIo(n) { return Math.min(7, Math.max(0, n)); }
|
|
84
|
+
|
|
85
|
+
/** Persist settings, creating ~/.moshcode on first use. */
|
|
86
|
+
export function saveNice(settings) {
|
|
87
|
+
const file = niceFile();
|
|
88
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
89
|
+
const body = {
|
|
90
|
+
on: settings.on === true,
|
|
91
|
+
cpu: clampCpu(Number.isInteger(settings.cpu) ? settings.cpu : DEFAULTS.cpu),
|
|
92
|
+
io: clampIo(Number.isInteger(settings.io) ? settings.io : DEFAULTS.io),
|
|
93
|
+
memoryMax: settings.memoryMax || "",
|
|
94
|
+
memoryHigh: settings.memoryHigh || "",
|
|
95
|
+
};
|
|
96
|
+
fs.writeFileSync(file, `${JSON.stringify(body, null, 2)}\n`, { mode: FILE_MODE });
|
|
97
|
+
// `mode` only applies at creation; tighten every write the way aliases does.
|
|
98
|
+
try { fs.chmodSync(file, FILE_MODE); } catch { /* best effort */ }
|
|
99
|
+
return body;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Is `bin` runnable here? Cached, because this is asked on every spawn. */
|
|
103
|
+
const lookupCache = new Map();
|
|
104
|
+
export function haveBin(bin, { spawn = spawnSync } = {}) {
|
|
105
|
+
if (lookupCache.has(bin)) return lookupCache.get(bin);
|
|
106
|
+
let found = false;
|
|
107
|
+
try { found = spawn("sh", ["-c", `command -v ${bin}`], { stdio: "ignore" }).status === 0; }
|
|
108
|
+
catch { found = false; }
|
|
109
|
+
lookupCache.set(bin, found);
|
|
110
|
+
return found;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Only used by tests, which need to ask about a box they are pretending about. */
|
|
114
|
+
export function resetBinCache() { lookupCache.clear(); }
|
|
115
|
+
|
|
116
|
+
/** Is a memory ceiling actually enforceable here? Needs a systemd user cgroup. */
|
|
117
|
+
export function canCapMemory({ has = haveBin, env = process.env } = {}) {
|
|
118
|
+
if (!has("systemd-run")) return false;
|
|
119
|
+
// --user needs a session bus to place the scope in. Over ssh without
|
|
120
|
+
// lingering there is none, and systemd-run fails rather than degrading —
|
|
121
|
+
// which would take the engine down with it. Check before, not after.
|
|
122
|
+
return Boolean(env.XDG_RUNTIME_DIR || env.DBUS_SESSION_BUS_ADDRESS);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Wrap a spawn spec so the child runs throttled. Returns a new spec plus a
|
|
127
|
+
* short `how` describing what was actually applied.
|
|
128
|
+
*
|
|
129
|
+
* Everything here is best-effort by design: each wrapper is added only if its
|
|
130
|
+
* binary exists, and a box with none of them gets the original spec back. The
|
|
131
|
+
* alternative — refusing to launch, or launching a command line referencing a
|
|
132
|
+
* binary that isn't there — turns a comfort feature into an outage.
|
|
133
|
+
*
|
|
134
|
+
* Windows has no nice/ionice/cgroups in this form, so it is a no-op there
|
|
135
|
+
* rather than a wrong guess.
|
|
136
|
+
*/
|
|
137
|
+
export function throttleSpec(spec, {
|
|
138
|
+
settings = loadNice(),
|
|
139
|
+
has = haveBin,
|
|
140
|
+
env = process.env,
|
|
141
|
+
platform = process.platform,
|
|
142
|
+
} = {}) {
|
|
143
|
+
const plain = { cmd: spec.cmd, args: spec.args ?? [], throttled: false, how: "" };
|
|
144
|
+
if (!settings.on || platform === "win32") return plain;
|
|
145
|
+
|
|
146
|
+
let cmd = spec.cmd;
|
|
147
|
+
let args = [...(spec.args ?? [])];
|
|
148
|
+
const how = [];
|
|
149
|
+
|
|
150
|
+
// Innermost first: each wrapper below prepends, so build outward.
|
|
151
|
+
if (has("ionice")) {
|
|
152
|
+
args = ["-c", "2", "-n", String(settings.io), cmd, ...args];
|
|
153
|
+
cmd = "ionice";
|
|
154
|
+
how.push(`ionice -c2 -n${settings.io}`);
|
|
155
|
+
}
|
|
156
|
+
if (has("nice")) {
|
|
157
|
+
args = ["-n", String(settings.cpu), cmd, ...args];
|
|
158
|
+
cmd = "nice";
|
|
159
|
+
how.push(`nice -n${settings.cpu}`);
|
|
160
|
+
}
|
|
161
|
+
// Outermost, so the scope contains the whole niced pipeline rather than
|
|
162
|
+
// sitting inside it — a cgroup only accounts for what it encloses.
|
|
163
|
+
const caps = [];
|
|
164
|
+
if (settings.memoryHigh) caps.push(`MemoryHigh=${settings.memoryHigh}`);
|
|
165
|
+
if (settings.memoryMax) caps.push(`MemoryMax=${settings.memoryMax}`);
|
|
166
|
+
if (caps.length && canCapMemory({ has, env })) {
|
|
167
|
+
const props = caps.flatMap((p) => ["-p", p]);
|
|
168
|
+
args = ["--user", "--scope", "--quiet", ...props, "--", cmd, ...args];
|
|
169
|
+
cmd = "systemd-run";
|
|
170
|
+
how.push(caps.join(" "));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (cmd === spec.cmd) return plain;
|
|
174
|
+
return { cmd, args, throttled: true, how: how.join(" ") };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** One line for `/nice` with no arguments. */
|
|
178
|
+
export function describeNice(settings = loadNice(), opts = {}) {
|
|
179
|
+
if (!settings.on) return "throttle is off — CLIs run at normal priority";
|
|
180
|
+
const parts = [`nice -n${settings.cpu}`, `ionice -c2 -n${settings.io}`];
|
|
181
|
+
if (settings.memoryMax || settings.memoryHigh) {
|
|
182
|
+
const caps = [
|
|
183
|
+
settings.memoryHigh ? `MemoryHigh=${settings.memoryHigh}` : "",
|
|
184
|
+
settings.memoryMax ? `MemoryMax=${settings.memoryMax}` : "",
|
|
185
|
+
].filter(Boolean).join(" ");
|
|
186
|
+
parts.push(canCapMemory(opts) ? caps : `${caps} (no systemd user session here — not applied)`);
|
|
187
|
+
}
|
|
188
|
+
return `throttle is on — ${parts.join(", ")}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Validate a memory size the way the command wants to report it. */
|
|
192
|
+
export function parseMemory(value) {
|
|
193
|
+
const clean = String(value ?? "").trim().toUpperCase();
|
|
194
|
+
if (!clean || clean === "OFF" || clean === "NONE") return { ok: true, value: "" };
|
|
195
|
+
if (!MEM_RE.test(clean)) {
|
|
196
|
+
return { ok: false, error: `"${value}" isn't a memory size — try 2G, 1500M, or off` };
|
|
197
|
+
}
|
|
198
|
+
return { ok: true, value: clean };
|
|
199
|
+
}
|
package/src/tui.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { activeChildInput, createMirror, pressKey, setActiveSink, teeOutput } fr
|
|
|
22
22
|
import { fetchMotdAd } from "./ads.mjs";
|
|
23
23
|
import { runScript } from "./runtime.mjs";
|
|
24
24
|
import { moshVocabulary } from "./commands.mjs";
|
|
25
|
+
import { loadNice, saveNice, describeNice, canCapMemory, parseMemory } from "./nice.mjs";
|
|
25
26
|
import { mcpCommand, pluginCommand, skillCommand } from "./integrations.mjs";
|
|
26
27
|
import { stocksCommand } from "./advisor.mjs";
|
|
27
28
|
import { cryptoCommand } from "./crypto.mjs";
|
|
@@ -388,6 +389,63 @@ function isInstalledTool(key) {
|
|
|
388
389
|
* line, not an argument list: re-joining tokens would drop the quoting that the
|
|
389
390
|
* shell still has to read.
|
|
390
391
|
*/
|
|
392
|
+
/**
|
|
393
|
+
* `/nice` — run the CLIs the pit starts at a lower priority than your terminal.
|
|
394
|
+
*
|
|
395
|
+
* A toggle rather than a per-launch flag because the thing being tuned is the
|
|
396
|
+
* box, not the command: you decide once that this machine is shared, and every
|
|
397
|
+
* engine started afterwards honours it. Off by default — a throttle nobody
|
|
398
|
+
* asked for is a slow engine nobody can explain.
|
|
399
|
+
*/
|
|
400
|
+
function niceCommand(rest) {
|
|
401
|
+
const [verb, ...args] = rest.filter((a) => a !== "--json");
|
|
402
|
+
const sub = String(verb ?? "").toLowerCase();
|
|
403
|
+
const json = rest.includes("--json");
|
|
404
|
+
const settings = loadNice();
|
|
405
|
+
|
|
406
|
+
if (json) { console.log(JSON.stringify(settings, null, 2)); return; }
|
|
407
|
+
|
|
408
|
+
if (!verb || sub === "status") {
|
|
409
|
+
console.log(info(describeNice(settings)));
|
|
410
|
+
if (!settings.on) console.log(ash(` ${acid("/nice on")} throttles CPU and I/O for every engine the pit starts`));
|
|
411
|
+
else if (!settings.memoryMax) console.log(ash(` ${acid("/nice mem 2G")} adds a memory ceiling — the part nice(1) can't do`));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (sub === "on" || sub === "off") {
|
|
416
|
+
const saved = saveNice({ ...settings, on: sub === "on" });
|
|
417
|
+
console.log(ok(describeNice(saved)));
|
|
418
|
+
if (saved.on) console.log(ash(" applies to engines started from here on; anything already running keeps its priority"));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (sub === "cpu" || sub === "io") {
|
|
423
|
+
const n = Number(args[0]);
|
|
424
|
+
if (!Number.isInteger(n)) {
|
|
425
|
+
console.log(err(`usage: /nice ${sub} <number>${sub === "cpu" ? " (-20..19, higher = yields more)" : " (0..7, higher = yields more)"}`));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const saved = saveNice({ ...settings, [sub]: n });
|
|
429
|
+
console.log(ok(describeNice(saved)));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (sub === "mem" || sub === "memory") {
|
|
434
|
+
const parsed = parseMemory(args[0]);
|
|
435
|
+
if (!parsed.ok) { console.log(err(parsed.error)); return; }
|
|
436
|
+
const saved = saveNice({ ...settings, memoryMax: parsed.value });
|
|
437
|
+
console.log(ok(describeNice(saved)));
|
|
438
|
+
// Worth saying plainly: this is the one setting that can silently not apply.
|
|
439
|
+
if (parsed.value && !canCapMemory()) {
|
|
440
|
+
console.log(ash(" no systemd user session on this box, so the ceiling is recorded but not enforced"));
|
|
441
|
+
console.log(ash(` ${acid("loginctl enable-linger $USER")} gives this login one`));
|
|
442
|
+
}
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
console.log(err(`/nice ${sub} isn't a thing — try on, off, cpu <n>, io <n>, or mem <size>`));
|
|
447
|
+
}
|
|
448
|
+
|
|
391
449
|
function aliasCommand(rest, line) {
|
|
392
450
|
const json = rest.includes("--json");
|
|
393
451
|
// `--json` is the listing's flag wherever it appears, so `/alias --json` is a
|
|
@@ -936,6 +994,7 @@ export async function tui() {
|
|
|
936
994
|
continue;
|
|
937
995
|
}
|
|
938
996
|
if (cmd === "alias" || cmd === "aliases") { aliasCommand(rest, line); continue; }
|
|
997
|
+
if (cmd === "nice" || cmd === "throttle") { niceCommand(rest); continue; }
|
|
939
998
|
if (cmd === "pwd" || cmd === "where") { printPwd(); continue; }
|
|
940
999
|
if (cmd === "login") {
|
|
941
1000
|
const device = rest.includes("--device") || rest.includes("device") || rest.includes("-d");
|