kc is the command-line interface (CLI) for Kvindo Cloud — a
kubectl-style client to manage cloud infrastructure (VMs, S3 object storage, Kubernetes, load
balancers, VPCs, VPNs, managed PostgreSQL) from the terminal. It talks to the
same API as the web console, so anything you can see or change in the console you can also script
with kc. It is modelled on kubectl: you read resources with kc get and create/update them
declaratively with kc apply -f <manifest>.
How it works (good to know):
kcis a thin client. It sends your command to the server and prints the result — all logic runs server-side. This means the CLI never needs upgrading to gain new server features, and the output is identical to the API.
Using Claude Code? This repo ships a Claude Code skill that teaches Claude how to manage your Kvindo Cloud resources through
kc— clone this repo (or otherwise have it on disk) and open Claude Code there to pick it up automatically.
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/Kvindo/kc-cli/main/install.sh | shWindows (PowerShell):
irm https://raw.githubusercontent.com/Kvindo/kc-cli/main/install.ps1 | iexThese download the right binary for your OS/architecture from the latest release and install it
(/usr/local/bin on Linux/macOS, %LOCALAPPDATA%\kc on Windows; override with KC_INSTALL_DIR).
Grab the binary for your platform from the latest release:
| Platform | Asset |
|---|---|
| Linux x86-64 | kc-linux-amd64 |
| Linux ARM64 | kc-linux-arm64 |
| macOS Intel | kc-darwin-amd64 |
| macOS Apple Silicon | kc-darwin-arm64 |
| Windows x86-64 | kc-windows-amd64.exe |
| Windows ARM64 | kc-windows-arm64.exe |
Rename it to kc (or kc.exe), make it executable, and put it on your PATH:
chmod +x kc-linux-amd64
sudo mv kc-linux-amd64 /usr/local/bin/kc
kc versionRequires Go 1.25+:
cd KvindoCloud.CLI
make build # produces ./kc
# or cross-compile every platform into dist/:
make build-allFor local CLI development there is a self-rebuilding launcher (make dev-install) that recompiles
kc whenever the Go sources change.
kc authenticates with a bearer token tied to your user. Create one in the web console:
Console → IAM → Tokens → Create token — copy the token value (shown once).
Then just run any command. If kc has no token yet it will prompt you (input is hidden) and save it:
$ kc get folders
Enter token for profile "default": ********
The token is stored in your profile (see Configuration) and reused from then on. Verify it any time:
kc login # "Logged in. Token is valid."
kc logout # clears the saved token (asks for confirmation)Other ways to supply the token (no prompt):
kc config set-token <token> # save it explicitly
export KC_TOKEN=<token> # one-off / CI — bypasses the config file entirelyDownloaded a .kcc credentials file from the console's Sign In page? Import it directly — this
is the only kc command that works before you have any token at all:
kc config import-kcc ~/Downloads/kvindo-cloud-credentials.2t.json.kccEvery account in the file becomes its own profile in one go (see Profiles below). The unlock password is asked locally and never sent to the server.
By default kc talks to https://cloud-api.kvindo.com. Override it per command with
KC_API_URL=…, or persist it with kc config set-server <url>.
Config lives under ~/.kc/:
~/.kc/config/default.yaml # the "default" profile: { server, token }
~/.kc/config/active # name of the currently-active profile
~/.kc/cache/ # response cache (safe to delete)
A profile file looks like:
server: https://cloud-api.kvindo.com
token: eyJhbGci...After you kc login under a profile, it also carries a cached copy of your identity (resolved
server-side once, at login — never fetched again automatically):
server: https://cloud-api.kvindo.com
token: eyJhbGci...
organizationId: 01K7...
organizationName: AVANT
userId: 01KK...
userName: rootUse profiles to switch between accounts or environments — like kubectl contexts:
kc config view # show the active profile
kc config get-contexts # list all profiles as a table
kc config use-context dev # switch active profile (aliases: use-profile, switch, uc)
kc switch prod # same thing, short top-level alias
kc uc 2 # ...or "kc config switch"/"kc config uc"
kc config set-server <url> # set server URL on the active profile
kc config set-token <token> # set token on the active profile
kc config import-kcc <file> # import every account in a .kcc file as its own profilekc config get-contexts prints a table, entirely from the local cache (no network call — it has
to work offline and across profiles that point at different, sometimes-unreachable servers):
№ NAME SERVER ORGANIZATION_ID_AND_NAME USER_ID USER_NAME TOKEN_HASH
*1 dev https://dev-cloud-api.avant-it.ru 01K7.../AVANT 01KK... root sha256:1a2b3c4d5e6f7890
2 prod https://cloud-api.kvindo.com <none> <none> <none> sha256:aabbccdd11223344
№ doubles as the CURRENT marker: the active profile's row is prefixed with * (*1 above) instead
of a separate column. Identity columns show <none> until you've run kc login under that profile
at least once — see above. prod above has never been logged into.
use-context/switch/uc accept a target from any column shown in kc config get-contexts
— not just NAME: the row number (№, with or without its * prefix), ORGANIZATION_ID_AND_NAME (the
whole id/name cell, or either half on its own), USER_ID, USER_NAME, or TOKEN_HASH (with or
without the sha256: prefix), matched case-insensitively. Pick whichever's easiest to paste. If a
value matches more than one profile (e.g. two profiles share an organization), you get an error
naming which field was ambiguous and listing the candidates — disambiguate with the exact NAME or
the № instead, both always unique. Row numbers are a convenience for interactive use, not a stable
identifier for scripts — they're just the current alphabetical position and can shift if a profile
is added or removed; NAME/the id half of ORGANIZATION_ID_AND_NAME are stable. A token hash likewise
changes if you re-kc login with a rotated/reissued token under the same profile — it's a
convenience lookup, not a stable identifier across credential rotation.
kc config import-kcc creds.kcc # imports every account in the file
kc config import-kcc creds.kcc --account root # only that one account
kc config import-kcc creds.kcc --account root --use --profile prod # narrow, name, and activate itWith no flags, every account in the file is imported — one profile each, prefilled as
<org>-<username>-<userid> (e.g. avant-root-01k336qkpxfzd81nqw8djt1dye). --account narrows
the batch to one match, which is required before --profile (explicit name) or --use (make it
active) — both are ambiguous across multiple accounts. No existing profile is ever deleted;
importing over a name that already exists asks for confirmation first. A wrong unlock password
re-prompts indefinitely — press Enter with no password to skip that one account and continue.
| Variable | Effect |
|---|---|
KC_PROFILE |
Use this profile instead of the active one — accepts any of the get-contexts columns (№, NAME, org/user id, org/user name, token hash), same as use-context |
KC_API_URL |
Override the server URL |
KC_TOKEN |
Use this token inline (skips the config file — handy in CI) |
KC_PROFILE=staging kc get vm
KC_API_URL=https://cloud-api.kvindo.com KC_TOKEN=$TOKEN kc get foldersBreaking change: KC_PROFILE set to a value that doesn't match any saved profile now errors
clearly (error: no profile matching "...") instead of silently falling back to an anonymous,
tokenless profile that just failed with a confusing 401. If a script relies on the old silent
fallback, fix the typo/stale name it's masking rather than working around the new error — that's
the whole point of the change. The same applies if the active profile itself gets deleted by
hand: the next command errors instead of quietly running with no token.
- Folders are namespaces. Every resource lives in a folder. Scope a command to one with
-n <folder>(by name or ID), or list across all folders with-A. - Name or ID. Wherever a command takes a resource, you can pass its name or its ULID.
Names are not globally unique — if a name matches more than one resource,
kclists the matches and asks you to use the ID. - Declarative first. You create and update resources by applying a manifest (
kc apply -f). The manifest format is the kubectl-style envelope thatkc get -o yamlprints, so the easiest way to learn any resource's fields is to look at an existing one with-o yaml. - References accept names. Inside a manifest, fields that point at another resource (e.g.
folderId,vpcId,vpcSubnetId,routeTableId,securityGroupIds, …) accept that resource's name —kcresolves it to the ID for you. (Rawcurl/API calls require the ULID.)
kc get vm # list VMs in your default folder
kc get vm -n prod # list VMs in folder "prod"
kc get vm -A # list VMs across ALL folders
kc get vm my-vm # one resource by name
kc get vm 01k9... # one resource by ULID
kc get vm,s3-bucket,folder # several types at once
kc get all # every top-level resource type
kc get network-all # every resource in the networking product| Format | Description |
|---|---|
table (default) |
Human-readable columns |
wide |
Table with extra columns (IPs, flavor, provider, …) |
json |
kubectl-style envelope (apiVersion, kind, metadata, spec, status) |
yaml |
Same envelope as YAML — this is the manifest format for apply |
kc get vpc my-vpc -o yaml # see a resource as a manifest
kc get vm -A -o wide # extra columns
kc get s3-bucket -o json | jq . # machine-readable
kc get vm -q # IDs only (scripting)kc ps # = kc get vm
kc images # = kc get image
kc image ls # = kc get image
kc volume ls -n prod # = kc get volume -n prod
kc container ls # = kc get vmYou create or update resources by applying a manifest — one or more kubectl-style documents.
kc apply is idempotent: run it again and it updates the existing resource instead of making a
duplicate (matched by name within the folder).
kc apply -f my-resources.yaml # apply a file
kc apply -f - # apply a manifest from stdin
cat my.yaml | kc apply -f -
kc apply -f one.yaml --wait # block until reconciled (single-document manifests only)
applyis the only declarative verb.kc createis reserved for a future imperative create and currently reports “not implemented yet” — usekc apply -fto create resources.
A document is an envelope with apiVersion, kind, metadata, and (for most types) spec.
The simplest discovery trick: kc get <type> <name> -o yaml and edit what it prints.
apiVersion: v1
kind: Folder
metadata:
name: my-app
folderId: 01k0parent... # parent folder (name or ID)
spec: {}
---
apiVersion: v1
kind: Vpc
metadata:
name: my-vpc
folderId: my-app # ← references the folder above BY NAME
spec:
hostingProviderId: 01kk7at... # from: kc get provider
---
apiVersion: v1
kind: VpcSubnet
metadata:
name: my-subnet
folderId: my-app
spec:
vpcId: my-vpc # ← references the VPC above BY NAME
ipv4Cidr: "10.10.0.0/24"Apply it all at once:
kc apply -f stack.yaml
# Folder/my-app applied
# Vpc/my-vpc applied
# VpcSubnet/my-subnet appliedNotes:
- Multiple documents are separated by
---and applied in order, so a later document can reference a resource an earlier one creates (by name, as above). - Idempotent. If a document has no
metadata.id,kclooks up an existing resource of thatkindwith the same name in the same folder: none → create, one → update, many → it asks you to addmetadata.idto disambiguate. - Updating = edit the YAML (or re-run
kc get … -o yaml > file, change it) andkc apply -fagain. - Read-only fields like
statusare ignored on apply.
Opens the resource's manifest in your $EDITOR; on save it is applied. Works for any resource type:
kc edit folder my-app
kc edit vpc my-vpckc delete vpc my-vpc # by name
kc delete vpc 01k9... # by ID
kc rm vpc my-vpc # rm / remove are aliases for delete
kc delete vpc my-vpc --wait # block until it is actually gone
kc delete -f stack.yaml # delete everything described in a manifest
kc delete -f stack.yaml --wait # (single-document manifests)Delete dependents before their parents (e.g. a subnet before its VPC).
| Command | Description |
|---|---|
get, ls, list |
List resources (all types, all output formats) |
api-resources |
List every resource type with its aliases and kind (like kubectl api-resources) |
apply |
Create/update from a manifest with -f <file|-> |
edit |
Edit a resource in $EDITOR |
delete, remove, rm |
Delete a resource (add --wait; or -f <manifest>) |
login |
Verify the token and confirm identity |
logout |
Clear the saved token |
version |
Print the server version |
config |
Manage profiles (view, get-contexts, use-context, set-server, set-token, import-kcc) |
help |
Show built-in help — also kc (no args), kc ?, kc -h, kc --help |
| Flag | Short | Applies to | Description |
|---|---|---|---|
--folder <name|id> |
-n |
read verbs | Scope to a folder |
--all-folders |
-A |
get |
Show resources in every folder |
--output <fmt> |
-o |
get |
table (default), wide, json, yaml |
--quiet |
-q |
get |
Print IDs only |
--filename <file> |
-f |
apply/delete |
Manifest path (- = stdin) |
--wait |
-w |
delete, single-doc apply/delete |
Block until reconciled |
For
applyanddelete,-f/--filenameis the manifest path (-= stdin). To scope agetto a folder, use-n/--folder.
Core (core-all): folder, provider, transaction
IaM (iam-all): user, token, policy
Compute (compute-all): vm, volume, volume-attachment, image, image-schedule,
sshkey, ssh-private-key, cert, on-off-schedule
Networking (network-all): vpc, subnet, sg, fip, route-table, route-table-route,
route-table-attachment, peering, peering-peer, peering-external-peer
LoadBalancer (lb-all): lb, lb-target-group, lb-static-target, lb-sd-target,
lb-http-listener, lb-https-listener, lb-tls-listener,
lb-tcp-listener, lb-udp-listener,
lb-http-listener-rule, lb-https-listener-rule, lb-tls-listener-rule,
lb-tcp-listener-rule, lb-udp-listener-rule
K8s (k8s-all): k8s, k8s-ng, k8s-user, k8s-user-role
S3 (s3-all): s3-bucket, s3-user, s3-user-policy
PostgreSQL (pg-all): pg, pg-ng, pg-paramset, pg-standalone
VPN (vpn-all): openvpn, openvpn-user, openvpn-user-settings
GitLab (gitlab-all): gitlab, gitlab-runner
AI (ai-all): ollama
Quota (quota-all): quota, quota-change-request
Billing (billing-all): billing-account
Besides the short aliases above, every type also accepts its API names, so the following all refer to the same resource:
k8s-ng # short alias
k8sng # short alias, no separators
kubernetes-node-group # kebab-case API path
kubernetesnodegroup # lowercase API kind
KubernetesNodeGroup # API kind (PascalCase)
Run kc api-resources to list every type with its aliases, kind, and product group.
Group shorthands: kc get all (every top-level type) and kc get <product>-all
(e.g. kc get network-all, kc get k8s-all).
Run kc help for the built-in summary.
Snapshot a resource as a reusable manifest
kc get vpc my-vpc -o yaml > my-vpc.yaml # edit, then `kc apply -f my-vpc.yaml`Copy a stack between folders
kc get vpc,subnet -n staging -o yaml > stack.yaml
# edit folderId values to point at "prod", then:
kc apply -f stack.yamlFind a hosting provider ID for a new VPC
kc get providerScripting (JSON + jq)
kc get vm -A -o json | jq -r '.items[].metadata.name'| Symptom | Cause / fix |
|---|---|
| Prompted for a token every time | Token wasn't saved — run kc config set-token <token>, or check ~/.kc/config/. |
authentication failed |
Token is invalid/expired — create a new one in the console (IAM → Tokens). |
multiple … resources named "x" — use ID |
The name is ambiguous; pass the ULID instead (kc get <type> to find it). |
… resources with that name — add metadata.id to disambiguate (apply) |
Same, in a manifest: set metadata.id on that document. |
| Wrong server / environment | Check kc config view; set with kc config set-server or KC_API_URL. |
| Stale output | Delete the cache: rm -rf ~/.kc/cache. |
These appear in kc help as coming soon and currently return “not implemented yet”. Until then,
use manifests (kc apply -f) for create/update:
kc describe— detailed resource view- Imperative
kc create <type> --flag …,kc update/patch kc start/kc stop(VM power)- Docker-style
run/stop, andimage rm/volume rm(top-levelkc rm <type> <name>does work — it's an alias fordelete)
There is no logs, exec, or watch (-w streaming) in this version.
Part of the Kvindo Cloud developer toolchain:
- terraform-provider-kvindo — Terraform provider for Kvindo Cloud (Registry).
- kc-sdk-python — Python SDK for the Kvindo Cloud API (PyPI).
- kc-mcp-server — MCP server for Kvindo Cloud (npm), for managing resources from Claude Desktop/Code and other MCP clients.
- Kvindo Cloud console — web UI and API.
- Claude Code skill — lets Claude manage your Kvindo Cloud resources through
kcconversationally.