Require bug/feature reports via issue forms with automated format checks, while accepting legacy Bug: titles from older app builds.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror the existing mosh bundling pipeline so that release and tag
builds automatically fetch and package the EternalTerminal `et`
client alongside mosh-client.
Build the EternalTerminal `et` client in CI the way mosh-client is:
- build-et-binaries.yml builds et for linux x64/arm64, macOS universal
and windows x64, uploads artifacts, and optionally publishes them to a
dedicated binary repository on manual workflow_dispatch.
- scripts/build-et/{linux,macos,windows} compile et from upstream.
- .gitignore excludes the fetched binaries; resources/et/README.md
documents source provenance.
After the GitHub Release is published, push an updated Cask to
binaricat/homebrew-netcatty so `brew install binaricat/netcatty/netcatty`
stays current within minutes of the release. Stable tags only — prerelease
tags (v1.2.0-rc.1 etc.) are skipped to keep brew users on stable.
Implementation:
- New script .github/scripts/bump-homebrew-cask.sh computes SHA-256 of the
arm64 + x64 DMGs already downloaded by the release job, sed-patches the
Cask file in the tap repo, sanity-checks the result parses as Ruby, and
pushes the bump. Idempotent on re-run when checksums match.
- New homebrew-tap job in build.yml runs after the release job on the same
stable-tag gate, downloads the macOS artifact bundle, then runs the
bump script with HOMEBREW_TAP_TOKEN.
Requires HOMEBREW_TAP_TOKEN secret with contents:write on
binaricat/homebrew-netcatty. With the secret missing the job will fail
fast at the env-var check with no side effects (no push attempted).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Run CI on every push/PR; gate release on strict v<X>.<Y>.<Z> tags
The build-packages workflow used to trigger only on `push: tags: v*`,
so branches and PRs never built and the only way to test the matrix
was to push a tag — which also auto-published a GitHub Release. That
made it impossible to verify a CI change without either skipping
testing or shipping a junk release.
Restructure the triggers:
- `push: branches: ['**']` + `pull_request` so any push or PR runs
the build matrix and uploads workflow artifacts.
- `push: tags` accepts only strict semver: `v<MAJOR>.<MINOR>.<PATCH>`
with an optional pre-release suffix like `v1.2.3-rc.1`. Loose tags
(`v-test`, `vNEXT`, `v1.0`) no longer match.
- The release job's `if:` enforces the same rule independently — even
if someone re-broadens the trigger later, branches and PRs can't
publish a release.
- `Set version` produces semver-compliant `0.0.0-sha.<short>` for
non-tag runs so `npm pkg set` / electron-builder don't choke on a
bare commit SHA like `abc1234`.
- Add a concurrency group that cancels superseded branch/PR builds
to save runner minutes; tag builds use a unique group so releases
never get cancelled by a follow-up commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply strict-semver Set-version step to Linux jobs too
The previous commit only patched the matrix job's Set version step
(macOS/Windows) because the Linux legs had a slightly different
template (no comments). The Linux Set version step kept setting
package.json's version to a bare 7-char commit SHA like "812f296",
which electron-builder rejects with `Invalid version: "812f296"`
during normalizePackageData.
Replicate the same strict regex + 0.0.0-sha.<short> fallback in both
Linux jobs so non-tag runs produce a valid semver across the matrix.
Reproduced from build-linux-x64 logs of the run on 112bf3a1:
Setting version to 812f296
⨯ Invalid version: "812f296" failedTask=build
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix build workflow trigger review issues
* Address build workflow review findings
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Bundle mosh-client via CI build pipeline
Add a GitHub Actions workflow that builds a static, distro-portable
mosh-client for linux-x64, linux-arm64, darwin-universal (arm64+x86_64)
from upstream mobile-shell/mosh source, plus a pinned win32-x64 binary
sourced from FluentTerminal (GPL-3.0). Releases attach SHA256SUMS so
scripts/fetch-mosh-binaries.cjs can verify and pull the right binary
into resources/mosh/<platform-arch>/ during npm run pack.
electron-builder.config.cjs gains a moshExtraResources() helper that
adds the binary to extraResources only when present on disk, keeping
local dev packages working without bundled mosh.
terminalBridge.cjs now exports bundledMoshClient() and prefers the
bundled static client over whatever the system mosh wrapper would
resolve via PATH (via the MOSH_CLIENT env var). The Windows branch
throws a clear error pointing at Settings instead of silently falling
back to a literal "mosh.exe" string when no wrapper is installed.
This is Phase 1 — Phase 2 (follow-up) replaces the FluentTerminal
Windows binary with an in-CI Cygwin static build and adds a Node-side
mosh-server bootstrap so Mosh works out-of-the-box on Windows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Phase 2: Node-side Mosh handshake (no Perl wrapper required)
Reimplement what the upstream Mosh Perl wrapper does in pure Node:
spawn `ssh [user@]host -- mosh-server new`, sniff the byte stream
for `MOSH CONNECT <port> <key>`, then spawn `mosh-client` locally
with MOSH_KEY in the environment.
The new electron/bridges/moshHandshake.cjs module exposes the parser,
sniffer, and command builders as pure functions so they can be unit
tested without spawning real ssh. terminalBridge.startMoshSession now
prefers this path whenever a bare mosh-client (bundled, explicit, or
system) and ssh (in-box OpenSSH on Win10 1809+, system everywhere
else) are both detectable. The legacy path through the system mosh
Perl wrapper is preserved as a fallback so users with custom mosh
setups don't regress.
Auth is delegated to system ssh, so keys, agent, ssh_config, and
known_hosts all keep working. Password / 2FA need a controlling TTY
which the bootstrap doesn't provide; affected users keep the legacy
wrapper path until interactive UI lands.
Tests:
- moshHandshake.test.cjs (20 tests) — parser corner cases, command
builders, sniffer split-chunk handling, ring-buffer trim, exec
resolver
- terminalBridge.bareMoshClient.test.cjs (4 tests) — explicit-path
basename gating
317 → 341 passing tests; lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Phase 3: in-CI Cygwin Windows build + visible PTY handshake
Phase 3a — in-CI Cygwin Windows build
- scripts/build-mosh/build-windows.sh builds mosh-client.exe from
upstream mobile-shell/mosh source inside Cygwin, then walks the
cygcheck import graph to bundle every required Cygwin DLL
(cygwin1.dll, cygcrypto, cygprotobuf, cygncursesw, etc) into a
tar.gz alongside the exe.
- The `build-mosh-binaries` workflow swaps the FluentTerminal-pinned
fetch job for a real Cygwin build (windows-latest + cygwin-install-
action). fetch-windows.sh is preserved as an emergency fallback but
no longer wired into the matrix.
- fetch-mosh-binaries.cjs unpacks the tar.gz into resources/mosh/
win32-x64/ so mosh-client.exe sits next to its DLLs.
- mosh-extra-resources.cjs ships the entire win32-x64/ dir
(exe + DLL bundle) into Resources/mosh/, so the packaged installer
runs on a stock Windows host with no Cygwin install.
Phase 3b — visible PTY handshake (password / 2FA prompts)
- terminalBridge.startMoshSession now spawns ssh inside node-pty so
the user sees and can answer password / 2FA / known-hosts prompts
in their terminal. When `MOSH CONNECT` is sniffed from the byte
stream, session.proc is atomically swapped from the ssh PTY to a
freshly-spawned mosh-client PTY. The MOSH CONNECT line itself is
redacted from the visible output.
- writeToSession / resizeSession read session.proc lazily, so input
arriving after the swap goes to mosh-client without extra wiring.
- The ZMODEM sentry is recreated for the new proc since its
writeToRemote closure captured the previous handle.
- Removes the earlier non-PTY child_process.spawn handshake — the
PTY-based one supersedes it.
Phase 3c — win32-arm64 deferred
- Cygwin's arm64 port has no stable cygwin1.dll release yet, so we
do not attempt an arm64 Windows build. arm64 Windows installs fall
through to the legacy `mosh` wrapper path that the bridge already
handles. Documented in the workflow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Allow branch/PR pushes to test the mosh-binaries workflow
Mirrors the build-packages workflow change in #868: any push or PR
that touches the mosh build pipeline triggers the matrix (artifacts
only, no release), while only `mosh-bin-*` tag pushes (or an
explicit workflow_dispatch with release_tag) publish a release.
`paths` filter keeps unrelated commits from running this expensive
workflow (~30min for the Cygwin leg). Concurrency group cancels
superseded branch/PR builds; tag builds use a unique group so a
follow-up commit can't kill an in-progress release.
Release job's `if:` enforces the same rule independently — even if
the trigger gets re-broadened, branches/PRs can't leak a release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix mosh binary workflow runners
* Fix Windows mosh workflow invocation
* Keep shell scripts LF in workflow checkouts
* Trigger mosh workflow on attributes changes
* Fix mosh build tool dependencies
* Fix Linux mosh static build
* Fix macOS mosh build tool lookup
* Skip macOS ncurses terminfo install
* Fix mosh PR review findings
* Allow Linux system mosh dependencies
* Fix Windows mosh DLL bundling
* Limit bundled Windows mosh DLLs
* Honor configured PATH for mosh handshake
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: prevent crash when codex-acp binary is not found (#645)
When codex-acp is not installed, resolveCodexAcpBinaryPath returned the
bare binary name as a fallback. This caused createACPProvider to spawn a
non-existent process, emitting an async ENOENT error that crashed the app.
Return null instead of the bare name and guard all createACPProvider call
sites so the error is handled gracefully.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: install cross-platform codex-acp binaries in CI build
macOS and Windows CI builds produce both arm64 and x64 packages, but
npm ci only installs optional dependencies for the host platform. This
means the codex-acp native binary for the other architecture is missing
from the packaged app, causing ENOENT crashes for users on the
non-host architecture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add --force to bypass cpu/os constraints for cross-arch install
The platform-specific codex-acp packages declare cpu/os constraints in
their package.json, so npm refuses to install the non-host-arch binary
with EBADPLATFORM. Use --force to bypass this check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gh run download requires actions:read scope. Without it, the recovery
step would fail silently when trying to re-download individual artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
download-artifact@v4 merge-multiple can silently drop files when
multiple artifacts contain same-named files (builder-debug.yml).
This caused latest-mac.yml to be missing from v1.0.64 release.
Add a verification step that checks all platform update yml files
exist after merge. If any are missing, re-downloads individual
artifacts to recover them. Fails the release if recovery fails.
Fixes#456
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added environment variables for npm configuration to specify architecture in CI jobs for both x64 and arm64 builds.
- Implemented verification steps for downloaded Linux deb artifacts, ensuring both amd64 and arm64 versions are checked for integrity.
- Updated the `ensure-node-pty-linux.sh` script to resolve and verify serialport prebuilds, ensuring compatibility with the specified architecture.
- Enhanced the `verify-linux-deb-artifact.sh` script to allow optional deb file input and improved error handling for missing artifacts.
These changes improve the reliability of the build process and ensure that the correct native modules are used for each architecture.
The v1.0.62 amd64 deb/AppImage shipped with an aarch64 node-pty binary
because the build pipeline never explicitly locked the target architecture:
1. `electron-rebuild` was called without `--arch`, relying on auto-detection
2. electron-builder's default `npmRebuild` re-compiled native modules during
packaging, adding a second uncontrolled rebuild that could override the
prepare script's output
3. The x64 job did not set `npm_config_arch`, unlike the arm64 job
Changes:
- Pass `--arch` explicitly to `electron-rebuild` in ensure-node-pty-linux.sh
- Set `npm_config_arch: x64` in the x64 CI job (prepare + build steps)
- Disable `npmRebuild` in electron-builder config so only the prepare script
controls native module compilation
Closes#446, closes#448
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ubuntu-latest (24.04) links native modules against glibc 2.39 which can
cause dlopen failures on some distros. Pin to 22.04 (glibc 2.35) for
wider compatibility across Linux distributions.
Related: #264
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only pass CSC_LINK, CSC_KEY_PASSWORD, and Apple notarization secrets
to the macOS matrix job. Previously these were passed to all matrix
jobs, causing electron-builder to sign Windows .exe with the Apple
Developer ID certificate. Windows doesn't trust Apple's certificate
chain, so electron-updater's signature verification failed during
auto-update.
Closes#309
* feat: add auto-update support via electron-updater (#289)
- Add autoUpdateBridge.cjs wrapping electron-updater for check/download/install
- Register bridge in main.cjs, expose IPC in preload.cjs
- Add auto-update methods to NetcattyBridge type in global.d.ts
- Extend updateService.ts with electron-updater bridge functions
- Add Software Update section in Settings > System tab with state machine UI
- Add i18n keys for update UI (en + zh-CN)
- Add publish config for GitHub Releases in electron-builder.config.cjs
- Update CI workflow to upload update metadata (*.yml, *.blockmap, *.zip)
- Fallback to manual GitHub download for unsupported platforms or errors
* fix: address Codex review - guard bridge call and pin sender window
- Guard optional bridge call in SettingsSystemTab to prevent TypeError
when getAppInfo is unavailable (e.g. browser/dev/test rendering)
- Capture senderWindow at download initiation in autoUpdateBridge so
progress/downloaded/error events always go to the requesting renderer,
even if focus changes during download
* fix: use semver ordering for version check and clean up listeners on rejection
- Replace strict equality (===) with localeCompare for version comparison
to avoid false positives on pre-release/nightly builds
- Clean up download-progress/update-downloaded/error listeners in the
catch path when downloadUpdate() rejects before emitting events
* feat: redirect update toast to Settings window for in-app update
- Update toast notification now opens Settings window instead of
GitHub Releases page, enabling the in-app download/install flow
- Add 'update.viewInSettings' i18n key (en + zh-CN)
- Remove unused openReleasePage from App.tsx destructuring
- Move useWindowControls() before the update effect to fix declaration order
- Enable hardenedRuntime and notarize in electron-builder config
- Remove FixQuarantine.app workaround from DMG (no longer needed
with proper code signing)
- Pass signing and notarization secrets in CI build step
- Shrink DMG window to fit the simpler two-icon layout
The debian:bullseye container introduced in v1.0.39 broke native module
packaging — node-pty's .node binary was missing from app.asar.unpacked,
causing 'No such file or directory' on ArchLinux and other x64 distros.
Revert to the v1.0.38 approach: build x64 directly on ubuntu-latest
with setup-node. ARM64 keeps the Debian container for GLIBC compat.
Closes#264
The Linux x64 AppImage was missing the compiled node-pty native module
(pty.node), causing the app to crash on launch. This happened because
the bare ubuntu-latest runner lacked build-essential/python3 needed by
node-gyp to compile native addons.
Move the Linux x64 build into a dedicated job using debian:bullseye
container (matching the ARM64 job) which:
- Installs build-essential, python3 and other native build deps
- Ensures node-pty, ssh2, cpu-features compile correctly
- Pins GLIBC to 2.31 for broader distro compatibility
Fixes#264
* fix(ci): build linux-arm64 in Debian Buster container for GLIBC 2.28 compat\n\nSplit linux-arm64 out of the build matrix into a dedicated job that\nruns inside a debian:buster container (GLIBC 2.28) on the ARM64 runner.\nThis ensures the compiled node-pty native module is compatible with\nolder distros like UOS/Deepin.\n\nCloses #253
* fix(ci): use archive.debian.org for EOL Buster repos
* fix(ci): switch to debian:bullseye for Python 3.9 + GLIBC 2.31 compat\n\nBuster's Python 3.7 is too old for node-gyp@11 (walrus operator).\nBullseye provides Python 3.9 and GLIBC 2.31 which is still below\nthe critical 2.34 boundary (libpthread merge into libc).
Use a dedicated step with `if` condition so npm_config_arch is only
set for linux-arm64. The previous approach set it to an empty string
for other jobs, which could interfere with node-gyp arch detection
on macOS, Windows, and linux-x64 builds.
On ubuntu-24.04-arm runners, electron-builder's post-build
@electron/rebuild incorrectly tries to restore native modules
to x64 architecture. The ARM64 g++ compiler doesn't support the
-m64 flag, causing the build to fail.
Setting npm_config_arch=arm64 ensures node-gyp correctly identifies
the host architecture, preventing the erroneous x64 rebuild.
The ARM64 AppImage contained x86-64 native modules (node-pty, ssh2)
because both architectures were built on the same x86 runner.
- Split Linux build into linux-x64 (ubuntu-latest) and linux-arm64
(ubuntu-24.04-arm) jobs so native modules compile on the correct arch
- Add pack:linux-x64 and pack:linux-arm64 npm scripts with explicit
--x64/--arm64 flags
- Unify CI build step using matrix variables instead of per-OS conditions
- Use toast.success/warning instead of toast({title:}) for better UX
- Change credentials copy format to labeled multi-line format
- Add ESLint global declarations for Node.js globals
Co-Authored-By: Claude <noreply@anthropic.com>
The workflow triggers on `v*` tags and build.yml extracts version from
any v-prefixed tag. Updated the regex from `^v\d+\.\d+\.\d+` to `^v\d`
to accept tags like v1, v1.2, v1.2.3, etc.
This ensures version parsing stays in sync with build.yml behavior.
Co-Authored-By: Claude <noreply@anthropic.com>
- Tag release: use version from tag (e.g., v1.2.3 -> 1.2.3)
- workflow_dispatch: use short commit ID (first 7 chars)
This ensures electron-builder artifacts match the release notes
download links in all scenarios.
Co-Authored-By: Claude <noreply@anthropic.com>
Version priority is now:
1. VERSION env variable
2. Valid version tag (v1.2.3 format)
3. Short commit ID (first 7 chars)
4. package.json version as final fallback
Co-Authored-By: Claude <noreply@anthropic.com>
When workflow is triggered via workflow_dispatch, GITHUB_REF_NAME is
the branch name (e.g., "main") instead of a version tag. This caused
the generated release notes to have incorrect download URLs.
Now the script:
1. Checks if GITHUB_REF_NAME is a valid version tag (v1.2.3 format)
2. Falls back to reading version from package.json if not
This ensures the release notes always match the actual artifact
filenames produced by electron-builder.
Co-Authored-By: Claude <noreply@anthropic.com>
Enables publishing a GitHub Release when the workflow is manually
triggered and the corresponding input is set, providing more
flexibility for release management beyond tag-based automation.
Switches from the default GitHub token to a specific release token
for artifact uploads, improving security and aligning with best
practices for token management.
Ensures the npm package version automatically matches the Git tag
during CI builds triggered by version tags, improving consistency
between releases and tags.
Limits uploaded and released artifacts to specific file types
to avoid unnecessary files, improving release clarity.
Adds artifact listing for easier debugging and ensures
release notes are auto-generated. Sets unmatched files
to not fail the release step for smoother automation.