Kit's source and binary releases are public at
speakeasy-api/kit.
GitHub Actions builds and publishes release artifacts on demand.
Ordinary pull requests do not update release versions. In GitHub, open
Actions > release, select Run workflow on main, and run it. Do not create
or push the release tag first.
The workflow verifies that the version in Cargo.toml matches the latest release
tag, then examines commits since that tag. Any breaking commit advances the minor
version; other commits advance the patch version. It prepares a release commit that
updates Cargo.toml and Cargo.lock, then runs
formatting, Clippy, tests, artifact builds, and release-note generation against that
exact commit.
After every required build succeeds, the workflow atomically advances main to the
release commit and creates the release tag. If main advances while the workflow is
running, the release stops without updating either ref and must be run again. A
rerun of the failed jobs resumes publication when the release commit and tag were
pushed but a later publishing step failed.
The atomic push authenticates with the repository's write-enabled kit-release
deploy key. Add Deploy keys to the main ruleset bypass list and store that
key's private half in the repository Actions secret RELEASE_DEPLOY_KEY. The
default GITHUB_TOKEN cannot bypass the pull-request and status-check rules.
The newly built Linux Kit CLI reviews every change since the previous release and
writes the GitHub release notes before the workflow attaches the archives and
checksums. Configure the repository Actions secret OPENROUTER_API_KEY for this
step. Kit uses the release-notes skill with the openai/gpt-5.6-terra OpenRouter model.
After the GitHub release and container images are published, announce-release
runs the newly released Linux CLI with the announce-kit-release skill. It uses
the same RELEASE_NOTES_MODEL setting and OPENROUTER_API_KEY secret as the
release-note generator. Kit posts directly to the configured Slack channel ID
through MCP tools, checks the posted message returned by Slack, and retrieves its
permalink. It does not search for the channel, so renaming it does not break the
announcement destination.
This exercises skill activation, MCP discovery and tool calling, and static HTTP
header authentication. The bot does not need channel-history access: the skill's
restricted-bot path verifies the actual message, channel, timestamp, and formatting
in the send response instead of fetching channel history. Verification is performed
by the agent, not an independent Slack API assertion. The job fails if configuration
is missing, Kit fails, or its final response is not a single Slack message permalink.
The permalink is written to the job summary.
Configure these repository Actions values before releasing:
| Kind | Name | Value |
|---|---|---|
| Variable | SLACK_CHANNEL_ID |
Stable Slack channel ID, not its name. Resolve it once during setup; releases use the ID directly. |
| Variable | SLACK_MCP_URL |
Trusted HTTPS Gram MCP endpoint exposing the built-in Slack tools and accepting consumer API keys. |
| Variable | GRAM_ENVIRONMENT |
Slug of the environment in the MCP's project containing the Slack bot token. |
| Secret | GRAM_API_KEY |
Gram API key with consumer scope authorized for the MCP's project. |
Store the raw Slack bot token as the secret SLACK_BOT_TOKEN in the selected Gram
environment, not in the workflow's MCP authentication header. If the MCP has
variable-provisioning controls, allow this variable from the request-selected
configuration (User), rather than filtering it to an attached System environment.
Kit sends the Gram key as Authorization: Bearer <key> and selects the environment
with Gram-Environment; Gram uses the Slack token for the upstream API calls.
An issuer-gated endpoint that accepts only OAuth user sessions is not sufficient.
The MCP must expose posting with the actual posted message in its response and
permalink retrieval. Invite the bot to the target channel and grant chat:write.
The bot must be a channel member with this scope; MCP authentication and channel
lookup can succeed even when posting would fail with not_in_channel.
The announcement does not use channel lookup, message search, or channel-history
tools. The final permalink must refer to the configured channel ID. Use only a trusted MCP endpoint: it
receives the Gram API key.
The workflow writes a private temporary MCP JSON file with an explicit
headers.Authorization and headers.Gram-Environment, passes it via --mcp-config, and removes it on
exit. The captured CLI response uses the same temporary directory. On failure,
the job logs the CLI exit code and up to 8 KiB of its final response (without the
session ID) to explain which step failed. It does not upload the config or Kit
session as an artifact.
An announcement failure marks the workflow failed but does not roll back the already published release. Before rerunning the announcement job, check Slack: if posting succeeded but verification failed, a rerun can produce a duplicate.
The release workflow signs the standalone Mach-O executable with the code-signing
identifier com.speakeasy.kit. It also archives Kit.app, signs its bundled helper
first with com.speakeasy.kit, and then signs the outer app with
com.speakeasy.kit.desktop. Both signatures enable the Hardened Runtime and use
an explicit empty entitlement set: the unsandboxed app and CLI need no Hardened
Runtime exceptions. The workflow submits both products to Apple's notary service,
staples and validates the app ticket, verifies nested signatures, and runs Gatekeeper
assessment before packaging Kit-vVERSION-aarch64-apple-darwin.zip. This Developer
ID distribution does not require an Apple App ID or provisioning profile.
Create the credentials as follows:
-
In Keychain Access, use Certificate Assistant > Request a Certificate From a Certificate Authority to save a certificate signing request (CSR).
-
In Apple Developer Certificates, Identifiers & Profiles, create a Developer ID Application certificate from that CSR. If that option is not available for your role, ask the team's Account Holder to create it. Import the downloaded certificate on the Mac that created the CSR.
-
In Keychain Access, export the Developer ID certificate together with its private key as a password-protected PKCS#12 (
.p12) file. Record the exact identity shown bysecurity find-identity -v -p codesigning. It normally has the formDeveloper ID Application: <Organization> (<TEAM_ID>). -
In App Store Connect Users and Access > Integrations, create a team API key that can access the notary service. Record its key ID and issuer ID, and retain the downloaded
.p8; Apple does not allow it to be downloaded again. -
Store the
.p12, its password, and the.p8in the company's secret manager. Configure these repository Actions secrets:MACOS_CERTIFICATE_P12_BASE64: base64-encoded.p12MACOS_CERTIFICATE_PASSWORD:.p12export passwordMACOS_SIGNING_IDENTITY: exact Keychain identity from step 3APPLE_API_KEY_P8_BASE64: base64-encoded.p8APPLE_API_KEY_ID: App Store Connect API key IDAPPLE_API_ISSUER_ID: App Store Connect issuer ID
For example, from a trusted Mac authenticated to GitHub CLI:
repo=speakeasy-api/kit
base64 < DeveloperIDApplication.p12 | gh secret set MACOS_CERTIFICATE_P12_BASE64 -R "$repo"
read -r -s 'p12_password?P12 password: '; echo
printf %s "$p12_password" | gh secret set MACOS_CERTIFICATE_PASSWORD -R "$repo"
unset p12_password
printf %s 'Developer ID Application: Example Corp (TEAMID)' | gh secret set MACOS_SIGNING_IDENTITY -R "$repo"
base64 < AuthKey_KEYID.p8 | gh secret set APPLE_API_KEY_P8_BASE64 -R "$repo"
printf %s 'KEYID' | gh secret set APPLE_API_KEY_ID -R "$repo"
printf %s 'issuer-uuid' | gh secret set APPLE_API_ISSUER_ID -R "$repo"The workflow fails on a partial configuration rather than silently publishing an unsigned build. With none of these secrets configured, it still publishes the standalone archive and Kit.app ZIP unsigned and unnotarized, and identifies the release as such in the release notes. XcodeGen 2.45.4 is downloaded with a pinned SHA-256 before the app archive is generated; the checked-in project is independently regenerated and drift-checked in CI.
Install a specific published version from a clean environment:
mise use github:speakeasy-api/kit@0.1.83
kit --version