moshcode 0.90.0 → 0.91.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 +91 -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 +109 -0
- package/src/commands.mjs +164 -0
- package/src/ssh.mjs +1228 -0
- package/src/tui.mjs +14 -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,
|
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