Repository navigation
Add hermod mvp - #1
Merged
Merged
Conversation
Add the Go implementation of the single-command remote-sandbox bridge, layered cli → control → git/mirror/sandbox → shell → execx: - execx: how a command runs — real subprocess vs printed dry-run, behind New(opts) - shell: where it runs — ssh/tmux decorators nested by the caller, over a leaf Factory - git: read local author identity (from the mirrored dir) to carry into the remote - mirror: the Mutagen file mirror — open (create-or-resume)/flush/pause/close/status - sandbox: the tmux-over-ssh session — attach-or-create + read-only liveness probe - control: apply the invocation options, run the flow, and settle the mirror by remote-session liveness alone (alive/unknown → pause; gone → flush then close) - cli: cobra edge (-s/-C/-n/-q, -- passthrough, ssh-host completion) + entrypoint Carry both GIT_AUTHOR_* and GIT_COMMITTER_*; default the session name to a path slug. Reconcile docs/ARCHITECTURE.md, the MVP design, and the cli spec with the implemented design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The execx package existed only to give shell its leaf executor, but its Command/Result types leaked into git, mirror, and sandbox purely as the Shell.Run signature. Fold execx into shell: Command/Result/ExitCode become shell types, and the real-vs-dry-run choice collapses into the leaf as an internal run func selected by NewLocal(dryRun bool) — no public Executor, subprocess, or dry-run surface remains. Also drop shell.Factory: control now builds the two leaves (real, effective) directly and hands each collaborator only the shell it needs, matching what ARCHITECTURE.md already described. Reconcile the docs (remote→sandbox, git.Reader→git.Git, dropped Executor/Resolve references) and fix the "sandbox sandbox" doubling. Dependency graph simplifies to git/mirror/sandbox → shell; net -164 lines, one package deleted. Behavior unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire signal.NotifyContext(SIGINT, SIGTERM) into the command context so an interrupted attach is killed and unwinds through the normal teardown path instead of leaking the mirror. Teardown itself runs on context.WithoutCancel so cleanup survives the cancellation that triggered it — otherwise the probe and pause/close calls would fail on the dead context, leaking the very mirror the handler exists to settle. Log the final pre-terminate flush failure instead of swallowing it: a failed flush risks losing unsynced edits, so surface it (termination still proceeds, since a live "gone" session is a leak). Cover the path with a teardown test. Refine comments per the code's own principle (why, not what): drop the WithWorkdir play-by-play and name-restating setter docs, and clarify NewLocal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a Makefile as the single entry point for development commands (build, test, lint, install, release) and point the README there instead of listing raw go commands. Build and release targets are real file targets keyed on the source tree, so Make skips binaries already newer than every source; release fans out over PLATFORMS via $(foreach) to cross-compile static binaries. Ignore the bin/ output directory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a completion target so `make install` regenerates the bash completion script from the freshly installed binary into the XDG per-command directory, keeping it in lockstep with the installed version. Modeled as real file targets keyed on sources, so an unchanged tree is a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI runs lint/test/build via the Makefile on PRs and pushes to main. Release cross-compiles per-platform binaries and attaches them with checksums to a published GitHub Release, using the tag as the version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A passthrough like `hermod host -- claude` exited immediately: tmux runs an explicit command in a bare non-login shell, so a PATH-installed tool (claude) is not found. The empty-command case worked only because tmux's default session is itself a login shell. Wrap the command via `exec "$SHELL" -lc "$1"` so it inherits the sandbox user's full PATH and profile. The wrapping is a new NewLoginShell shell decorator the sandbox composes into its interactive stack — the tmux transport stays generic; the login-shell policy lives with the session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the completed add-hermod-mvp change to the dated archive and sync its 5 delta specs into openspec/specs/ as the project's baseline main specs: cli, file-sync, git-identity, remote-session, session-orchestration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hermod MVP: one-command bridge to a remote dev box
Establishes the Hermod MVP — a single command that mirrors your working directory to a
remote sandbox, drops you into a persistent session running there, and cleans up on the
way out. The repo is spec-driven: behavior was defined as OpenSpec specs before any code
was written, and this PR ships both the specs and their implementation.
Why
Working on a remote dev box means hand-wiring three tools every session — Mutagen to mirror
files, SSH to get a shell, tmux to survive drops — plus threading git identity through so
commits are attributed correctly, and tearing it all down cleanly on the way out. The
friction is high enough that people avoid remote sandboxes even when the sandbox is where
the work belongs. Hermod collapses that setup into one command.
What Changes
The core loop is sync → attach → pause/teardown:
cli— parse one invocation into a resolved, immutable intent: sandbox host alias,session name (
-s), working directory (-C), dry-run (-n),--passthrough to theremote command, ssh-config path, log level.
file-sync— the Mutagen mirror lifecycle: open (create or resume), flush beforework, pause, and terminate.
remote-session— the remote interactive session (a tmux window reached over SSH):attach-or-create a persistent window, run a remote command, probe liveness.
git-identity— read localuser.name/user.emailand carry them into the remotesession's environment; a soft feature that never hard-fails when identity is unset.
session-orchestration— the end-to-end flow and its central decision: remote-sessionliveness after detach is the single source of truth for pause vs teardown.
Guarantees mirror teardown even on interrupt/error.
Dry-run (
-n) prints a copy-pasteable plan of side effects; read-only probes (git identity,sync status) stay real even under
--dry-run.Also included: Makefile (build/test/lint + dev workflow), CI and release GitHub Actions
workflows, and bash completions installed via
make install.Implementation notes
hermodbinary with no runtime dependencies. Itorchestrates tools already on the machine (Mutagen,
ssh,tmux) and reads~/.ssh/config; it does not bundle or provision them.internal/cli,internal/mirror,internal/sandbox,internal/git,internal/shell(local/ssh/tmux/login-shell execution, quoting, exit codes).add-hermod-mvpOpenSpec change and syncs its 5 delta specs intoopenspec/specs/as the project's baseline.