feat(cli): add curl installer channel with checksum verification - #799
Conversation
Ship a hosted install script (hunk.dev/install.sh) that resolves the platform archive from GitHub releases, verifies it against a new SHA256SUMS release asset, and lays the binary and bundled skills out under ~/.hunk so skill resolution keeps working. Teach the update seam a curl install source: detected from the .hunk/bin executable path, version-checked against GitHub releases, and updated by re-running the installer with the target version pinned, so curl installs get the same self-update behavior as npm and Homebrew instead of a manual download. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Wrap the install script in a main function invoked on its last line so a truncated curl-to-sh stream dies on a syntax error instead of executing a prefix of the install, swap the bundled skills through renames so an existing install never has a window with no skills, exit explicitly on INT/TERM so an interrupted run cannot resume past its own cleanup, fall back to wget when hunk update re-runs the installer on a curl-less machine, and correct the docs and detection comment for custom-directory installs, which are not auto-detectable once the installing shell exits — the installer now says so and prints the re-run command instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Greptile SummaryThis PR adds a hosted macOS/Linux installer with release checksums and integrates that install source with update detection and
Confidence Score: 2/5This PR should not merge until failed installer downloads are reported correctly and installer interruption or custom path syntax can no longer leave users with a falsely successful or broken installation. The new update pipeline can report success without downloading anything, while the installer can lose the canonical skills tree during interruption and writes unescaped custom paths into shell startup files. Files Needing Attention: src/core/process/selfUpdate.ts and website/public/install.sh Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant H as hunk update
participant G as GitHub API
participant S as hunk.dev/install.sh
participant R as GitHub Release
participant F as Local Filesystem
U->>H: hunk update
H->>G: Resolve latest release
G-->>H: Version
H->>S: Download installer via curl/wget
S->>R: Download archive and SHA256SUMS
R-->>S: Release assets
S->>S: Verify checksum and extract
S->>F: Replace skills and binary
S->>F: Optionally update shell PATH
H-->>U: Report update result
Prompt To Fix All With AI### Issue 1
src/core/process/selfUpdate.ts:161-163
**Downloader failures report success**
When `curl` or `wget` cannot fetch the installer, the downstream `sh` receives empty input and exits successfully because the pipeline preserves only its status. `hunk update` consequently suppresses the downloader error and prints that the target version was installed even though the binary was unchanged.
### Issue 2
website/public/install.sh:300-304
**Interrupted swap removes skills**
When the installer is interrupted after moving `skills` to `skills.old` but before promoting `skills.new`, cleanup removes only the temporary download directory and never restores the old tree. The existing binary remains installed without its canonical skills directory, breaking bundled-skill resolution until another successful installation repairs it.
### Issue 3
website/public/install.sh:315
**Custom paths become shell syntax**
When `HUNK_INSTALL_DIR` contains shell-significant characters, the installer writes that value directly into executable POSIX or fish startup syntax. On the next shell startup the directory is expanded or parsed instead of treated literally, leaving Hunk off `PATH` and allowing command syntax embedded in the path to execute.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix(cli): harden curl installer against ..." | Re-trigger Greptile |
Download the installer to a file before executing it so a failed fetch surfaces its own error instead of feeding sh empty input and reporting a successful update, restore the parked skills tree from cleanup when an interrupted swap left it aside, and write the PATH line with a single-quoted directory so a custom install path containing shell-significant characters stays a literal path in shell startup files instead of becoming code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Match the exact line the installer writes instead of the raw directory, which no longer appears verbatim once quoting escapes it, so re-running the installer against a path containing shell-special characters cannot append duplicate PATH blocks. Also escape the dot in the vercel header route so it matches only the literal install.sh path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Exercise the real download, checksum, extract, skill-resolution, and PATH flow on ubuntu and macos whenever the script changes, weekly to catch release-asset drift, and on demand. The release version is resolved with an authenticated gh call so shared-runner API rate limits cannot fail runs the script did not cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
The install script is a product artifact with its own tests, CI, and release-pipeline contract, not site content, so it moves from website/public/ to the repository root where contributors and read-before-you-pipe users can find it. The website build stages it into the deploy output as its final step, keeping the served copy in lockstep with the same deploy, and the vercel ignore rule now includes it so script changes still trigger a site deploy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Move install-source detection, per-channel release lookup, and the hunk update execution out of core/process into a core/install module: they are one feature family about how the binary was installed and how it gets replaced, not about the process a run lives in, and they only landed in process because the startup update notice lived there. The notice stays in core/process as a startup-notice producer built on the app-state file and consumes core/install. Path-only move; no exported symbol changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Hunk gets a one-line installer:
curl -fsSL https://hunk.dev/install.sh | shIt figures out your platform (macOS/Linux, Intel/ARM, including Rosetta-translated shells), downloads the matching release archive, verifies it against the release's checksums, and puts everything under
~/.hunkwith the bin directory added to your PATH. After that,hunk updatekeeps it current — the same way it already does for npm and Homebrew installs.How it works
The script installs into
~/.hunk/bin, with the bundled agent skills next to the binary sohunk skill pathkeeps working. Pin a version withHUNK_VERSION=0.19.0(or pass it as an argument), pick a different directory withHUNK_INSTALL_DIR, and skip the shell-rc edit with--no-modify-path.hunk updaterecognizes a curl install by its~/.hunk/binlocation, checks GitHub releases for the newest version, and updates by re-running the installer with the version pinned — so there's exactly one code path that knows how to install Hunk, and it's the one users can read.Safety
Curl-pipe installers have a bad reputation, so this one is defensive about the classic failure modes:
mainfunction called on the last line; a cut-off stream dies on a syntax error instead of executing a prefix. (hunk updategoes further and downloads the script fully before executing it.)SHA256SUMSasset (attested like the archives), and the script refuses a mismatched or missing entry. Releases from before the asset existed install with a warning instead of a silent skip.$(...), or apostrophes in an install path can't turn into code at shell startup.Testing
scripts/install-sh.test.tschecks the script in the normal test suite: POSIX syntax, release-asset naming kept in sync with the publish workflow, the platform-detection matrix (with stubbeduname), and a guard that themain-on-last-line defense stays in place.hunk skill pathresolution for both default and custom directories.Worth knowing
hunk.dev/install.sh(and/install) go live when the website deploys after merge.SHA256SUMSstarts existing with the first release cut after this merges — until then installs print the "no checksums yet" warning by design.HUNK_INSTALL_DIRinstall can't be auto-detected byhunk updatelater (the env var is gone once your shell exits); the installer tells you to re-run it with the same directory instead.npm install -g hunkdiff.🤖 Generated with Claude Code
https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6