@khotan/cli
v0.12.0
Published
Khotan CLI: a human- and agent-facing adapter over the Khotan /api/v1 surface.
Readme
Khotan CLI
@khotan/cli is the first-party command-line interface for the Khotan API. It
is a thin adapter over the public /api/v1 HTTP API, driven by the shared
capability catalog in @khotan/core. It adds nothing the API
cannot do — it makes the same operations ergonomic for humans and agents alike.
Install & run
The published CLI is a self-contained Node.js binary (no Bun required). Install or invoke it with standard npm tooling:
npx @khotan/cli --help # zero-install
npm i -g @khotan/cli # global `khotan` binary
# or: bun add -g @khotan/cli / pnpm add -g @khotan/cli
khotan --helpFrom this repository (contributors), run it from source on Bun:
bun run khotan -- --help # via the root script
bun packages/khotan-cli/src/bin/khotan.ts --help # directlyScaffold a workspace
khotan init writes Khotan agent integration into the current directory so your
own agents (Cursor, Devin, …) can drive setup. It is non-destructive and
re-runnable, and never writes secrets.
khotan init # Cursor: .cursor/rules/khotan.mdc
khotan init --client generic # khotan-agents.md
khotan init --force # regenerate managed assets, overwriting edits
khotan init --json # machine-readable summary on stdoutIt writes agent guidance for using the CLI. Authenticate separately with
khotan auth set-key or environment variables — never commit a key.
init also installs catalog-derived agent skills for using the CLI into
.cursor/skills/, .agents/skills/, and .claude/skills/ (regardless of
--client): an overview khotan skill plus one per command domain
(khotan-apps, khotan-databases, …). The skills are generated from the same
capability catalog that drives the commands and khotan help, so they can
never drift. They are managed assets: every khotan init run regenerates
them in place (no --force needed), so re-running init is how a workspace
picks up new CLI commands — like the databases branches group. Your
hand-authored rule/guide stays non-destructive (preserved unless --force).
Authentication & profiles
The CLI authenticates with an organization-scoped API key. Credentials are
stored in an owner-only file at ${XDG_CONFIG_HOME:-~/.config}/khotan/profiles.json
(0600, in a 0700 directory). You can keep several named profiles.
Store an existing API key
Create a key in the dashboard, then:
khotan auth set-key --api-url https://app.example.com --api-key khk_live_...
khotan whoami # verifies the key via GET /api/v1/meExchange credentials for a key
khotan login \
--api-url https://app.example.com \
--email [email protected] \
--password '••••••' \
--organization-id org_123login calls POST /api/v1/api-keys, stores the minted key once, and verifies
it. The key itself is never printed to stdout.
Profiles
khotan auth set-key --profile staging --api-url https://staging.example.com --api-key ...
khotan auth use staging # switch the active profile
khotan auth list # list profiles (never prints keys)
khotan apps list --profile staging # one-off overrideEnvironment variables (CI & agents)
Environment variables take precedence over stored profile values, so no on-disk state is needed in CI or sandboxes:
| Variable | Purpose |
| ----------------- | ----------------------------------------- |
| KHOTAN_API_URL | API origin (e.g. https://app.example.com) |
| KHOTAN_API_KEY | Organization-scoped API key |
| KHOTAN_PROFILE | Select a stored profile by name |
| KHOTAN_ORG_ID | Expected org id; the CLI asserts whoami.organizationId matches it and fails closed otherwise |
export KHOTAN_API_URL=https://app.example.com
export KHOTAN_API_KEY=khk_live_...
khotan apps list --json | jq '.apps[].id'Repo-local org isolation (env.khotan.local)
When an env.khotan.local file sits at (or above) the working directory, the CLI
auto-loads its KHOTAN_* values so every command is scoped to that repo's
organization — never a stray machine-global profile from another customer. The
file is authoritative: its values win over shell-exported KHOTAN_*, which win
over the stored profile, and CLI flags win over everything (flag >
env.khotan.local > shell env > profile).
# env.khotan.local (git-ignored; never commit secrets)
KHOTAN_API_URL='https://<customer>.khotan.com'
KHOTAN_API_KEY='<org-scoped key>'
KHOTAN_ORG_ID='<org id>'When KHOTAN_ORG_ID (or --assert-org <id>) is set, every operation verifies
whoami.organizationId matches before trusting output and fails closed on
mismatch (error [org_mismatch]). With no expected org id set, behavior is
unchanged.
Command surface
Commands are grouped by domain and derived from the capability catalog, so the
documented surface and the executable surface cannot drift. Run khotan help or
khotan <command> --help for details.
| Domain | Examples |
| --------- | -------- |
| Apps | apps list, apps get <id>, apps create --name web --env KEY=VALUE, apps delete <id>, apps redeploy <id> |
| App env | apps env list <id>, apps env set <id> <KEY> --value V, apps env reveal <id> <KEY>, apps env unset <id> <KEY>, apps env bulk-set <id> --var A=1 --var B=2 |
| Pipelines | same shape as apps under pipelines … |
| Databases | databases list, databases create --name db, databases connection <id>, databases rotate-credentials <id>, databases delete <id> |
| Files | files list, files get <id>, files upload <path>, files download <id> --output <path>, files download-url <id>, files preview-url <id>, files update <id> --name new.txt, files delete <id> |
| Folders | folders list, folders create --name docs, folders rename <id> --name new, folders delete <id> |
| Context | context list, context get <slug>, context raw <slug>, context create --title T --kind knowledge --content '...' --folder-id <id>, context update <slug> --expected-revision 3 --content '...' --folder-id <id>, context delete <slug> |
| Context folders | context folders list, context folders create --name docs --parent-id <id>, context folders update <id> --name new --parent-id <id>, context folders delete <id> --recursive |
| Context revisions | context revisions list <slug>, context revisions get <slug> <revision>, context revisions restore <slug> <revision> |
Output modes
- Human-readable tables/detail by default.
--jsonemits the API-shaped JSON to stdout.- Results go to stdout; diagnostics, prompts, and progress go to stderr,
so
khotan ... --json | jqstays clean.
Safety
Each capability has a safety class (read, write, destructive, secret):
- Destructive commands (
delete,env unset) prompt for confirmation in a terminal and refuse in non-interactive sessions unless you pass--yes. - Secret values (
env reveal,databases connection,databases rotate-credentials) are only available via explicit commands and are never included in list/table output.
File transfer
Uploads and downloads move bytes directly between your machine and object storage using presigned URLs:
khotan files upload ./report.pdf --folder-path /reports/2026
khotan files download file_123 --output ./report.pdfTo hand a file to a browser instead of saving it locally, mint a short-lived presigned URL and load it directly (no API key on the URL; expires in ~5 minutes):
khotan files preview-url file_123 # inline disposition, images and PDFs only
khotan files download-url file_123 # attachment disposition, any file typeSee Serving file attachments for the full preview/download flow and the receiver wiring guidance.
Catalog ↔ OpenAPI consistency
@khotan/core validates that every catalog operation maps to a real /api/v1
operation (method, path, operationId) using a snapshot of the OpenAPI document at
packages/khotan-core/src/catalog/__fixtures__/openapi.snapshot.json.
Because that snapshot is committed, the catalog can validate green against a stale contract. Guard against it by regenerating the live operation index in-process and diffing it against the snapshot (no running server needed):
bun run khotan:snapshot:check # exit 1 on any added/removed/renamed operationWire this into CI (it only needs build-minimum env) so a route added, removed, or renamed without updating the snapshot fails the build.
Regenerate the snapshot when the API surface changes:
curl -s "$KHOTAN_API_URL/api/v1/openapi.json" \
| jq '{openapi, info, paths: (.paths | map_values(map_values({operationId})))}' \
> packages/khotan-core/src/catalog/__fixtures__/openapi.snapshot.jsonDevelopment
bun run khotan:typecheck # typecheck both packages
bun run khotan:test # run all Khotan tests
bun run khotan -- <args> # run the CLI from source
bun run khotan:build # bundle the publishable dist/khotan.js (Node target)The build bundles @khotan/core into a single Node-executable ESM file at
packages/khotan-cli/dist/khotan.js. @khotan/core stays a private workspace
package; only the bundled @khotan/cli is published, so the published manifest
has no workspace:* dependencies.
Release
Versioning and publishing use changesets:
bun run changeset # author a changeset describing the change
bun run release:version # apply changesets: bump version + update changelog
bun run release:publish # build, then publish @khotan/cli to npm (public)prepublishOnly rebuilds the bundle, so the published bin always matches
source. Publishing requires the @khotan npm org and a publish token.
