The repository had no automation at all: every release was packaged and
published by hand, and nothing ran tests on a pull request.
- ci.yml runs gofmt, go vet and go test on Linux and macOS, and cross-builds
all five release targets. macOS is a stated release target but was never
actually exercised, only cross-compiled.
- release.yml publishes on a v* tag. It gates on `make release-check` so a red
suite cannot ship, and builds through release.sh rather than duplicating the
packaging rules, so CI archives stay byte-identical to local ones. A
hand-written docs/releases/<tag>.md becomes the release body when present,
otherwise notes are generated from history.
- nightly.yml rebuilds the tip of main on every push and replaces a rolling
`nightly` prerelease. Prerelease is deliberate: it keeps GitHub's `Latest`
badge on the newest real release rather than on an untested build.
The rolling tag is why version discovery was pinned to v* in the previous
commit; nightly.yml depends on that filter already being in place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nightly builds will move a rolling `nightly` tag across main. A plain
`git describe --tags` returns whichever tag is nearest, so once that tag exists
every build — including a real release build — would report its version as
"nightly" and lose the release lineage entirely.
Restrict discovery to `v*` so the rolling tag is invisible to versioning:
with a nightly tag ahead of v0.3.1
git describe --tags → nightly
git describe --tags --match 'v*' → v0.3.1-1-gf940087
Land this before the nightly workflow exists, so no build is ever stamped from
the rolling tag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`sshkeeper forward add` could never succeed. RunE read --local-port and init()
marked it required, but the flag was never registered on forwardAddCmd. Cobra
ignores MarkFlagRequired for an unknown flag, and GetInt returns 0 for one, so
every invocation failed validation with "invalid local port 0: must be
1-65535". There was no argument combination that worked.
Register the flag so both the read and the required annotation bind to a real
option. README and the guide already documented --local-port, so the intent was
there from the start; only the registration was missing.
The existing tests missed this because they build a throwaway cobra.Command,
register the flags on it themselves, and pass it to forwardAddCmd.RunE — the
real command's flag set was never exercised. Add a test that parses argv into
forwardAddCmd's own flags, plus one pinning the required annotation. Both fail
against the unfixed command with "unknown flag: --local-port".
Present since c2edaa4, so v0.2.0 and v0.3.0 both ship it broken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Complete the final task of the unified TUI shell plan, which had been left
undone: the code dropped F1 in favour of Ctrl+H, but README and the guide
still documented F1 as the full-help key in nine places, and the repository
screenshots still showed the pre-shell UI.
- Replace every F1 reference in README.md and docs/guide.md with Ctrl+H,
including the two ASCII help blocks that mirror runtime output.
- Document the BS/DEL terminal constraint: Ctrl+H is BS (0x08) while xterm
sends DEL (0x7F) for Backspace, so help and text editing do not collide.
- Recapture all five screenshots from the current binary in a real xterm at
120x40, 80x24 and 60x16 using an isolated XDG profile.
Runtime verification in xterm confirms Ctrl+H opens full help while seven
Backspace presses correctly trim typed text, and that every captured screen
carries the header, framed content and bottom-anchored footer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1. Forward type selector: visible radio items (1.Local 2.Remote 3.SOCKS) with descriptions
2. Forward list: column header row (NAME/TYPE/LISTEN/TARGET/ON)
3. Forward delete: confirmation dialog before deletion
4. Server route column: → icon for via/chain, spaces for direct
- Forward form: type selector (Local/Remote/SOCKS) with radio items
- Dynamic fields: listen addr/port, target addr/port based on type
- Default listen: 127.0.0.1, warning for 0.0.0.0
- Forward list: table view NAME/TYPE/LISTEN/TARGET/ENABLED
- Forward edit: Enter/Ctrl+E opens pre-filled edit form
- Human explanation and OpenSSH preview for selected forward
- Tunnel state manager: PID tracking, start/stop, state file
- Tunnel manager screen: list running tunnels, stop, refresh
- Action menu: Connect/Connect with tunnels/Start tunnels only/Start tunnels in background/Manage port forwards/Manage tunnels/Manage route/Test/Edit/Delete
- Help screen: updated shortcuts
- CLI: tunnel --background for detached tunnel process
- README: updated with forward vs tunnel examples, new hotkeys
- Forward model: add Name, Description, Enabled fields
- DB migration 003: add name/description/enabled columns to forwards
- Forward type: radio selector (Local/Remote/SOCKS) instead of free text
- Forward form: dynamic fields based on type, 127.0.0.1 default, 0.0.0.0 warning
- Forward list: table view with NAME/TYPE/LISTEN/TARGET/ENABLED columns
- Forward edit: Enter/Ctrl+E opens edit form
- Human explanation and OpenSSH preview for selected forward
- Tunnel state manager: PID tracking, start/stop, state file
- Tunnel manager screen: list running tunnels, stop, refresh
- Action menu: reworked with Connect/Connect with tunnels/Start tunnels only/Start tunnels in background/Manage port forwards/Manage tunnels/Manage route/Test/Edit/Delete
- Help screen: updated with all shortcuts
- CLI: tunnel --background for detached tunnel process
- Default listen address: 127.0.0.1 instead of 0.0.0.0
- Validation: type required, ports 1-65535, target required for local/remote