moshcode 0.92.0 → 0.93.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 CHANGED
@@ -1017,15 +1017,34 @@ Run a few engines at once on a box you also want to type on and you get the
1017
1017
  failure everyone knows: nothing crashed, but the machine stops answering.
1018
1018
 
1019
1019
  ```text
1020
- /nice on # nice -n10 + ionice -c2 -n7 for every engine started after
1020
+ /nice agents claude # throttle this one engine
1021
+ /nice pnpm -r build # …or this one shell line
1022
+ /nice merge # …or any other pit command
1023
+
1024
+ /nice on # or throttle everything from here on
1021
1025
  /nice mem 2G # a ceiling, so a runaway dies alone
1022
1026
  /nice cpu 15 # yield more (nice takes -20..19)
1023
1027
  /nice # what it is set to
1024
1028
  /nice off # back to normal priority (the default)
1025
1029
  ```
1026
1030
 
1027
- It is off by default a throttle nobody asked for is a slow engine nobody can
1028
- explainand it applies to engines started *after* you turn it on.
1031
+ `/nice <line>` is the form to reach for. It runs **anything the pit can already
1032
+ run**a pit command, an engine, a tool, one of your aliases, or a bare shell
1033
+ line — at low priority, without changing any setting. It works on all of those
1034
+ because it hands the rest of the line back to the top of the dispatcher exactly
1035
+ as an alias expansion does, rather than keeping a list of its own that would
1036
+ drift. The leading slash is optional: `/nice agents claude` and
1037
+ `/nice /agents claude` are the same line.
1038
+
1039
+ The throttle lasts exactly as long as the line that asked for it, including
1040
+ through an alias that expands into something else. The next thing you type is
1041
+ back to normal.
1042
+
1043
+ `/nice on` is the other half, for when the box is shared all day and you would
1044
+ rather decide once. It is off by default — a throttle nobody asked for is a slow
1045
+ engine nobody can explain — and it applies to engines started *after* you turn
1046
+ it on. The settings words (`on`, `off`, `status`, `cpu`, `io`, `mem`) win the
1047
+ first position, so a program named `on` is not reachable through `/nice`.
1029
1048
 
1030
1049
  **`nice` alone is half a fix, and it is worth knowing which half.** It reorders
1031
1050
  CPU, so it buys back the part of a freeze you could have waited out. It does
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshcode",
3
- "version": "0.92.0",
3
+ "version": "0.93.0",
4
4
  "type": "module",
5
5
  "description": "moshcode \u2014 a metal wrapper for coding engines and native UGig/CoinPay workflow CLIs, with OpenPRD and moshscript",
6
6
  "repository": {
@@ -1596,20 +1596,23 @@ 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",
1599
+ { name: "nice", aliases: ["throttle"], args: "<command> | on | off | cpu <n> | io <n> | mem <size>", pitOnly: true,
1600
+ description: "run something at low priority so the box stays usable",
1601
1601
  synopsis: [
1602
+ ["/nice <command> [args]", "run one line throttled — a pit command, engine, tool, alias or shell line"],
1602
1603
  ["/nice [status]", "what the throttle is set to"],
1603
- ["/nice on | off", "toggle it (off by default)"],
1604
+ ["/nice on | off", "throttle everything from here on (off by default)"],
1604
1605
  ["/nice cpu <-20..19>", "nice level — higher yields more CPU"],
1605
1606
  ["/nice io <0..7>", "ionice best-effort level"],
1606
1607
  ["/nice mem <size> | off", "memory ceiling per engine (needs systemd)"],
1607
1608
  ],
1608
1609
  examples: [
1610
+ ["/nice agents claude", "throttle this engine, leave the setting alone"],
1611
+ ["/nice pnpm -r build", "a shell line works too"],
1609
1612
  ["/nice on", "nice -n10 + ionice -c2 -n7 for every engine started after"],
1610
1613
  ["/nice mem 2G", "the ceiling nice(1) can't give you — a runaway dies alone"],
1611
1614
  ],
1612
- seeAlso: ["agents", "start"] },
1615
+ seeAlso: ["agents", "start", "alias"] },
1613
1616
  { name: "alias", aliases: ["aliases"], args: 'set <name> "<cmd>" | list | get | rm | install <tool>', pitOnly: true,
1614
1617
  description: "name a line you keep retyping; /<name> runs it",
1615
1618
  synopsis: [
package/src/nice.mjs CHANGED
@@ -52,6 +52,42 @@ export function niceFile() {
52
52
  return path.join(os.homedir(), ".moshcode", "nice.json");
53
53
  }
54
54
 
55
+ /**
56
+ * `/nice <line>` throttles one line without changing the saved setting.
57
+ *
58
+ * Module-level rather than threaded through every call because the thing being
59
+ * throttled is not a function argument -- it is whatever that line eventually
60
+ * spawns, which may be an alias that expands to a pit command that starts an
61
+ * engine, three dispatch rounds later. The pit reads one line at a time and
62
+ * fully awaits it, so "armed until the next line the user types" is both the
63
+ * simplest implementation and exactly the intent.
64
+ */
65
+ let oneShot = false;
66
+
67
+ /** Throttle whatever the current line ends up spawning. */
68
+ export function armOneShot() { oneShot = true; }
69
+
70
+ /**
71
+ * Stop throttling. The pit calls this when it reads a fresh line, NOT when a
72
+ * command finishes: one typed line can dispatch several times through alias
73
+ * expansion, and all of it is the line the user asked to be nice.
74
+ */
75
+ export function disarmOneShot() { oneShot = false; }
76
+
77
+ /** Is a one-shot throttle in force? */
78
+ export function oneShotArmed() { return oneShot; }
79
+
80
+ /**
81
+ * The settings a spawn should actually use: what is saved, plus a one-shot.
82
+ *
83
+ * `/nice on` and `/nice <cmd>` end in the same place by design -- a spawn does
84
+ * not need to know which of the two asked for it.
85
+ */
86
+ export function effectiveNice() {
87
+ const saved = loadNice();
88
+ return oneShot ? { ...saved, on: true } : saved;
89
+ }
90
+
55
91
  /**
56
92
  * The current settings, always a complete object.
57
93
  *
@@ -135,7 +171,7 @@ export function canCapMemory({ has = haveBin, env = process.env } = {}) {
135
171
  * rather than a wrong guess.
136
172
  */
137
173
  export function throttleSpec(spec, {
138
- settings = loadNice(),
174
+ settings = effectiveNice(),
139
175
  has = haveBin,
140
176
  env = process.env,
141
177
  platform = process.platform,
package/src/tui.mjs CHANGED
@@ -22,7 +22,10 @@ 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
+ import {
26
+ loadNice, saveNice, describeNice, canCapMemory, parseMemory,
27
+ armOneShot, disarmOneShot,
28
+ } from "./nice.mjs";
26
29
  import { mcpCommand, pluginCommand, skillCommand } from "./integrations.mjs";
27
30
  import { stocksCommand } from "./advisor.mjs";
28
31
  import { cryptoCommand } from "./crypto.mjs";
@@ -389,6 +392,16 @@ function isInstalledTool(key) {
389
392
  * line, not an argument list: re-joining tokens would drop the quoting that the
390
393
  * shell still has to read.
391
394
  */
395
+ /**
396
+ * The words that mean "configure the throttle" rather than "run this".
397
+ *
398
+ * Kept next to the command that implements them so the two cannot drift: every
399
+ * other first word after `/nice` is a line to run, so adding a verb here
400
+ * without teaching niceCommand about it would make that word silently
401
+ * unrunnable rather than produce an error.
402
+ */
403
+ const NICE_SETTING_VERBS = new Set(["on", "off", "status", "cpu", "io", "mem", "memory"]);
404
+
392
405
  /**
393
406
  * `/nice` — run the CLIs the pit starts at a lower priority than your terminal.
394
407
  *
@@ -929,6 +942,11 @@ export async function tui() {
929
942
  finally { atPrompt(null); }
930
943
  if (line == null) break; // Ctrl-D
931
944
  expansions = 0;
945
+ // A one-shot `/nice <line>` lasts exactly as long as the line that asked
946
+ // for it. Cleared here, where a REAL line is read, and not when a command
947
+ // returns: one typed line can dispatch several times through alias
948
+ // expansion, and all of that is still the line the user said to be nice.
949
+ disarmOneShot();
932
950
  line = line.trim();
933
951
  if (!line) continue;
934
952
  saveHistory(); // readline just recorded this line into the shared history
@@ -994,7 +1012,31 @@ export async function tui() {
994
1012
  continue;
995
1013
  }
996
1014
  if (cmd === "alias" || cmd === "aliases") { aliasCommand(rest, line); continue; }
997
- if (cmd === "nice" || cmd === "throttle") { niceCommand(rest); continue; }
1015
+ if (cmd === "nice" || cmd === "throttle") {
1016
+ // `/nice <line>` runs one line throttled without touching the setting --
1017
+ // the form anyone who has used nice(1) reaches for first. The settings
1018
+ // verbs win the name, so a command called `on` is unreachable this way;
1019
+ // that is the right trade for `/nice on` meaning what it obviously means.
1020
+ const sub = String(rest[0] ?? "").toLowerCase().replace(/^\//, "");
1021
+ // A leading flag (`/nice --json`) is asking about the throttle, not
1022
+ // naming a program: no line the pit runs starts with a dash.
1023
+ if (!rest.length || sub.startsWith("-") || NICE_SETTING_VERBS.has(sub)) {
1024
+ niceCommand(rest);
1025
+ continue;
1026
+ }
1027
+
1028
+ // Hand the remainder back to the top of this loop rather than dispatching
1029
+ // it here, exactly as alias expansion does. That is what makes `/nice`
1030
+ // work on anything the pit can already run -- a pit command, an engine, a
1031
+ // tool, an alias, or a bare shell line -- with no roster of its own to
1032
+ // drift out of date. The leading slash is optional because the dispatcher
1033
+ // strips one anyway, so `/nice agents claude` and `/nice /agents claude`
1034
+ // are the same line.
1035
+ armOneShot();
1036
+ pending = commandRemainder(line);
1037
+ console.log(ash(` ▸ throttled: ${pending}`));
1038
+ continue;
1039
+ }
998
1040
  if (cmd === "pwd" || cmd === "where") { printPwd(); continue; }
999
1041
  if (cmd === "login") {
1000
1042
  const device = rest.includes("--device") || rest.includes("device") || rest.includes("-d");