Repository navigation
v0.12.0
Added
-
krowk sync attachopens the TUI. On a terminal, a session another
machine runs is drawn as your own are: its history, then live. Prompts go
from the prompt box, approvals are answered in the usual dialog, and Esc
interrupts. The status line says whether the host is there (prompts wait
for it when it is not) and whether the session comes by the relay or
directly.krowk --resume <id>of a synced session opens it the same way.
The host runs prompts from a viewer in the session's default mode, so
/mode,/model,/newand/sessionssay they are the host's, and a
viewer allows a call once or for the session, never for the host's
project. With--json, or stdout not a terminal, it prints stream-json as
before. -
krowk sync initsets up your device list, with a 12-word recovery
kit. It asks you to sign in again in the browser, then makes the list's
first entry: this device and the recovery device the kit derives, with your
user key wrapped to both. The kit's words are shown once on stderr ([Enter]
when written down, [s] to skip) or written to--save FILE, 0600, and are
never typed back.--start-overmakes a new list; every other device on
the old one stops syncing and asks to be paired again. Run from a device
that holds your key, it keeps the old keys aside and seals the sessions it
can open again under the new list; running it again (under another
workspace's key, for that workspace's) goes through what is left, and
krowk sync recovery discard-olddrops the old keys when you say so. The 24-word recovery phrase is gone, and so is
krowk sync register: a device is registered by being on the list. -
krowk devices remove NAMEtakes a device off your list and rotates
your key away from it. It names every device the new key goes to before it
asks, needs a fresh sign-in, and removes any device revoked on the dashboard
in the same post, so no new key reaches one.krowk sync statusoffers to
finish a dashboard Revoke. Your other devices take the new key at their next
sync. -
krowk sync recovergets back in on a new machine from the kit's
words, typed at a prompt that doesn't echo them or piped in. It verifies
the list from its first entry, goes through every device on it with you to
keep or remove, and only then wraps your key, to what you kept. -
krowk sync recovery newreplaces the kit at once, with the old kit's
words, or from any device when you have none.krowk sync recovery checktests the words against the list, locally.krowk sync status
verifies the list against the head this device pinned, and says when there
is no kit; so does the TUI's status line. -
Pairing a device by a short code, in the library.
krowk_client::pairing
holds both sides ofkrowk devices addas sans-IO state machines: an
eight-character code (Crockford base32 less0and1, shownXXXX-XXXX,
typed in any case with spaces and dashes ignored), SPAKE2 in asymmetric mode
bound to the peer kind, the person and both device ids, and key confirmation
both ways before the new device's name is shown or anything is posted. One
failed step ends the pairing, and the new device's code is consumed by the
attempt, so a hostile registry gets one guess against each side. The SPAKE2 crate is held to magic-wormhole's vectors. Nothing
calls it yet. -
The client crypto for devices you own, not yet wired to any command:
a user key per person with generations, each wrapping the one before; a
12-word recovery kit that derives a recovery device; and a signed,
chained device list that every client verifies against the head it last
saw, refusing an older or forked list and any removal of the recovery
device. Only the current kit can replace the kit, or any device when there
is none. -
krowk devices addandkrowk sync joinpair a machine by a short
code.addshowsXXXX-XXXX, valid ten minutes and once;joinon the
new machine takes it at a prompt, never as an argument. Both machines check
the code, the paired one asksAdd '<name>' (<os>) to your devices? [Y/n],
and the new machine keeps the user key only once the chain it was sent
adds exactly its keys. A wrong code, or any failure on the new machine, a
dropped connection included, ends the pairing and asks for a new code. A
machine that was sent the key but never confirmed it is still listed, so
krowk devices removecan take it off; ^C onaddends the pairing. -
krowk sync hostseals under the device list as the registry has it
now, extended from this machine's pin, and refuses when the registry
cannot be asked, rather than sealing under a list a removal left behind. -
The stand-in registry holds devices you own.
krowk-devregistryserves
a person's signed device list (verified on every post by krowk-client's own
verifier), the user key wrapped to each device, keys bound to one device,
a fresh sign-in stamp, and the pairing mailbox: one live pairing per
person, ten minutes, ended by the first step out of turn. The device
approval endpoints answer410 sync_reset. -
Releases are signed, come with an SBOM, and are proven reproducible.
Each release carrieschecksums.txt.sigstore.json, a keyless Sigstore
signature over the checksums of every archive, bound to the release
workflow at that tag, and a CycloneDX SBOM of the full and lean builds,
each signed the same way. Before anything is published, the release builds
Linux x86-64 a second time on another runner from another path and stops
unless every binary matches byte for byte. The npm packages carry npm
provenance.SECURITY.mdsays how to verify a download and rebuild one. -
SECURITY.md: where to report a vulnerability (security@krowk.com),
what happens then, what is in scope, and every network call krowk makes
without being asked. krowk has no telemetry and no crash reporting. -
cargo denyon every pull request and at every tag. A dependency
with a RustSec advisory, a licence outsidedeny.toml's list, or a
source other than crates.io fails CI and stops a release. -
krowk sync attachanswers approvals and steers the turn. Besides
prompts, a line typed on its stdin can be/approve REQUEST_ID,
/allow-session REQUEST_IDor/deny REQUEST_ID, answering the tool call
anapproval.requestedline names,/interrupt, which stops the running
turn, or/steer TEXT, which adds to it. Eachapproval.requestedline
carries itsrequestId, and stderr says which command answers it. Any
other line starting with/goes as a prompt, so skills and paths still
work, and//TEXTsends/TEXT.krowk help sync attachlists them. -
A synced session goes direct over Tailscale when it can.
krowk sync hostreads this machine's tailnet address, MagicDNS name and LAN address
from the localtailscaled, listens there, and names the addresses to
each viewer inside the sealed channel. A viewer tries them all beside the
relay and moves onto the first that passes the same ticket, challenge and
handshake the relay does, mid-session and without losing a frame; if the
direct path goes, or its welcome is more than three seconds late, it
falls back to the relay by itself and tries again later, less often each
time nothing is found (10 seconds, doubling to 5 minutes).krowk sync attachprints the path in use (sync.path:relay,
direct over Tailscaleordirect over LAN). Being on the tailnet lets
nobody in: direct paths are on only with the registry's ticket keys in
KROWK_RELAY_TICKET_KEYS, andKROWK_TAILSCALE_SAME_USER=1also turns
away a connection from another tailnet user. The LAN address is offered
only withKROWK_DIRECT_LAN=1, and never with the same-user check.
krowk hostslists the
tailnet's machines taggedtag:krowk-host. -
A file a session publishes now says what made it. Besides the session
(krowk.session), each artifactpublishpushes records the engine that
ran the turn (krowk.engine:krowk,claude-codeor
codex-app-server), the model (gen_ai.request.model) and its provider
(gen_ai.system), so its card on krowk.com can link back to the session
with akrowk --resumecommand. A subagent's file names the subagent's
model. Nothing from the session's log is sent; the session id, engine,
model and provider are, in the clear, even for a synced session. -
A session running on one machine can be watched and steered from
another. The host keeps the session's lease, renewing it every 20
seconds, streams it over the relay sealed end to end, and writes its log
to the registry as sealed chunks with a checkpoint to attach from.
Another of your machines attaches from the latest checkpoint in well
under half a second, follows the session live, and can prompt it, steer
or stop it, and answer its approvals. When the host is away the other
machine is read-only, and a prompt typed there waits and runs when the
host is back. A dropped connection resumes by itself with nothing lost.
Neither the relay nor the registry ever sees the session's content.
krowk sync host <session>syncs a session of this machine,krowk sync sessionslists what another machine can open, andkrowk sync attach <session>— orkrowk --resume <session>on a machine without the
session's log — follows it there. The relay isKROWK_RELAY_URL, or
krowk relay serveon this machine. -
Sync calls that act as this machine are signed by its own key.
Registering or approving a device, a lease call, writing a session or
its log, and asking for a relay ticket now carryX-Krowk-Device,
X-Krowk-TimestampandX-Krowk-Signature, an Ed25519 signature by
this machine's signing key. The registry refuses them unsigned, signed by
another key, more than five minutes off its clock, or sent twice, so a
workspace API key alone can no longer act as one of its devices. A
clock more than five minutes out makes these calls fail with
signature_stale. -
A file a session publishes now says what made it. Besides the session
(krowk.session), each artifactpublishpushes records the engine that
ran the turn (krowk.engine:krowk,claude-codeor
codex-app-server), the model (gen_ai.request.model) and its provider
(gen_ai.system), so its card on krowk.com can link back to the session
with akrowk --resumecommand. A subagent's file names the subagent's
model. Nothing from the session's log is sent; the session id, engine,
model and provider are, in the clear, even for a synced session. -
Setting up sync now registers this machine's relay signing key.
krowk sync init,recoverandregister, andkrowk devices approve,
send the public half of the signing key beside the device key, andkrowk sync joinsends it with the approval request, so the approval registers
it. krowk's hosted relay uses it to tell this machine's connections from
anyone else's. The key is set once: a different one for the same device
is refused (signing_key_mismatch). -
The code
krowk sync joinshows to approve a machine changed. It
now covers both of the new machine's keys, so a request that copied its
device key with someone else's signing key shows a different code, and
krowk devices approveapproves only the one request whose code is
exactly the one you typed. Only one approval request per machine may
wait at a time. -
Relays admit devices on tickets the registry signs. A device joins
a relay with a short-lived ticket, which comes with the session's lease
for the host and on request for a viewer.krowk relay servenow takes
--ticket-keys FILE, the registry's ticket-signing public keys, instead
of--roster. The ticket travels in theX-Krowk-Ticketheader of the
WebSocket upgrade, and a connection without a good one is turned away
before it takes any room on the channel, so a flood of connections
cannot keep a session's devices off it. -
krowk relay serve --state DIRkeeps each channel's fence across
restarts. It is required when--addris reachable from the network,
like--origin, so a restart never lets a machine that lost the lease
host again. On loopback the relay may keep it in memory. -
A relay keeps development sessions apart from production ones.
A relay join now says which it is,env"production" or "development",
andkrowk relay servegives each its own channel of a session, so a
developer's session never shares a relay buffer or a viewer with a
user's. krowk says "development" for a debug build, for
KROWK_ENV=development, or for any registry butapi.krowk.com. -
krowk relay serveruns a relay of your own. It carries a synced
session between the machine running it and the devices watching it, and
only ever sealed bytes: it refuses anything not encrypted, and never
stores it. A device joins by signing the relay's challenge with a new
per-device signing key (signing.jsonin krowk's home, made the first
time it is needed); only the machine holding the session's lease may
host it. A device that reconnects picks up where it left off from the
relay's short buffer, and the relay answers heartbeats itself, so a busy
or quiet host never looks gone. It listens on 127.0.0.1:7790 unless
--addrsays otherwise; an address reachable from the network also
needs--origin, the URL devices dial it by, and the banner says it is
open. Nothing connects to it on its own yet; the host daemon and the
terminal will once syncing sessions lands.krowk help --alllists it. -
A synced session's log can now be stored, encrypted, in the registry.
The machine holding a session's lease seals each piece of the log on its
own side and uploads it straight to storage. Each piece is numbered,
names the lease it was written under, and is chained to the piece before
it, and the last one is marked. A device reading the log back refuses a
piece that is repeated, out of order, missing, or spliced in from another
machine's copy of the log, and can tell a finished log. A piece is
refused unless it comes with the lease's current token, and one a
previous lease holder left half-uploaded is replaced rather than blocking
the log. Pieces are at most 64 MiB. The registry and storage only ever hold the sealed bytes.
The pieces count on your workspace's storage meter, and never show up in
krowk uploads list, on a card, or anywhere else a push's artifacts do.
Nothing writes them yet on its own; the host daemon will once syncing
sessions lands. -
A device the workspace's owner revoked by resetting sync can come back.
Runkrowk sync registeron it after the reset. The fix line on a
device_revokedrefusal now says so, where it used to point at
krowk sync recover, which could not help. -
Add a machine to sync by approving it from one that already syncs,
with no recovery phrase. On the new machine,krowk sync joinshows a
32-character code and waits. On a machine that already syncs,krowk devices approveasks for that code, shows which machine it belongs to,
and hands the account key over, encrypted to the new machine's key, once
you say yes. The new machine then asks for the account key idapprove
printed (or takes it askrowk sync join <id>), asks you to confirm, and
keeps nothing unless the ids match. Read that id off your other machine,
never from an error message or a web page. Both commands need a person at
a terminal: an agent told to run them is refused.krowk devices list
shows the workspace's machines and the account key id this one holds.
All of it needs a key to a Pro workspace; on a free one the commands
refuse with a fix line.krowk sync initandrecoverstill work with
no account, on a free plan, or with the registry unreachable: the keys
stay on this machine, andkrowk sync registerregisters it later. With
a Pro key they register this machine themselves, andrecoverthen
refuses a phrase that restored a different account key from the
workspace's, which is how a mistyped word that happens to pass the
checksum is caught.--name(orKROWK_DEVICE_NAME) sets what the device
list calls this machine instead of its host name. -
krowk sync initsets up the end-to-end encryption keys sync will
use, and shows your recovery phrase. It makes a key for this machine
and an account key, prints the account key as 24 words, and keeps it
only once you type the words back (they are not echoed). Write them
down, with the key id shown beside them: krowk never stores the phrase
and cannot show it again, and it is the only way back to your sessions
if every device is lost. On another machine,krowk sync recovertakes
the 24 words — typed at its prompt, or piped from a file (< phrase.txt),
never withecho, which keeps them in your shell history — restores the
same account key there and shows its key id; if the id differs, a word
was wrong, and runningrecoveragain with the right words replaces it. The keys
stay in krowk's home,device.jsonandaccount-key.json, both
0600, and the wrapped account key opens only with that machine's own
device key. -
The host daemon can serve its sessions over a WebSocket on this
machine, the transport other devices will reach it through once the
relay lands. It is off unlessKROWK_HOST_WSorhost.websocketin
config.jsonnames a loopback address (127.0.0.1:7788; port0picks
one, andkrowk host statusshows it); anything but loopback is
refused. A client proves it is you with the token inhost.tokenbeside
the daemon's socket, which is new each time the daemon starts. The
frames are the unix socket's, sent in compressed batches. -
The TUI's sessions now run in the host daemon, so closing the
terminal no longer ends them. Barekrowkstarts the daemon if none is
running and runs every turn there. When the window closes mid-reply, the
turn keeps going. Reopenkrowkin the same directory and it tells you
which session is still running;krowk --resume <id>(or
/sessions <id>) then shows what happened while you were away and
follows the rest live. Several TUIs can follow one session and see the
same reply./connectupdates the daemon's providers too. Set
KROWK_TUI_HOST=localto keep the TUI's sessions in its own process, as
before.krowk host stopstops the daemon once no turn is running. -
A session can outlive its terminal:
krowk -p --daemonruns the turn
in a per-user host daemon. The firstkrowkthat needs the daemon
starts it in the background; it listens on a private unix socket
($XDG_RUNTIME_DIR/krowk/host.sock, or under$TMPDIRon macOS) and
exits after ten idle minutes with no session running —host.idleMinutes
inconfig.json, orKROWK_HOST_IDLEin seconds, changes that. Kill the
process or close the terminal mid-turn and the turn goes on;krowk host attach <session>shows it — what it has done so far, the reply being
typed, then the rest live — and any number of clients can follow one
session and see the same events.krowk host statussays whether the
daemon runs and what it holds. The daemon asks nobody whether to trust a
repository, so a backend runs there only once it is trusted. The TUI
still runs its sessions in its own process for now. -
krowk host enablekeeps the daemon running on an always-on machine,
as a systemd user service on Linux or a launchd agent on macOS, with no
idle exit;krowk host disablestops and removes it. A service has no
shell variables, so give it keys withkrowk connectrather than an
exportedANTHROPIC_API_KEY.
Changed
-
A synced session opens on one line, not the splash.
krowk sync attachdraws the session's history, then⇄ Attached to "<title>" on <host>(orAttached to <host>for an untitled session) — or that the
host is away and prompts wait — with the host named as the device list
names it. The status line says the host in
a glyph and its name:● <host>there,○ <host>away,◌ <host>
connecting. -
krowk sync attach --jsonends when its stdin does, once every
command sent has been answered. Before, it ran until interrupted. -
Nothing asks you to sign in again in the browser any more. Starting
over,krowk sync recovery new,krowk devices removeandkrowk sync recoveruse the key you're signed in with; a browser login opens only
when there is none, and still says what it is for. -
Sync's device and pairing calls are under
/v1/users/:user_id. The
device list, its append and start-over (…/devices,…/devices/reset),
a device's wrapped user key (…/devices/:id/key) and the person's one
pairing (…/pairing), for the user the key names. The payloads are the
same; the stand-in registry answers the new routes. -
Synced sessions are sealed under your user key.
krowk sync host
seals a new session's key under the newest user key generation this machine
holds, and records the generation in the wrapped key, which stays 74 bytes.
sync attach,sync sessionsand--resumeopen any older generation down
the chain of wraps. A machine holding only an older generation, such as one
removed before a rotation, can't open a newer session, and is told which
generation it would need. The user keys a machine holds live in
user-keys.json(0600, wrapped to its device key, replaced by rename),
and a save never drops an older generation's wrap it already holds.
Each session's record is signed by the machine that published it. Every
machine that opens a session —sync host,attach,sessionsand
--resume— checks that signature against your verified device list
(device-list.json), so any machine of yours can take a session up again,
and a record no device of yours signed opens nowhere. A session published
by a machine since removed from your devices still opens to read, but no
machine hosts it again: start a new one.
Sessions sealed under the account key no longer open: clean break. No
command puts a user key or a device list on a machine yet, so these
commands say it holds none until adding a device does. -
The client protocol's types live in
krowk-client. The commands,
events and log lines, and the daemon's 28-byte frame header, are
declared in the crate the desktop app and the phones will link, which
pulls in no engine, tokio or reqwest;krowk_harness::protocol
still names the same types. The generated JSON Schema is unchanged. -
Leaving the TUI takes two presses, as in Claude Code, so one stray key no longer ends a session. On an empty prompt, Ctrl-C or Ctrl-D shows "Press Ctrl-C again to exit" (or Ctrl-D) under the prompt, and the same key again within 1.5 seconds quits.
-
The TUI's prompt sits on the same band as your messages in the chat, edge to edge across the terminal with an empty row either side, instead of between two rules. The arrow and text stay where they were. The status line lines up with the arrow, with an empty row under it, and the working line reads "12s · esc to interrupt".
-
Keys the TUI suggests stand out: in hints, the help menu, the status line, approvals and questions, each key (
esc,enter,y,ctrl-g,?) is white instead of grey like the words around it.
Removed
krowk devices approveand the 32-hex device code. Pairing by a short
code replaces them;krowk sync jointakes no argument.
Fixed
-
A krowk session has one id.
krowk sessionslisted a krowk session
under an id of krowk.db's own, whichkrowk sync hostrefused as no
session. A session is now stored under its log's id, the onesync host
and--resumetake;sync hostalso takes the id an older store listed
it under (krowk sessions rebuildrelists those under their log's id). -
krowk sync initwarns about a skipped recovery kit once, where you
press [s], rather than again in the line after it. -
Sync reaches the hosted relay.
krowk sync hostandkrowk sync attachcould not dial awss://relay, so withKROWK_RELAY_URLset to
wss://relay.krowk.comthe host was never on it: a viewer replayed the
history but saw nothing live, and its prompts stayed queued. They now dial
it over TLS with the same trust as every other connection krowk makes, and
say on stderr why, once, when the relay cannot be joined. -
KROWK_RELAY_URLis no longer needed for krowk.com. Signed in to the
production registry,krowk sync hostandkrowk sync attachdial
wss://relay.krowk.comby default; a stand-in or customKROWK_API_URL
keeps the local relay, andKROWK_RELAY_URLstill overrides both. -
A host the relay let go joins again. When the relay dropped
krowk sync host's link without the close reaching it, or the host was stopped
or asleep longer than the relay keeps a silent link, the host stayed off
the relay with no error while viewers showed it gone. It now joins again
at once, and says why on stderr. -
A prompt sent as the host went away runs when it is back. A viewer
that sent a command the host never received, because its link was lost
just then, now sends it again when the host returns. The host runs it
once. -
/nameloads the skill on every agent, not only krowk's own. A skill
picked from the TUI's slash menu reached Claude Code or Codex as a bare
/implement, and a vendor that did not have that skill answered that there
was no such skill. krowk now loads the skill's instructions itself and sends
them ahead of your words, whichever agent runs the session. -
Skills installed with
npx skillsare found. krowk reads
~/.agents/skillsand.agents/skillsfrom the repository's root down to
the working directory, beside the.claude/skillsit already read, so a
skill in the shared directory is listed and can be used with/name. -
A viewer that moves to the direct path mid-session keeps receiving
the session. The direct listener replays from where the viewer was when
it started looking for the direct path. The viewer had already opened
those batches through the relay, so it never acked them, and once the
listener's window filled, no further event reached the viewer, though
its own prompts and their acks still went through. An approval request
waiting while the viewer moved is now shown once, not once per path. A
command still running when the viewer moved is answered on the path the
viewer is on now. A host that reconnects to the relay no longer drops the
viewers on its direct path. -
krowk sync hostno longer hangs on a session this machine does not
have. It used to take the session's lease and then wait, silent, until
interrupted; it now fails at once withno_sessionand how to start one,
before writing anything to the registry. A bridge that stops by itself
for any other reason also ends the command then, saying why. -
A turn sent the moment the host daemon starts no longer stalls every
other session. The daemon waited on its thread for the TLS setup a
turn's first request needs, and read a new directory's configuration
there too; both now happen off it, so streams and heartbeats keep going.
A TLS setup that fails once is tried again, rather than failing every
turn until the daemon restarts. -
A turn caught up after its client fell behind no longer loses its
answer. A terminal or phone that stopped reading, and was caught up
from where it stood just as the turn ended, could get the typing and the
end of the turn but not the finished answer. It now always gets it. -
A terminal whose connection to the host daemon drops mid-turn picks the
turn up where it left off. It reconnects and follows on from the last
thing it had, with nothing shown twice and nothing skipped, instead of
reporting the turn lost. A model switch caught up by another terminal
also no longer shows up twice. -
krowk host stopstraight after a turn keeps that turn on disk. The
daemon waits for the turn's log to be flushed before it exits, for up to
ten seconds. Stopped by SIGTERM or Ctrl-C, it first interrupts the
running turns, a turn still starting included, so each is logged as
interrupted and flushed too. The waits add up: up to ten seconds for the
turns to end, ten for each backend to close, and ten for the flush. A
second SIGTERM or Ctrl-C exits at once. A prompt sent while it exits is
refused rather than lost, an idle exit's included. -
A terminal suspended while it follows a long session no longer grows
the host daemon's memory. The daemon keeps a few megabytes for each
client that stops reading, then drops what it held and, once the client
reads again, sends what it missed from where it stopped — nothing twice,
nothing lost.krowk host status --jsoncounts the bytes waiting for
clients and how often one was caught up. -
A slow disk no longer stalls every session in the host daemon. Each
event a turn logs, and its context record, is written off the thread all
sessions and heartbeats share, and still before any client sees it. -
Quitting the TUI is instant. On a Claude Code model it took most of a second, waiting on Claude Code to flush its telemetry as it exited; krowk now starts Claude Code with telemetry off (an instance can turn it back on by setting
DISABLE_TELEMETRYin itsenv). The screen is also handed back before anything else is tidied up, and the session is saved intokrowk sessionsafter each turn instead of on the way out, so leaving has nothing left to wait on. A quit now takes about 30 ms, down from 0.9 s. -
A session resumed in the TUI shows what it has cost so far. On a model models.dev has no price for yet, such as a new Claude through Claude Code, the status bar showed
$—until the next turn ran; it now counts what the backend reported for each past turn, the way the live figure does.
Install
# npm — the bare `krowk` name is blocked by npm's typosquat filter
npx @krowk/cli push screenshot.png
# The installer: the full build on a workstation, the lean one in CI
# and containers (or with --lean)
curl -fsSL https://krowk.com/install | bash
# Direct download — no runtime, no package manager. krowk_… is the
# full build (the agent, its TUI, sessions); krowk-lean_… the lean
# agent-container build.
curl -sSfL https://github.com/krowkcom/krowk/releases/download/v0.12.0/krowk_0.12.0_$(uname -s | tr A-Z a-z)_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz \
| tar -xz -C /usr/local/bin krowk krowk-mcp
# From source
cargo install --locked --git https://github.com/krowkcom/krowk --tag v0.12.0 --features harness krowkchecksums.txt covers every archive on this release, and
checksums.txt.sigstore.json is its signature. To verify a
download (with cosign):
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity https://github.com/krowkcom/krowk/.github/workflows/release.yml@refs/tags/v0.12.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum --check --ignore-missing checksums.txtThe .cdx.json files are CycloneDX SBOMs of the full and lean
builds, each signed the same way. SECURITY.md in the repository says
how to rebuild a release and compare the bytes; this release's
reproduce job already did it once for Linux x86-64.