moshcode 0.90.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 +124 -0
- package/bin/moshcode.mjs +7 -0
- package/package.json +1 -1
- package/prd/0013-persistent-ssh-workspaces.md +1181 -0
- package/prd/README.md +2 -0
- package/src/cli-schema.mjs +123 -0
- package/src/commands.mjs +164 -0
- package/src/engines.mjs +16 -0
- package/src/nice.mjs +199 -0
- package/src/ssh.mjs +1228 -0
- package/src/tui.mjs +73 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ or miss one that does. A test fails the build when it drifts.
|
|
|
35
35
|
| `moshcode kill` | runtime | end a herd session |
|
|
36
36
|
| `moshcode wait` | runtime | block until a session is blocked, done, or idle |
|
|
37
37
|
| `moshcode restore` | runtime | rebuild the herd's sessions after a reboot |
|
|
38
|
+
| `moshcode ssh` | runtime | persistent SSH workspaces — one connection, many clean commands |
|
|
38
39
|
| `moshcode install` | engines | install an engine or workflow tool |
|
|
39
40
|
| `moshcode uninstall` <br>`remove` | engines | take an engine or workflow tool off this machine |
|
|
40
41
|
| `moshcode upgrade` <br>`update` | engines | update moshcode, engines, or tools |
|
|
@@ -579,6 +580,96 @@ current pit untouched.
|
|
|
579
580
|
The modes are not identical across providers. In particular, OpenCode `--auto`
|
|
580
581
|
auto-approves permission requests but continues to enforce explicit deny rules.
|
|
581
582
|
|
|
583
|
+
## SSH workspaces
|
|
584
|
+
|
|
585
|
+
`/ssh` keeps the SSH connection alive; `ssh exec` still gives each tool call a
|
|
586
|
+
clean command channel.
|
|
587
|
+
|
|
588
|
+
A coding run against a remote box is a few hundred small operations: read a
|
|
589
|
+
file, `git status`, apply a patch, run the tests. Each one as a fresh `ssh
|
|
590
|
+
user@host cmd` pays for a TCP handshake, a key exchange, a host-key check and
|
|
591
|
+
authentication every time. OpenSSH can carry many channels over one
|
|
592
|
+
authenticated connection, and `moshcode ssh` is a thin, careful wrapper over
|
|
593
|
+
exactly that — named targets, one persistent master connection per target,
|
|
594
|
+
and a `--json` execution surface built for agents.
|
|
595
|
+
|
|
596
|
+
```sh
|
|
597
|
+
moshcode ssh add dev deploy@example.com --cwd /srv/app # or an alias from ~/.ssh/config
|
|
598
|
+
moshcode ssh open dev # authenticate once
|
|
599
|
+
moshcode ssh exec dev -- git status --short # …then every command reuses it
|
|
600
|
+
moshcode ssh dev # a real shell, same connection
|
|
601
|
+
moshcode ssh close dev # or let it expire (--persist, default 10m)
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Nothing secret is stored. `~/.moshcode/ssh/targets.json` holds a host, a port
|
|
605
|
+
and a directory; your `~/.ssh/config`, agent, `known_hosts`, `ProxyJump` and
|
|
606
|
+
hardware keys keep working exactly as they do at the prompt, and host keys are
|
|
607
|
+
never auto-accepted. The connection lives in an OpenSSH ControlMaster behind a
|
|
608
|
+
socket only you can read, is checked with `ssh -O check` and closed with `ssh
|
|
609
|
+
-O exit`, and a socket the master has gone away from is cleaned up and reopened
|
|
610
|
+
on the next command.
|
|
611
|
+
|
|
612
|
+
### For an agent: one connection, many clean commands
|
|
613
|
+
|
|
614
|
+
`exec` runs each command on its own channel with no PTY, so stdout, stderr and
|
|
615
|
+
the exit status come back separately and stdin stays raw. `ok` is the
|
|
616
|
+
command's verdict; `transportOk` is ssh's. A `grep` that finds nothing is
|
|
617
|
+
`{ ok: false, transportOk: true, code: 1 }` — a fact about the files, not the
|
|
618
|
+
network.
|
|
619
|
+
|
|
620
|
+
```sh
|
|
621
|
+
moshcode ssh exec dev --json -- git diff --stat
|
|
622
|
+
# {
|
|
623
|
+
# "ok": true, "target": "dev", "connected": true, "transportOk": true,
|
|
624
|
+
# "code": 0, "signal": null,
|
|
625
|
+
# "stdout": " src/app.ts | 12 +++++---\n", "stderr": "", "durationMs": 14
|
|
626
|
+
# }
|
|
627
|
+
|
|
628
|
+
# a model-produced multi-file patch, applied in one round trip
|
|
629
|
+
git diff | moshcode ssh exec dev --json --stdin --cwd /srv/app -- git apply -
|
|
630
|
+
|
|
631
|
+
moshcode ssh exec dev --json --timeout 10m -- pnpm test
|
|
632
|
+
moshcode ssh exec dev --env NODE_ENV=test -- pnpm test # for this command only
|
|
633
|
+
moshcode ssh exec dev --sh 'git log --oneline | head -5' # a pipeline, on purpose
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
There is no shell state between calls, deliberately: `exec dev -- cd /tmp`
|
|
637
|
+
followed by `exec dev -- pwd` still answers with the target's cwd. Independent
|
|
638
|
+
commands may run concurrently over the same connection. A run that performs a
|
|
639
|
+
hundred operations authenticates once.
|
|
640
|
+
|
|
641
|
+
The same objects come back from moshscript:
|
|
642
|
+
|
|
643
|
+
```js
|
|
644
|
+
sshOpen("dev");
|
|
645
|
+
const r = sshExec("dev", ["git", "status", "--short"], { cwd: "/srv/app" });
|
|
646
|
+
if (!r.ok) say(r.stderr);
|
|
647
|
+
sshExec("dev", ["git", "apply", "-"], { cwd: "/srv/app", stdin: patch });
|
|
648
|
+
sshClose("dev");
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### When shell state matters
|
|
652
|
+
|
|
653
|
+
Some work needs a shell that remembers: a `cd`, an export, a dev server, a
|
|
654
|
+
REPL. `shell` puts one in tmux on the remote box, where it outlives this
|
|
655
|
+
terminal, this connection, and the laptop lid.
|
|
656
|
+
|
|
657
|
+
```sh
|
|
658
|
+
moshcode ssh shell dev --name app # create or attach · Ctrl-b d leaves it running
|
|
659
|
+
moshcode ssh shell send dev/app "pnpm dev" # type into it without attaching
|
|
660
|
+
moshcode ssh shell read dev/app --lines 40 # its screen, as text
|
|
661
|
+
moshcode ssh shell kill dev/app
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
If tmux is not on the remote box, `shell` says so and `exec` keeps working;
|
|
665
|
+
nothing is installed remotely on your behalf.
|
|
666
|
+
|
|
667
|
+
`put` and `get` copy single files over the same connection with `scp`; `put`
|
|
668
|
+
lands as a temp file and is renamed into place. `bench <name>` measures fresh
|
|
669
|
+
connections against the shared one on your own hosts — on a loopback sshd the
|
|
670
|
+
median command went from ~96ms to ~12ms, and on a real network the handshake
|
|
671
|
+
is the part that grows.
|
|
672
|
+
|
|
582
673
|
## Workflow tools: UGig, CoinPay, and the cloud CLIs
|
|
583
674
|
|
|
584
675
|
These remain independent native CLIs with their own authentication,
|
|
@@ -919,6 +1010,39 @@ an equity's score, and each response ships the `caveats` that say so. Prices are
|
|
|
919
1010
|
Alpaca's US venue alone and can differ materially from other exchanges. Research
|
|
920
1011
|
aid, not advice — and like `stocks`, nothing under `crypto` can place an order.
|
|
921
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
|
+
|
|
922
1046
|
### Aliases (`/alias`)
|
|
923
1047
|
|
|
924
1048
|
The pit is a prompt you sit at all day, so it lets you name the lines you keep
|
package/bin/moshcode.mjs
CHANGED
|
@@ -353,6 +353,13 @@ async function main() {
|
|
|
353
353
|
process.exitCode = (await herdCommand([cmd === "usage" ? "cost" : cmd, ...rest])) || 0;
|
|
354
354
|
return;
|
|
355
355
|
}
|
|
356
|
+
// SSH workspaces (PRD 0013). Imported here rather than at the top: it is a
|
|
357
|
+
// registry read and a few spawns, and `moshcode claude` never needs it.
|
|
358
|
+
if (cmd === "ssh") {
|
|
359
|
+
const { sshCommand } = await import("../src/ssh.mjs");
|
|
360
|
+
process.exitCode = (await sshCommand(rest)) || 0;
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
356
363
|
if (cmd === "tools") {
|
|
357
364
|
const asJson = rest.includes("--json");
|
|
358
365
|
printStatus(toolStatus(), asJson);
|
package/package.json
CHANGED