nssh: add Eternal Terminal (et) as a third transport#15
Conversation
Adds et as a third interactive transport alongside ssh/mosh, with ET-preferred auto-detection (binary check only) and a Mosh->Transport log schema change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires `et` in alongside ssh and mosh in selectTransport. With no
--ssh/--mosh/--et flag, auto-selection now prefers et (local `et` +
remote `etserver`), then mosh, else ssh — binary-presence check only,
mirroring the existing mosh detection.
The pure decision is factored into pickTransport and covered by a
truth-table test (transport_test.go); selectTransport feeds it real
LookPath + remote `command -v` probes (the two probes are folded into
one remoteHasCommand helper). Remote probes are skipped when a transport
is forced or its local binary is absent.
The session-end log's `Mosh *bool` becomes `Transport string`
("ssh"|"mosh"|"et") since a bool can't express three transports; status
rendering and protocol.md updated to match. Docs (CLAUDE.md, internals,
README) describe the new transport and selection order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a09bc54386
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| switch pickTransport(force, localMosh, remoteMosh, localET, remoteET) { | ||
| case "et": | ||
| fmt.Fprintln(os.Stderr, "nssh: using et for interactive session") | ||
| return exec.Command("et", sshTarget), "et" |
There was a problem hiding this comment.
Report missing ET client instead of succeeding
When users force the new --et path on a machine without the local ET client, this branch still returns a command that cannot be started. runSession returns that start error, but nsshMain only converts *exec.ExitError into a non-zero exitCode, so this scenario prints using et, logs transport=et with exit=0, and exits successfully without explaining that et was not found. Please either check exec.LookPath("et") before selecting this forced transport or make start errors propagate as a non-zero failure.
Useful? React with 👍 / 👎.
What
Wires Eternal Terminal (
et) in as a third interactive transport alongsidesshandmosh.--etflag, mutually exclusive with--ssh/--mosh.et → mosh → ssh.etwins whenetis on the local PATH andetserveris on the remote PATH — binary-presence check only, mirroring the existingmosh-serverprobe.etlaunches aset <host>(resolves the host via~/.ssh/config, default port); like the mosh path, extra ssh flags aren't forwarded. NoLC_ALLoverride (mosh-specific).How
pickTransport(force, localMosh, remoteMosh, localET, remoteET)and covered by a 12-case truth-table test (transport_test.go).selectTransportfeeds it realLookPath+ remotecommand -vprobes, folded into oneremoteHasCommandhelper. Remote SSH probes are skipped when a transport is forced or its local binary is absent.session-endlog'sMosh *boolbecomesTransport string(ssh/mosh/et).statusrendering anddocs/protocol.mdupdated to match; old logs'moshfield is harmlessly ignored.Why this is cleanly scoped
nssh's clipboard/URL bridge runs over ntfy and is transport-agnostic — the interactive transport is just the subprocess
runSessionexecs after the remote-prepare step. ET slots in as a peer of mosh with no changes to the ntfy subscriber, wire format, shims, remote prepare, or collision logic.Out of scope
et_portconfig knob (default port only).nssh sweepforet/etterminal(etserver is a shared daemon).et(mirrors the existing mosh limitation).Verification
go build,go vet, fullgo test ./...green;gofmtclean. Confirmed against the built binary: usage shows[--ssh|--mosh|--et], and--ssh --eterrors with the mutual-exclusion message.Design doc:
docs/superpowers/specs/2026-06-03-eternal-terminal-transport-design.md🤖 Generated with Claude Code
Note
Low Risk
Additive transport and logging only; ntfy bridge, shims, and auth boundaries are untouched, with unit tests on selection logic.
Overview
Adds Eternal Terminal (
et) as a third interactive shell transport next tosshandmosh, with a new--etflag (mutually exclusive with--ssh/--mosh). When no transport is forced, auto-selection is nowet→mosh→ssh, using localLookPathplus remotecommand -vforetserver/mosh-servervia a sharedremoteHasCommandhelper; the pure decision is inpickTransport, covered bytransport_test.go.Session logging replaces
Mosh *boolonsession-endwithTransport(ssh/mosh/et);nssh status --tailanddocs/protocol.mdmatch. Docs (README, CLAUDE.md, internals) describe ET behavior and note the ntfy clipboard/URL bridge is unchanged.Reviewed by Cursor Bugbot for commit a09bc54. Bugbot is set up for automated code reviews on this repo. Configure here.