README
¶
Kei CLI
The Kei CLI manages Kei bot installation metadata. It authenticates operators, creates an installation for a bot platform, assigns agents, and reports the installation status. Runtime deployment and credentials are managed outside this CLI.
Install
Binary releases of kei-cli are available for macOS and Linux (arm64 and amd64). The installer downloads the matching archive and verifies its SHA-256 checksum.
Option 1: curl install from S3 (recommended)
The recommended public release endpoint is https://kei-cli-releases.s3.us-east-1.amazonaws.com; AWS_S3_RELEASES_URL_BASE can override it. The install script
detects your OS and architecture and defaults to /usr/local/bin:
# Install the latest version to a user-writable directory:
export PATH="$HOME/.local/bin:$PATH"
curl -fsSL "https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli/install.sh" \
| sh -s -- -d "$HOME/.local/bin"
# Install a specific version:
curl -fsSL "https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli/install.sh" \
| sh -s -- -v 0.2.0 -d "$HOME/.local/bin"
After installing, verify:
kei help
kei --version
Managing the installation
The installer records what it installs in a .kei-install-manifest file in the
install directory. Use it to install only the proxy or to remove the
installation:
# Install only kei-proxy (from the standalone kei-proxy release). The default
# version is the pinned KEI_PROXY_VERSION; override it with -v.
curl -fsSL "https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli/install.sh" \
| sh -s -- --proxy-only -d "$HOME/.local/bin"
# Remove exactly the files the installer recorded (kei, kei-proxy, and the
# manifest) from the install directory.
curl -fsSL "https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli/install.sh" \
| sh -s -- --uninstall -d "$HOME/.local/bin"
Option 2: go install (development use)
For development environments with Go 1.27+, you can build and install from the Go module:
go install github.com/HaikeiLabs/kei-cli/cmd/kei@latest
export PATH="$(go env GOPATH)/bin:$PATH"
Add the export line to ~/.zshrc (or your shell's startup file) to make it
permanent. For deployed binaries, use the S3 release installer above.
Upgrade a deployed CLI in place from the official S3 release endpoint (no Go installation required):
kei upgrade # latest stable release
kei upgrade --version 0.2.0 # pin a release (optional leading v accepted)
Upgrade independently resolves the latest published kei and kei-proxy releases,
downloads matching macOS/Linux and amd64/arm64 archives, verifies their SHA-256
checksums, then replaces kei and the sibling kei-proxy. --version pins only
the kei CLI; the proxy is still upgraded to its latest published version. Both
artifacts are fetched and verified before either binary is replaced. The
current executable's directory must be writable. To use a mirror or alternate
endpoint, set AWS_S3_RELEASES_URL_BASE; otherwise https://kei-cli-releases.s3.us-east-1.amazonaws.com
is used. GPG signature verification is not performed by this command; checksum
verification follows the official installer's required SHA-256 check.
Building from source
go build -o tmp/kei ./cmd/kei/
./tmp/kei help
Configure a local runtime
To run a local headless harness against an existing Kei runtime installation, run setup once:
kei setup
Setup prompts for the production control-plane URL, runtime installation token,
local harness URL, kei-proxy path, and optional model settings. It verifies the
token with Kei before saving the configuration to ~/.config/kei.yaml. The file
is created with 0600 permissions and the token is never printed. For scripted
or non-interactive setup, pass the token explicitly (be aware it may be saved
in shell history):
kei setup \
--control-plane-url https://app.haikeilabs.com \
--runtime-token RUNTIME_INSTALLATION_TOKEN
Verify the saved runtime installation and send its heartbeat through the local
kei-proxy binary:
kei runtime bootstrap
Use --config PATH if you need a separate configuration file. The runtime
token is used only by the local proxy; it is not sent to the model or written
to the repository.
Runtime service
The runtime can run as a background service that heartbeats independently of
any harness, using the runtime token in ~/.config/kei.yaml (not the kei
login token):
kei runtime service install
kei runtime service status
kei runtime service uninstall
kei runtime service install writes a LaunchAgent on macOS
(~/Library/LaunchAgents/com.haikeilabs.kei-runtime.plist) or a systemd
--user unit on Linux (~/.config/systemd/user/kei.service), then loads
and starts it. The service runs kei runtime heartbeat --config <path> on a
loop and restarts automatically if it exits. On macOS, logs go to
~/Library/Logs/kei-runtime.log.
kei runtime service status reports whether the service is installed and
running. An installation that has never sent a heartbeat reports as
pending in kei bot status until the first successful heartbeat arrives.
kei runtime service uninstall stops the service and removes the definition
file. No --yes flag is needed.
Login
The CLI uses Kei device authorization. It prints a browser URL and one-time verification code; complete the flow in your browser:
kei login
The approval page opens automatically. Use --no-browser in a headless
environment.
The login token is stored in the operating system credential store. It is not printed or written to the repository.
Logout
Remove the stored CLI token for a Kei environment:
kei logout
Logout only removes the local credential from the OS keychain; it does not revoke the token server-side. It is idempotent: running it while not logged in succeeds and reports that no session was stored.
Manage installations
List runtime installations for an organization (the CLI does not persist an org ID, so --org is required):
kei bot list --org ORGANIZATION_ID
kei bot list --org ORGANIZATION_ID --all
kei bot list --org ORGANIZATION_ID --json
The table includes installation ID, name, platform, status, workspace IDs, and relative last-heartbeat time. --all fetches all pages; otherwise the command fetches one page and indicates when more results are available. JSON output contains the safe installation items only.
Create pending installation metadata for a bot:
kei bot init \
--platform cli \
--name "Local OpenWebUI" \
--workspace "My Workspace"
Use --platform cli for a platform-neutral local runtime such as
kei-connector-runtime behind OpenWebUI. The existing teams, discord, and
slack values remain for chat-platform installations. The --workspace flag
is optional on init; when provided, the workspace ID or name is forwarded to
the installation record.
The command prints the installation ID. Use that ID to inspect the installation or manage its agent assignments:
kei bot status --installation INSTALLATION_ID
kei bot delete --installation INSTALLATION_ID --yes
kei bot agents list --installation INSTALLATION_ID
kei bot agents add --installation INSTALLATION_ID --agent AGENT_ID
kei bot agents add --installation INSTALLATION_ID --agent AGENT_ID --default
kei bot agents remove --installation INSTALLATION_ID --agent AGENT_ID
kei bot status includes the installation lifecycle fields above and, when the
server has reported it, a read-only policy_bundle snapshot with its state,
version, revision, digest, timestamps, and safe reason code. A null value means
no compatible health report is available; it does not change installation
status or binding_status. The CLI never prints bundle contents or runtime
credentials. Health projection availability depends on the runtime/catalog
producer rollout.
To create or rotate a runtime credential for a command pipeline, stdout must be redirected or piped; the CLI refuses to print a credential to an interactive terminal:
kei bot credential --installation INSTALLATION_ID --workspace WORKSPACE_ID | secret-manager import
kei bot credential --installation INSTALLATION_ID --workspace WORKSPACE_ID --rotate | secret-manager import
The --workspace flag is required and accepts a UUID or an exact workspace
name. When a name is given, the CLI resolves it to a UUID by calling the
workspaces API. The workspace value can also be set via the KEI_WORKSPACE_ID
environment variable.
List available workspaces:
kei workspaces list
kei workspaces list --json
The CLI always targets the Kei production API at https://app.haikeilabs.com.
Set the KEI_WEB_URL environment variable to override the endpoint for
development and testing.
Manage connectors
Connector setup renders from the kei.connector-setup/v1 schema in
kei-connector-contracts v0.3.0, the same definition the console uses. The
CLI offers the providers Kei has built connectors for: gmail,
google_drive, linear, github, tito, and crm.
# OAuth: pick the account model (per_user is the default).
kei connectors create --workspace Main --provider gmail --name team-mail
kei connectors create --workspace Main --provider google_drive --name shared-drive --account-model shared
kei connectors reconnect CONNECTOR_ID --workspace Main # opens the consent page for a shared account
# Shared secret: non-secret fields with --set; the secret is read without echo.
kei connectors create --workspace Main --provider tito --name events --set account_slug=acme
<secret-manager read> | kei connectors create --workspace Main --provider tito --name events --set account_slug=acme
kei connectors reconnect CONNECTOR_ID --workspace Main # replaces the secret (a new generation)
kei connectors list --workspace Main
kei connectors get CONNECTOR_ID --workspace Main
kei connectors delete CONNECTOR_ID --workspace Main --yes # revokes the connector
Missing fields are prompted for, with the schema's labels and defaults. A
secret field is never accepted as a flag: it is read from the terminal without
echo, or from stdin. The CLI seals it to the workspace's runtime
credential-sync keys (the envelope the console uses for model-profile keys),
and only the sealed copies are sent. The runtime stores the secret in the
tenant secret manager, and the connector becomes connected when it does. To
use a secret that is already in your secret manager, pass --credential-ref REF instead.
Manage model profiles and the credential store
Model profiles and the credential store are organization resources in the
Kei API (/api/v1/organizations/{org}/...). The organization is the one your
kei login token is bound to. Pass --workspace (a name or ID) to work with a
workspace's profiles; omit it for organization-level profiles. Profile
arguments accept an ID or a display name; --agent takes an agent ID. Writes
require an organization admin.
# Configure the credential store once (PUT creates it), then change it (PATCH).
kei credential-store put --secret-backend aws-secrets-manager \
--backend-config '{"region":"us-east-1","account_id":"123456789012"}' --secret-name-prefix kei
kei credential-store update --secret-name-prefix kei-prod
kei credential-store get
# A profile with no credential.
kei model-profiles create --workspace Main --display-name local --endpoint https://llm.internal/v1 \
--default-model llama-3 --auth-type none --workspace-default
# An API-key profile: the key is read from stdin (or typed without echo).
<secret-manager read> | kei model-profiles create --workspace Main --agent AGENT_ID \
--display-name openai --endpoint https://api.openai.com/v1 --default-model gpt-4.1 --auth-type api_key
<secret-manager read> | kei model-profiles update openai --workspace Main --rotate-key
kei model-profiles list [--workspace Main] [--json]
kei model-profiles get openai --workspace Main
kei model-profiles update openai --workspace Main --default-model gpt-4.1-mini
kei model-profiles set-default PROFILE # organization default (org-level profile)
kei model-profiles set-default openai --workspace Main # workspace default
kei model-profiles assign openai --workspace Main --agent AGENT_ID
kei model-profiles assignment --workspace Main --agent AGENT_ID
kei model-profiles unassign --workspace Main --agent AGENT_ID
kei model-profiles readiness openai --workspace Main # can runtimes reach the provider?
kei model-profiles test --endpoint https://api.openai.com/v1 # validate an endpoint, offline
kei model-profiles test openai --workspace Main # validate a stored profile's endpoint
kei model-profiles delete openai --workspace Main --yes
kei model-profiles list displays NAME, ID, MODEL, AUTH, STATUS, DEFAULT,
and ENDPOINT in a table. Revoked profiles remain visible and are marked in
STATUS. Use --json to print the API response as JSON.
An API key is never accepted as a flag and is never printed. The CLI seals it to the credential-sync keys of the runtimes in the profile's scope (the agent's runtimes, or the workspace's for a workspace default) with the same KMP1 envelope used for connector secrets; only the sealed copies are sent.
Create and update validate --endpoint locally with the same rule as the
console and API, before making a request. Invalid endpoints print the specific
failed rule and exit 1.
kei model-profiles test does what the console's model-profile Test
button does: it checks the endpoint and does not contact the provider. The
endpoint must be an https URL with a host and no embedded credentials, query,
or fragment (the console's and the API's rules; the CLI always applies the
production rule, so http is rejected). On success it prints
Configuration is valid; provider credentials will be verified by credential sync. and exits 0; otherwise it names the failed rule and exits 1, without
printing the URL. --endpoint makes no network request and needs no login;
test PROFILE reads the stored profile and checks its endpoint. To see whether
runtimes can actually reach the provider, use readiness.
Rotating a runtime credential into AWS Secrets Manager
When a runtime's Kubernetes secret is owned by an ExternalSecret that syncs from AWS Secrets Manager, the authoritative store is AWS. Rotate the credential and update the AWS secret in one pipeline:
kei bot credential --installation INSTALLATION_ID --rotate \
| ./scripts/update-aws-secret.sh kei-discord kei_runtime_token
scripts/update-aws-secret.sh reads the token only from stdin, updates
exactly one JSON property (default kei_runtime_token) of the secret's
SecretString with jq --rawfile, and calls aws secretsmanager put-secret-value using file:// input. The token is never placed in argv,
stdout, stderr, a temp filename, or shell history, and all other JSON fields are
preserved. The second argument is the property name; omit it to use the default
kei_runtime_token.
Preflight first — before you rotate. The rotation happens in the CLI before the script runs, so a failed destination check cannot prevent the rotation. If the write fails, the one-time token is consumed and you must rotate again. Verify the destination is healthy first:
# 1. Confirm the AWS secret exists and is a JSON object (do this BEFORE rotating).
aws secretsmanager get-secret-value --secret-id kei-discord \
--query SecretString --output text | jq -e 'type == "object"'
# 2. Confirm you can write to it.
aws secretsmanager describe-secret --secret-id kei-discord
The script prints only Updated property 'kei_runtime_token' on secret 'kei-discord'. on success. After the AWS secret is updated, the ExternalSecret
syncs it to the cluster; restart the runtime pod if it does not pick up the new
value automatically.
Key naming
The runtime reads the KEI_RUNTIME_TOKEN environment variable. In the
Discord/AWS deployment the token is stored in the AWS secret's lowercase
kei_runtime_token JSON property, which the ExternalSecret maps onto the
Kubernetes secret key the deployment consumes. Pass that property name as the
script's second argument (it is the default).
Release process
Releases are built and published by .github/workflows/release.yaml. For
the step-by-step runbook (prerequisites, watching a run, and what to do when
a run is skipped or fails), see docs/release.md.
How a release runs
The workflow has three jobs:
- Authorize manual release. Runs only for
workflow_dispatch. It fails unless the person dispatching hasadminpermission on the repository (a repository or organization owner). It is skipped on tag pushes. - Validate tag. Resolves the release tag and rejects anything that is
not
vMAJOR.MINOR.PATCH[-prerelease]. It sets the channel:prereleasefor tags containing-rc.,-alpha., or-beta., andstableotherwise. On a dispatch it also fails if the calculated tag already exists. - Release. Runs only when Validate tag succeeded. On a dispatch it
first creates and pushes the calculated tag. It then imports the GPG
signing key, checks the runner's AWS identity and bucket access, confirms
the pinned kei-proxy archive exists, and runs GoReleaser to build, sign,
and upload the artifacts. After that it uploads
install.sh, checks the published checksums and installer for leaked credentials, and, for stable releases only, writeslatest.txt.
Validate tag and Release run on the self-hosted kei-cli-release runner. The
runner Pod gets its AWS identity from IRSA, a dedicated IAM role for the
release service account. No AWS credentials, OIDC role ARN, or
configure-aws-credentials step appear in the repository or the workflow.
Cutting a release
Tag push. Tag a commit on main and push the tag:
git fetch origin
git tag vX.Y.Z origin/main
git push origin vX.Y.Z
Before #44, tag pushes silently skipped the Release job. Since #44, a tag push releases.
Manual dispatch. Let the workflow calculate the next version from the
highest stable vX.Y.Z tag, then create and push that tag itself:
gh workflow run release.yaml -R HaikeiLabs/kei-cli -f bump=patch # or minor, major
bump defaults to patch. A dispatch requires a repository or organization
owner. For anyone else, Authorize manual release fails and nothing is
released. A dispatch releases the head of the ref it is started on, which is
normally main.
The release environment
The Release job runs in the release GitHub environment. The environment
holds the GORELEASER_SIGNING_KEY secret, the ASCII-armored GPG private key
for kei-cli-releases@haikeilabs.com. The environment has no required
reviewers and no branch policy. The owner check for a dispatch lives in the
workflow, not in the environment.
Optional repository variables. None are set today, so the defaults apply:
| Variable | Default | Purpose |
|---|---|---|
AWS_REGION |
us-east-1 |
Region of the releases bucket |
AWS_S3_RELEASES_BUCKET |
kei-cli-releases |
Releases bucket |
The kei-proxy version is pinned by the KEI_PROXY_VERSION file at the repo
root — see the Bundled kei-proxy section below.
What gets published
Everything goes to s3://kei-cli-releases/kei-cli/. The public URL is
https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli/. Versioned
paths use the version without the leading v:
kei-cli/
install.sh curl installer; overwritten every release, no-cache
latest.txt latest stable version, e.g. 0.1.6; stable releases only
<version>/
kei-cli_<version>_macOS_arm64.tar.gz
kei-cli_<version>_macOS_x86_64.tar.gz
kei-cli_<version>_Linux_arm64.tar.gz
kei-cli_<version>_Linux_x86_64.tar.gz
kei-cli_<version>_checksums.txt SHA-256 of every artifact
kei-cli_<version>_checksums.txt.sig armored GPG signature of the checksums
kei-cli_<version>_source.tar.gz
Each archive contains kei, LICENSE, README.md, and the pinned kei-proxy
for the same platform. A prerelease publishes its <version>/ directory and
install.sh, but leaves latest.txt alone. Install a prerelease with
-v <version>. GitHub Releases are disabled; S3 is the only distribution
channel. Public reads come from the bucket policy, not object ACLs.
Verifying a release
BASE=https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli
curl -fsSL "$BASE/latest.txt" # stable releases: prints the new version
V=0.1.6 # the version you released
A="kei-cli_${V}_macOS_arm64.tar.gz"
curl -fsSLO "$BASE/$V/$A"
curl -fsSLO "$BASE/$V/kei-cli_${V}_checksums.txt"
shasum -a 256 -c --ignore-missing "kei-cli_${V}_checksums.txt" # expect: <archive>: OK
On Linux, use sha256sum -c --ignore-missing instead. A fresh install with
the curl installer is also an end-to-end check, because it verifies the same
checksum. See docs/release.md for the
signature check.
Upgrading from 0.1.5 or earlier (one time)
kei upgrade in 0.1.5 and earlier runs
go install github.com/HaikeiLabs/kei-cli@<version>. The entrypoint moved to
cmd/kei in 0.1.6 (#38), so that command no longer builds. Reinstall once,
using either method:
curl -fsSL "https://kei-cli-releases.s3.us-east-1.amazonaws.com/kei-cli/install.sh" \
| sh -s -- -d "$HOME/.local/bin"
The old go install path produced a binary named kei-cli, so after
reinstalling you can remove any leftover $(go env GOPATH)/bin/kei-cli.
From 0.1.6 onward, kei upgrade [--version VERSION] upgrades from S3 and
replaces the running executable after checksum verification.
Bundled kei-proxy
Each CLI archive bundles the kei-proxy binary for its platform, pinned by
the KEI_PROXY_VERSION file at the repo root. During the release,
scripts/fetch-proxy.sh reads the pin from that file and downloads
s3://kei-cli-releases/kei-proxy/<version>/kei-proxy_<version>_<OS>_<ARCH>.tar.gz
using the runner's IRSA identity. <OS> is macOS or Linux, <ARCH> is
x86_64 or arm64, and <version> has no leading v, even when the pin
does. The Release job fails early if that proxy release does not exist, and
again if the fetched binary's --version does not match the pin. A CI
workflow (verify-proxy-pin.yaml) checks the same on every PR so the pin
cannot drift unnoticed.
To bump the pin when a new kei-proxy is released, edit the
KEI_PROXY_VERSION file at the repo root and change the version string
(one line, vX.Y.Z). Commit and push — the PR CI verifies the new pin is
valid and the artifact exists.
The installer installs kei-proxy when the archive contains it. Older
archives without it still install normally.
For a local test build with no upload and no signing:
AWS_S3_RELEASES_URL_BASE=https://kei-cli-releases.s3.us-east-1.amazonaws.com \
AWS_S3_RELEASES_BUCKET=kei-cli-releases AWS_S3_RELEASES_REGION=us-east-1 \
GORELEASER_SKIP_SIGN=1 make snapshot
Artifacts land in dist/. Run make clean to remove them.
Scope
The CLI currently supports login and logout, installation metadata, agent assignment, installation status, and self-upgrade for the Teams, Discord, and Slack platform identifiers. Runtime deployment workflows are intentionally outside the CLI's scope.