Skip to content

1.0.0 — 2026-07-14

Latest

Choose a tag to compare

@github-actions github-actions released this 14 Jul 06:12
54a506f

Release Notes

Halfhand 1.0.0. The first stable release. From here on the contracts in
STABILITY.md hold: CLI flags/exit codes, --json output, the on-disk
database, MSRV, and the hh-core Rust API change only additively (or via a
documented deprecation). A database written by v0.1.0-beta.1 opens and
reads correctly under 1.0 — proven by the permanent
hh/tests/migration_from_beta.rs against a committed beta-created fixture.

Highlights

  • Secret redaction (hh scan, hh redact): detect secrets in recorded
    sessions, report them without ever printing the value, and irreversibly
    redact in place — events and blobs rewritten, originals securely deleted,
    DB compacted. Exits 4 on findings so CI can gate on a clean scan.
  • Export / HTML replay (hh export --bundle|--html, hh import):
    portable .hh archives (manifest + events + referenced blobs, zstd), a
    self-contained single-file HTML replay page (hh replay --web), and hh import to bring a bundle back under a new id. Exports are always
    redacted by default
    ; opting out needs an interactive TTY confirmation.
  • Codex CLI, Gemini CLI, and Claude Desktop adapters in addition to
    Claude Code, each with a fuzz target that never panics on malformed input.
  • Cross-session FTS5 search (hh search): words, phrases, prefix *,
    AND/OR/NOT, with --agent/--kind/--since/--path filters and --json.
  • Windows support (build-only per SRS §2.2): CI builds on
    windows-latest every push; PTY recording on Windows remains best-effort.
  • Stability contract (STABILITY.md): the 1.0 promise for CLI, --json
    (schema frozen at 2), the on-disk DB (forward-only migrations),
    MSRV (1.75, minor-release bumps only), and hh-core semver (enforced by
    the cargo-semver-checks CI job).
  • Release pipeline via cargo-dist: per-target binaries (mac/linux
    gnu+musl, windows msvc), SHA-256 checksums, shell + powershell installers,
    a Homebrew formula, and keyless GitHub artifact attestations
    (gh attestation verify). hh completions <shell> and a generated hh.1
    man page ship in every archive.
  • Docs site (mdbook) on GitHub Pages, with a printable hand-run
    docs/manual-qa.md checklist and docs/1.0-definition-of-done.md mapping
    every release bar to its proof.

Added — 1.0 release-readiness

  • hh completions <shell>: prints a shell completion script (bash, zsh,
    fish, powershell, elvish) via clap_complete (FR-6.2 ergonomics). New
    docs/completions.md page; --help example per shell. The scripts and the
    hh.1 man page are also generated at build time by the new hh-dist
    workspace crate (just dist-assets) and shipped in release archives.
  • cargo-dist release pipeline: tag-triggered (v*) releases now produce
    per-target binaries (mac/linux gnu+musl, windows msvc) with the git sha
    embedded (HH_BUILD_SHA, NFR-8), SHA-256 checksums, a shell + powershell
    installer, keyless GitHub artifact attestations (verify with gh attestation verify), and an auto-pushed Homebrew formula to
    halfhand-org/homebrew-tap. Config lives in dist-workspace.toml; the
    workflow is generated by cargo dist generate. See CONTRIBUTING.md
    "Cutting a release".
  • mdbook docs site under docs/, published to GitHub Pages on release
    (and on push to main as a preview) via .github/workflows/docs.yml. The
    existing docs/*.md and docs/adr/*.md are rendered in place (no path
    moves, preserving the 93 in-repo references); new pages: Quickstart,
    Recording, Replay & Inspect, Adapters, FAQ, Stability, Security.
  • Repo hygiene: bug-report and feature-request issue templates (the bug
    form requires hh --version and hh stats output), a PR template encoding
    the CLAUDE.md done-definition, CODE_OF_CONDUCT.md (Contributor Covenant
    2.1), and CONTRIBUTING.md (dev setup, adapter fixture-capture guide, fuzz/
    bench instructions, release runbook).
  • hh-dist crate: a publish = false workspace crate that generates
    completion scripts and the man page from the single Cli definition, so
    cargo install halfhand still installs only hh.

Changed — 1.0 release-readiness

  • README rewritten for 1.0 (245 → 116 lines): hero, install matrix
    (cargo/brew/script), 30-second demo, the honest "faithful playback" framing,
    badges, and a short "why local-first" section. Depth moved to the docs site.
  • Release workflow replaced: the bespoke .github/workflows/release.yml
    is superseded by cargo-dist's generated workflow; re-run cargo dist generate to emit it from dist-workspace.toml.
  • just fuzz-all now runs all eight fuzz targets (was missing
    codex_jsonl, gemini_jsonl, import_bundle).

Added — 1.0 stability groundwork

  • STABILITY.md: the 1.0 promise for CLI flags/exit codes (additive-only,
    deprecate-before-remove), --json (schema frozen, additive-only, see
    below), the on-disk DB (forward-only automatic migrations; --json and
    hh export bundles — not raw SQL — are the supported programmatic
    interface), MSRV (bumps are minor releases only), and hh-core's Rust API
    (enforced today by the cargo-semver-checks CI job).
  • JSON schema bumped to schema:2, frozen for the 1.0 series: additive
    changes (new fields, new enum values, new body shapes) no longer bump the
    integer, reversing the beta-era policy. schema:2 folds in drift that
    shipped without a version bump in the previous entry below — three new
    agent_kind values and the redaction_audit lifecycle body — documented
    field-by-field with a diff from schema:1 in docs/json.md. A new
    hh-core::event::JSON_SCHEMA_VERSION constant is now the single source of
    truth every JSON-emitting call site (including hh-core::bundle, for hh export --bundle) reads from, replacing several independently hardcoded
    1s.
  • Docs/code reconciliation audit: README.md's command table was missing
    hh gc/hh stats/hh import/hh search and still called the Codex/
    Gemini/Claude Desktop adapters "planned for v0.2" after they'd shipped
    (see the entry below); docs/json.md's agent_kind table and
    docs/search.md's --agent filter list were missing claude-desktop (and
    docs/search.md also missing mcp-only); hh mcp-proxy --session-hint's
    --help text implied it was stored when it is in fact accepted but not
    yet persisted anywhere (now documented honestly in docs/mcp-proxy.md).
  • hh-core::deprecation::warn_deprecated: the standard stderr format for
    a deprecation warning (hh: warning: <what> is deprecated; <guidance>),
    deduplicated per unique warning id so a call site can never print more than
    once per invocation. Config::load's legacy halfhand.toml/hh.toml
    fallback notice now goes through it.
  • CLI conformance test (hh/tests/cli_conformance.rs): snapshot-tests
    hh --help and every hh <sub> --help, and asserts the documented exit
    codes — 2 usage error, 3 session not found, 4 hh scan with
    findings.

Added — Codex CLI, Gemini CLI, and Claude Desktop adapters; cross-session search (docs/search.md, docs/adr/0003-fts-index-strategy.md)

  • Codex CLI adapter: tails ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl,
    converting response_item (message, reasoning, function_call,
    custom_tool_call) and event_msg (user_message, agent_message,
    token_count, exec_command_end) records to the same structured event model
    as the Claude Code adapter (agent_kind = codex-cli).
  • Gemini CLI adapter: tails ~/.gemini/tmp/<hash>/chats/session-*.jsonl,
    converting user/gemini records (tool calls, thoughts, tokens)
    (agent_kind = gemini-cli).
  • Claude Desktop adapter: reuses the Claude Code JSONL tailer/parser
    (identical transcript format) under its own agent_kind = claude-desktop
    so hh list/hh inspect distinguish the two.
  • Force any of the four with hh run --adapter <claude-code|claude-desktop| codex-cli|gemini-cli> -- <command>; auto-detection matches on the wrapped
    command's basename same as before.
  • hh search <query> [--agent] [--kind] [--since] [--path] [--limit] [--json]: FTS5 full-text search across every recorded session's event
    summaries and message bodies, with highlighted snippets in the table view.
    Backed by a new events_fts virtual table (migration 0004, additive) kept
    incrementally in sync by the writer thread on every append, and maintained
    (rows removed) on hh delete and hh redact — a redacted secret is
    provably not findable via search (property-tested). --limit defaults to
    50, capped at 500.
  • Fuzz targets for the new Codex/Gemini JSONL parsers (fuzz/fuzz_targets/ codex_jsonl.rs, gemini_jsonl.rs) and a search benchmark (target <100ms
    across 100 sessions × 1000 events).
  • AgentKind marked #[non_exhaustive] (new variants append at the end, so
    existing discriminants don't shift); cargo-semver-checks CI excludes the
    one-time enum_marked_non_exhaustive lint for it.

Added — redaction pipeline (SRS §8, docs/redaction-design.md)

  • Secret detection engine (hh-core::redact): compiled detectors for
    AWS access key ids, GitHub/GitLab/Slack tokens, PEM private-key blocks
    (including truncated fragments), JWTs, a conservative high-entropy
    scanner, and user-defined regexes via the new config [redaction] rules.
    Matches are replaced with {{REDACTED:<type>:<hash8>}} where hash8 is
    a BLAKE3 prefix of the secret — one-way, but stable, so the same secret
    correlates across events without being stored. Contract (property-tested
    and fuzzed via the new redact_detect target): false positives are
    acceptable, false negatives on the named token types are bugs.
  • hh scan <session|last|--all> [--json]: reports detected secrets
    (type, step, location, hash8) without ever printing them. Exit code 4
    when findings exist — the documented redaction/policy exit code — so CI
    can gate on a clean store.
  • hh redact <session> [--yes]: irreversibly redacts a recorded
    session in place — rewrites events and affected blobs (refcounts move
    with the references), securely deletes originals (best-effort zero
    overwrite + unlink), appends a redaction_audit lifecycle event, and
    checkpoints + VACUUMs so no plaintext survives in the WAL or freelist.
    Property-tested: after redact, seeded secrets are undetectable anywhere
    in hh.db or the blob store.
  • hh export [session] [--out FILE] [--html] [--no-redact]: exports a
    session as a schema:1 JSON bundle or a self-contained HTML page.
    Redacted by default, always — the whole bundle passes through one
    redaction chokepoint before a byte is written. --no-redact requires an
    interactive typed confirmation and is refused on a non-TTY stdin (no
    bypass flag, by design).
  • Record-time redaction (opt-in, config [redaction] at_record = true):
    matches are scrubbed before hitting disk — event summaries/bodies on
    the writer task, blob content before hashing/compression.
  • New docs: docs/redaction.md (threat model, what is/isn't caught,
    config), docs/redaction-design.md (invariants), and a repo-root
    SECURITY.md with the disclosure policy.
  • New dependency: regex (linear-time matching — no catastrophic
    backtracking on hostile recorded input).

Added — portable session bundles, hh import, interactive HTML replay (docs/export-import.md)

  • hh export --bundle [-o FILE]: a portable, deterministic session
    archive — a zstd-compressed tar of manifest.json (format version, hh
    version, session metadata, integrity hashes) + events.ndjson (one
    compact JSON object per event, position-indexed since DB row ids don't
    survive import) + every referenced blob, content-addressed. Byte-identical
    on repeat export of an unchanged session (fixed entry order, fixed
    tar-header metadata, no wall-clock fields). Redacted by default through
    the same chokepoint as plain hh export/--html; if redaction changes a
    blob's hash, every reference to it in the bundle is rewritten to match.
    hh export <session> with no flags is unchanged (still plain JSON to
    stdout) — --bundle is additive, opt-in, and refuses to write its binary
    output to an interactive terminal without -o.
  • hh import FILE: validates a bundle end to end (bounded zstd
    decompression, a tar-entry allow-list, per-blob BLAKE3 verification, an
    events.ndjson integrity digest, and full referenced-blob-exists checks)
    before importing it under a brand-new local session id — the original id
    is preserved in the new sessions.imported_from column/JSON field
    (additive migration 0003). Property-tested: export → import → export
    round-trips every event's content and blob bytes exactly. Fuzzed (new
    import_bundle target): never panics on malformed/corrupt/hostile input.
  • hh export --html redesign: from a static "step report" into a real
    self-contained interactive replay page — a clickable step timeline, a
    detail pane (pretty JSON / a real diff viewer with precomputed hunks /
    plain text depending on event kind), j/k keyboard navigation mirroring
    the replay TUI, and a dark-by-default theme with a light toggle. Zero
    network requests, no CDN, no build step (hand-written vanilla JS). Session
    data — untrusted input, since it's whatever the recorded agent/tool output
    contained — is embedded once as escaped JSON inside a
    <script type="application/json"> tag and only ever reaches the DOM via
    createElement/textContent, never innerHTML, so a <script> tag or
    event-handler attribute anywhere in recorded content renders inert.
  • hh replay --web: sugar for hh export --html, writes the page to a
    temp file and prints the path. Needs no terminal (unlike the TUI); does
    not auto-open a browser or start a server.
  • New dependency: tar (the same crate Cargo itself uses for package
    tarballs — pure Rust, no network, no unsafe in the paths used here).

Diagnostics

  • A degraded Claude Code adapter session now self-documents in the database,
    not just on stderr: the specific degrade reason is persisted as an error
    event (body_json = {"adapter":"claude-code","reason":"…"}), so
    SELECT … WHERE kind='error' explains why a session recorded no structured
    steps — previously such a query returned empty and the failure was a silent
    mystery. Reasons are now specific: no jsonl matched cwd slug <slug> (with
    the slug dir it looked in + candidate counts), jsonl found (<file>) but 0 records parsed (read N line(s); first parse error at line K: <msg>),
    found a transcript at <file> but could not read it, or discovery selected file <file> but it's a directory. A found-but-empty transcript now degrades
    with 0 records parsed instead of finalizing active with 0 steps. Set
    HH_DEBUG=1 during hh run to capture a discovery+parse trace (computed
    slug, projects dir, candidate files, selected transcript, records read,
    events produced, first conversion failure) to stderr. Note: a degraded
    session now reports 1 step (the error event itself) rather than 0.

Fixed

  • Silent recording breakage (the reported "0 steps / 0 files but
    status=ok" symptom): the Claude Code adapter's transcript locator used a
    fixed 3 s deadline and matched the first cwd-bearing record, so on
    recent Claude versions — which write ~/.claude/projects/<slug>/*.jsonl
    lazily (only on first output) and omit cwd from the early system/meta
    records — the locator either timed out before the file appeared or rejected
    it on the cwd-less head, finalized ok with 0 structured steps, and printed
    no warning. The locator now polls until the session stops, snapshots
    pre-existing transcripts so it only matches one created during this
    session, and scans past cwd-less records to the first cwd-bearing one (and
    verifies it belongs to this session's cwd). Every degrade path now carries
    an actionable degrade_reason printed after the child exits (FR-1.5), and
    the hh run epilogue warns when an adapter-active claude-code session
    records 0 steps over >60 s.
  • A halfhand.toml (or hh.toml) config file is no longer silently ignored.
    When config.toml is absent but a legacy file exists in the same directory,
    hh now loads it as a fallback (so ignore globs / a custom data_dir take
    effect) and emits a single one-line deprecation hint on stderr suggesting the
    rename to config.toml. When config.toml is present, a sibling legacy
    file is still genuinely ignored (the canonical path wins) — hh warns on
    stderr at startup and hh doctor reports it as a failing check, naming the
    ignored file and where to move its contents.
  • The FS watcher no longer aborts hh run with "recording failed" when its
    cwd is unwatchable (e.g. notify rejects a recursive watch with EACCES).
    It degrades instead: a single stderr warning points at hh doctor, file
    recording is skipped, and the PTY + adapter session still records
    (status=ok). A per-directory fallback also keeps file recording working
    when one unreadable subdir would have blacked out the whole tree.
  • The FS watcher now drains in-flight events for a bounded window (150 ms of
    silence, capped at 1 s) after the child exits, before finalizing, and then
    runs a deterministic re-scan of cwd as a backstop. On macOS, FSEvents can
    deliver a write event after the process that performed it has already
    exited — or never deliver it at all on GitHub's macOS runners. The old
    behavior stopped the watcher the instant the child was gone and dropped that
    event, so a quick-exiting agent could finalize with "0 file changes" and
    hh inspect --diff reported "no file changes in this session" despite files
    being written. The grace drain catches late events; the re-scan backstop
    catches the absent-event case deterministically (it does not depend on
    notify delivering anything). At startup the watcher now hashes every
    capturable baseline file (storing only the hash, not a blob), so the backstop
    can also detect a missed modify of a pre-existing file (current hash ≠
    baseline hash) and record it as Modified — a backstopped modify renders
    with a missing before-side ("all added"), since the original content was
    overwritten before it was observed. Files already captured via the event
    path and unchanged baseline files are skipped, so neither mechanism
    duplicates a normal-path capture. The startup baseline is captured
    synchronously in spawn_watcher before it returns (on the caller's thread,
    not the worker), so any file written after the watch starts — by the recorded
    child or otherwise — is guaranteed post-baseline and is never misclassified
    as a pre-existing file the backstop would skip as "unchanged"; this also
    closes the scheduling-dependent window that made the macOS-CI capture test
    flaky. A prompt backend (Linux inotify) pays only the short quiet wait; the
    startup + shutdown baseline hashing is the trade-off for missed-modify
    coverage on large trees.
  • hh list column headers no longer drift out of alignment with the data
    when color is enabled: padding now measures visible width (ANSI escapes
    stripped) instead of byte length, so colored status cells no longer push
    every column to their right out of line. hh list also shows a marker on
    rows whose adapter ended degraded, so a silently-broken session is no
    longer indistinguishable from a clean one.
  • BlobStore::get / remove_if_unreferenced now reject a malformed hash
    (wrong length, non-hex, or non-ASCII bytes) instead of risking a byte-slice
    panic on a multi-byte UTF-8 character at offset 2, or a path-traversal write
    via a crafted hash string.
  • The FS watcher, adapter tailer, and MCP proxy threads share one writer
    Mutex; a panic in any one of them no longer poisons it for the rest of the
    session (every lock site now recovers from poisoning) or aborts hh run's
    finalize — a panicked adapter tailer now degrades the session
    (adapter_status=degraded, warning) exactly like the existing
    "no transcript found" path, instead of leaving it stuck at
    status=recording forever.
  • writer_run's PRAGMA foreign_keys = ON failure is now logged instead of
    silently swallowed (a silent failure here would disable FK enforcement for
    the rest of the session with no visible cause).
  • Bumped crossbeam-epoch 0.9.19 → 0.9.20 (RUSTSEC-2026-0204), pulled in
    transitively via ignore.

Performance

  • Ingest throughput: PRAGMA synchronous = NORMAL (NFR-1 / NFR-3). The store
    never set synchronous, so SQLite defaulted to FULL, fsyncing the WAL on
    every event commit. That met NFR-3's durability ("fsync on session finalize")
    and then some, but capped sustained ingest near ~300 events/s — far below
    NFR-1's ≥5,000/s. Store::open and the writer thread now set
    synchronous = NORMAL: in WAL mode this keeps ACID (no corruption on crash)
    but skips the per-commit fsync, fsyncing only at checkpoint. That matches the
    durability design NFR-3 actually names — finalize_session runs
    wal_checkpoint(TRUNCATE), which fsyncs, so a session is durable at finalize;
    SQLite's default wal_autocheckpoint (≈4 MiB) bounds the mid-session
    power-loss window to the last autocheckpoint. Sustained ingest is now ~1.5k
    events/s (≈5× over the per-event-fsync baseline); reaching NFR-1's 5k/s still
    needs statement caching + a batched writer (tracked as follow-up; the
    regression gate below protects the gain). No CLI/JSON/schema/env change.
  • hh inspect --json now streams a session as NDJSON through
    Store::for_each_event_detail (one EventDetail at a time) instead of
    collecting the whole session into RAM — constant memory for any session size
    (Area 2). A 100k-event session streams without materializing 100k rows.
  • The replay body cache (ReplayData) is verified bounded at its 50-entry
    capacity across unbounded scrolling (Area 2): a regression to an unbounded
    cache would fail get_evicts_lru_to_stay_bounded_at_capacity.

Added

  • hh doctor (read-only diagnostic): runs five health checks — data dir
    writability, PRAGMA integrity_check, config resolution + non-canonical
    config detection, Claude Code transcript discoverability for the cwd (with a
    parse-test of the newest transcript), and a notify watcher smoke test —
    printing one / line per check and exiting nonzero if any fail. --json
    emits a stable schema:1 object with a per-check array. Docs: see
    docs/doctor.md; --help carries an example.
  • Regression tests for the silent-breakage fix: an end-to-end file-change
    recording test, an adapter test against both Claude JSONL fixture
    generations (including the new-format transcript with mode /
    permissionMode / fileHistorySnapshot and no cwd on the early records),
    an integration test asserting the watcher-init-failure warning reaches
    stderr and the session still records ok (not "recording failed"), and an
    adapter degraded-warning-at-finalize test. hh list / hh doctor rendering
    is locked with insta snapshots.
  • cargo fuzz targets (fuzz/, nightly toolchain) for the four
    untrusted/external-input parsers: Claude JSONL transcript lines, MCP
    JSON-RPC framing, config.toml, and blob decompression. Seeded from
    tests/fixtures/; runs nightly in CI (60s/target,
    .github/workflows/fuzz-nightly.yml) and via just fuzz-all <seconds>
    locally.
  • proptest property tests: blob refcount/GC invariants (refcount tracks live
    references exactly; GC removes a blob iff unreferenced) across arbitrary
    create/append/delete sequences, step-assignment invariants (dense ordinals,
    exact call/result pairing, idempotent) across arbitrary event interleavings,
    and migration-reopen idempotency (byte-identical schema across N reopens).
  • Panic-injection tests proving a real panic on the FS watcher thread or the
    Claude adapter tailer thread degrades the session instead of aborting the
    recording.
  • CI: cargo-llvm-cov gate (hh-core ≥ 80% lines, fail-under; workspace-wide
    report-only) and cargo-semver-checks against main for hh-core's public
    API (adapters implement its Adapter trait).
  • Criterion benches (hh-core/benches/) + a nightly regression gate (Area 1 /
    NFR-1): four groups — ingest (append_event throughput), replay_index
    (10k/100k-event list_event_index), blob_write/blob_read (1 KiB/1 MiB),
    and adapter_jsonl (Claude JSONL conversion, gated on the fuzzing feature
    so it uses the dev-only parser entry point, off the semver-checked surface).
    .github/workflows/bench-nightly.yml runs cargo bench -p hh-core --features fuzzing nightly and fails on >15% regression vs the previous run's baseline
    (same runner class via an actions/cache rolling baseline — a committed
    baseline would false-positive across hardware). just bench /
    just bench-compare run the same locally. Dev-only: criterion is a
    dev-dependency (default-features off) and pulls no HTTP client, so NFR-2's
    no-network-crate check stays green. See docs/performance.md.
  • hh gc (reclaim space, Area 3): prunes orphaned blob files and stale
    blobs rows — the crash-leftover cases hh delete does not reach (a blob
    written to disk between BlobStore::put and the referencing event's commit,
    or a blobs row whose backing file was deleted out of band) — and VACUUMs
    hh.db to shrink it on disk (--no-vacuum skips the rebuild). Safe: it only
    removes blobs no live event references, never referenced data. --json emits
    a stable schema:1 object (orphan_files_removed / orphan_bytes_reclaimed
    / orphan_rows_removed / vacuumed). Docs: see docs/gc.md; --help
    carries an example.
  • hh stats (read-only store inventory, Area 3): session/event/blob counts,
    on-disk footprint (hh.db + WAL/SHM sidecars + compressed blob directory),
    and the largest sessions by event count (--top N, default 5). --json
    emits a stable schema:1 object with blobs / disk / largest_sessions
    sub-objects. Docs: see docs/stats.md; --help carries an example.
  • Storage schema migration 0002 (additive): a partial index
    idx_events_needs_heal on events(session_id) WHERE step IS NULL AND kind != 'terminal_output', making Store::open's step self-heal probe
    O(rows-needing-heal) instead of an O(all-events) scan on every invocation —
    including read-only hh list, whose cold start used to scale with total
    recorded history. Additive (a new index on an existing table); no breaking
    schema change, and the schema_version row is the only data write.

Install halfhand 1.0.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/halfhandorg/halfhand/releases/download/v1.0.0/halfhand-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/halfhandorg/halfhand/releases/download/v1.0.0/halfhand-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install halfhand-org/tap/halfhand

Download halfhand 1.0.0

File Platform Checksum
halfhand-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
halfhand-x86_64-apple-darwin.tar.xz Intel macOS checksum
halfhand-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
halfhand-x86_64-pc-windows-msvc.zip x64 Windows checksum
halfhand-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
halfhand-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum
halfhand-aarch64-unknown-linux-musl.tar.xz ARM64 MUSL Linux checksum
halfhand-x86_64-unknown-linux-musl.tar.xz x64 MUSL Linux checksum

Verifying GitHub Artifact Attestations

The artifacts in this release have attestations generated with GitHub Artifact Attestations. These can be verified by using the GitHub CLI:

gh attestation verify <file-path of downloaded artifact> --repo halfhandorg/halfhand

You can also download the attestation from GitHub and verify against that directly:

gh attestation verify <file-path of downloaded artifact> --bundle <file-path of downloaded attestation>