feat: add self-update command#10
Conversation
- fetch latest stable release from GitHub API - semver comparison with current version - atomic binary replacement with rollback on failure - platform asset name mapping (darwin->macos, amd64->x64) - Windows self-replace unsupported (returns error with release URL)
--update downloads and replaces the running binary --check only reports current/latest version and release URL
9 new keys: check failure, current/latest version, up-to-date, update available, release URL, downloading, apply failure, success
Uninstall() returns error instead of calling os.Exit, letting main handle exit code. cli.Init() loads i18n translations once at startup so subcommands can use localized messages.
Update(checkOnly bool) returns error. Removes updater dep from main and moves all i18n/update logic out of main.go.
There was a problem hiding this comment.
Code Review
This pull request refactors the CLI commands out of main.go into a new cli package and introduces self-update capabilities by checking and applying releases from GitHub, along with new localization strings. Feedback on these changes highlights several issues: a background goroutine designed to clean up the old binary will never execute due to immediate process termination, ignoring the error from cli.Init() could lead to silent initialization failures, the custom semver parser fails to handle pre-release tags correctly (where using golang.org/x/mod/semver is recommended), and ignoring the error from http.NewRequest could cause a nil pointer dereference panic.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
Adds a self-update/check feature to the ftm CLI by introducing a new internal/updater package that queries GitHub Releases, selects the platform-specific asset, and replaces the current executable. The PR also refactors uninstall/update-related CLI logic out of cmd/ftm/main.go into a new internal/cli package and adds i18n strings for the new commands.
Changes:
- Introduce
internal/updaterto fetch the latest GitHub release and apply an in-place binary update. - Add
--checkand--updateflags tocmd/ftm/main.gowired throughinternal/cli. - Extend EN/ES locale files with update-related messages.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/updater/updater.go | Implements GitHub release lookup, asset selection, download, and binary replacement. |
| internal/cli/update.go | Adds Update(checkOnly bool) command wrapper around internal/updater with i18n output. |
| internal/cli/uninstall.go | Moves uninstall logic into internal/cli. |
| internal/cli/cli.go | Centralizes CLI initialization (i18n load). |
| cmd/ftm/main.go | Adds --check / --update flags and delegates to internal/cli. |
| internal/i18n/locales/en.yaml | Adds update-related translation strings. |
| internal/i18n/locales/es.yaml | Adds update-related translation strings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Moves chmod + atomic rename + macOS xattr logic out of Apply() into apply_unix.go. Cross-platform Apply() now resolves exec path, downloads to temp, and delegates to applyUpdate().
Stage new binary as <name>.new, write a .bat that polls move every 1s (Windows holds an exclusive lock on the running exe), then spawn it detached. Caller should os.Exit immediately so the .bat can complete the swap; next ftm launch uses the new binary. Cross-compiles cleanly with GOOS=windows.
- async check on Init() and every 6h - gold badge in list header when a newer release exists - 'u' key downloads and replaces the running binary, then os.Exit(0); matches cli.Update behavior (no graceful shutdown that could hang on running tunnels/ws clients)
- update_tui_badge: '↑ Update available: v{0} — press u to install'
- update_applying: 'Downloading and installing update...'
- GET /api/update returns { current, latest, tag, hasUpdate, ... }
- POST /api/update applies the update and triggers os.Exit(0)
after 500ms grace period for the response
- updateService runs a check on Start() and every 6h
- broadcasts 'update_available' via WS on first detection
- updateApi client (get + apply) - updateStore state container - UpdateBanner component: gold-tinted row below the header with 'Update now' button + 'Release notes' link, listens to WS 'update_available' messages, surfaces apply errors
- update_web_banner: 'Update available: v{latest}'
- update_web_button: 'Update now'
- update_web_notes: 'Release notes'
- sync os.Remove(oldPath) instead of goroutine (os.Exit kills goroutines, the .old was never deleted) - clean tmpPath on rename rollback so a failed second rename doesn't leave ftm-update-*.tmp behind - handle http.NewRequest errors (fetch + download) instead of ignoring them and risking nil-deref on header sets
- GET /api/update now always returns the full UpdateInfo shape (empty strings when no info yet) so the frontend type matches every code path - update.svelte.ts converted from writable to runes to align with the other stores (providers/settings/theme)
Adds
ftm --updateandftm --checkflags--check: prints current/latest version and release URL--update: download and installs the new binaryAdd TUI and Web banner