@kutrion/cli 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -2
- package/dist/index.js +26314 -0
- package/package.json +34 -4
package/README.md
CHANGED
|
@@ -1,3 +1,188 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Kutrion CLI (`kt`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vaults, credentials and SSH/SFTP/RDP brokering from the terminal. The published package is a single
|
|
4
|
+
self-contained bundle (the shared client, `ws` and `@noble/post-quantum` are inlined at build time),
|
|
5
|
+
so it installs with no transitive dependencies.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Download the CLI tarball from https://kutrion.com/downloads, then install it with npm from the folder
|
|
10
|
+
you saved it in. It needs Node 18 or newer and pulls in no dependencies:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
node -v # must report v18 or newer
|
|
14
|
+
npm i -g ./kutrion-cli-<version>.tgz # the leading ./ is required; use the file name you downloaded
|
|
15
|
+
kt --version
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`npm i -g @kutrion/cli` does not work: the package is not on the public npm registry.
|
|
19
|
+
|
|
20
|
+
## First sign-in
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
kt --base https://kutrion.com login --email you@example.com
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`login` prompts for the password (and a 2FA code when the account has one), so neither lands in shell
|
|
27
|
+
history. A script pipes the password instead, with `--code` when the account has a second factor:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
printf '%s\n' "$KT_PASSWORD" | kt login --email you@example.com --password-stdin
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A successful login saves the server to `~/.config/kutrion/config.json`, so every command after it
|
|
34
|
+
needs no `--base`:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
kt whoami
|
|
38
|
+
kt vaults
|
|
39
|
+
kt get <vaultId> <credId>
|
|
40
|
+
kt conn add --name web-01 --host 10.0.0.5 --username deploy --cred <credId>
|
|
41
|
+
kt ssh <connId>
|
|
42
|
+
kt --help
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`kt get` prints a credential with its value hidden; add `--reveal` to print the value. `kt cred add`
|
|
46
|
+
and `kt cred set` prompt for the value, or read it from stdin with `--value-stdin`. `kt export <vaultId>
|
|
47
|
+
<file>` and `kt audit export <file>` write the file owner-only (mode 0600), tightening an existing file
|
|
48
|
+
before writing to it.
|
|
49
|
+
|
|
50
|
+
Secrets passed as flags (`--password`, `--value`, and `--token` on `kt run`, `kt mcp` and
|
|
51
|
+
`kt ci-login`) are refused before anything is sent: a flag lands in shell history and in the process
|
|
52
|
+
list. Use `--password-stdin`, `--value-stdin`, `KT_AGENT_TOKEN` or `KT_ID_TOKEN` instead.
|
|
53
|
+
|
|
54
|
+
Saved layouts, the account-synced arrangement the web app opens at sign-in, are `kt layouts`,
|
|
55
|
+
`kt layout create <name> [--default]` and `kt layout rm <id>`.
|
|
56
|
+
|
|
57
|
+
Snippets, the saved commands kept in vaults, are `kt snippets`, `kt snippet show <name|id>` and
|
|
58
|
+
`kt snippet run <name|id> --conn <connId> [--insert]`. `run` prints the whole command, opens a
|
|
59
|
+
brokered shell and has the server type it there, with the use recorded in the audit log; `--insert`
|
|
60
|
+
places it at the prompt without pressing Enter.
|
|
61
|
+
|
|
62
|
+
Use the address exactly as written. The CLI does not follow a redirect on a login: a redirected POST
|
|
63
|
+
is re-sent without its body, so `kt` stops and prints the address the server pointed to instead.
|
|
64
|
+
|
|
65
|
+
`--base <url>` (or `KT_API_BASE`) points at a server; the token is stored in the OS keychain. There
|
|
66
|
+
is no default: unconfigured, `kt` exits non-zero rather than guessing an address to send credentials
|
|
67
|
+
to. `login` also refuses a plaintext `http://` server unless it is loopback. On a machine with no
|
|
68
|
+
keychain, `KT_TOKEN_STORE=file` keeps the token in `~/.config/kutrion/token` (mode 0600).
|
|
69
|
+
|
|
70
|
+
Precedence is `--base`, then `KT_API_BASE`, then the saved server, and only `login` writes it:
|
|
71
|
+
`--base` on any other command is a one-off that reads a different server without changing the saved
|
|
72
|
+
one. To switch servers, sign in again; each server's token is filed under its own URL, so sessions are
|
|
73
|
+
never crossed and signing in to one does not end the other. `logout` clears the token for the current
|
|
74
|
+
server and leaves the saved URL, so the next `login` needs no flag either.
|
|
75
|
+
|
|
76
|
+
Environment variables set under the names earlier CLI releases used are still read when the `KT_`
|
|
77
|
+
name is not set, so an existing shell profile or assistant config keeps working.
|
|
78
|
+
|
|
79
|
+
## Scripting: exit status and `--json`
|
|
80
|
+
|
|
81
|
+
The exit status says what kind of failure it was, so a script can branch without parsing text:
|
|
82
|
+
|
|
83
|
+
| Status | Meaning |
|
|
84
|
+
|---|---|
|
|
85
|
+
| 0 | Success |
|
|
86
|
+
| 1 | Any other failure: bad usage, an unknown flag, a transport error, another 4xx |
|
|
87
|
+
| 2, 3 | Transfer commands only: partial transfer, and a job still running after the wait |
|
|
88
|
+
| 10 | Not signed in, or the session was refused (HTTP 401) |
|
|
89
|
+
| 11 | Needs a plan the organization does not have (402) |
|
|
90
|
+
| 12 | Refused: permission, policy or step-up (403) |
|
|
91
|
+
| 13 | Not found (404) |
|
|
92
|
+
| 14 | Conflict with the current state (409) |
|
|
93
|
+
| 15 | Rate limited (429) |
|
|
94
|
+
| 16 | Server error (5xx) |
|
|
95
|
+
|
|
96
|
+
`kt exec` exits with the remote command's own status, with or without `--json`.
|
|
97
|
+
|
|
98
|
+
With `--json`, stdout carries only JSON. A failure is one object on stdout,
|
|
99
|
+
`{ "error": "<code>", "status": <http status>, "exit_code": <n>, "message": "<text>" }` (`status` only
|
|
100
|
+
when the server answered), and prompts go to stderr. A flag no command takes is refused before
|
|
101
|
+
anything is sent. Answers to prompts can be piped, one per line; a prompt with no line left fails
|
|
102
|
+
instead of exiting 0.
|
|
103
|
+
|
|
104
|
+
## Connect an AI assistant (`kt mcp install`)
|
|
105
|
+
|
|
106
|
+
Once signed in, wire an assistant to Kutrion through the CLI's MCP bridge. Your organization's plan
|
|
107
|
+
needs agent access:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
kt mcp install --client claude-code # or claude-desktop, cursor, copilot
|
|
111
|
+
kt mcp status # each client's agent, and which clients are wired
|
|
112
|
+
kt mcp uninstall --client claude-code # revoke the agent and remove the config entry
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`install` mints an agent for that client, keeps its credential in the OS keychain, writes the
|
|
116
|
+
client's config to launch `kt mcp`, and starts the bridge once to check that it launches. The config
|
|
117
|
+
holds the server address and the client name, never a credential. Follow the next step it prints for
|
|
118
|
+
that client to load the new server.
|
|
119
|
+
|
|
120
|
+
## Job files in CI (`kt run`)
|
|
121
|
+
|
|
122
|
+
`kt run <file>` executes a declarative job file through the broker. Paired with `kt ci-login`, which
|
|
123
|
+
exchanges the pipeline's own OIDC token for a short-lived principal, a CI job needs no stored
|
|
124
|
+
Kutrion credential and the job file in the repo holds no secret: connections are referenced by name
|
|
125
|
+
and resolved against the caller at run time. A literal `password:`/`value:`/`token:` key anywhere in
|
|
126
|
+
the file is rejected rather than used, and no step type names a vault entry at all — the credential
|
|
127
|
+
a connection uses is the server's business, and a command that tries to interpolate one is refused.
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
name: deploy smoke
|
|
131
|
+
connections:
|
|
132
|
+
web: prod-web-01 # a connection name, or its id
|
|
133
|
+
steps:
|
|
134
|
+
- name: nginx is up
|
|
135
|
+
exec: { connection: web, command: "systemctl is-active nginx" }
|
|
136
|
+
expect: { exit_code: 0, stdout_contains: active }
|
|
137
|
+
|
|
138
|
+
- name: pull the config
|
|
139
|
+
get: { connection: web, remote: /etc/nginx/nginx.conf, local: ./nginx.conf }
|
|
140
|
+
|
|
141
|
+
- name: publish the healthcheck
|
|
142
|
+
put: { connection: web, local: ./health.sh, remote: /usr/local/bin/health.sh }
|
|
143
|
+
|
|
144
|
+
- name: mirror the artifacts
|
|
145
|
+
sync:
|
|
146
|
+
from: { connection: web, path: /var/artifacts }
|
|
147
|
+
to: { connection: web, path: /backup/artifacts }
|
|
148
|
+
mirror: true
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Each step carries exactly one action (`exec`, `get`, `put`, `sync`). `expect` applies to `exec` and
|
|
152
|
+
supports `exit_code`, `stdout_contains`, `stderr_contains`, `stdout_not_contains` and
|
|
153
|
+
`stdout_matches`; a step that asserts nothing still requires exit code 0. The run stops at the first
|
|
154
|
+
failed step and reports the rest as skipped, unless that step sets `continue_on_error: true`.
|
|
155
|
+
|
|
156
|
+
There is no `rotate` step. It was accepted once and is withdrawn: rotating changes a stored
|
|
157
|
+
credential, so a job file naming the wrong one can lock every human out of that system. A file that
|
|
158
|
+
still carries one is refused with that reason rather than silently ignored. Rotate with
|
|
159
|
+
`kt rotation run` or from the web app, where a person is present.
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
export KT_AGENT_TOKEN=$(kt ci-login --trust $KT_OIDC_TRUST_ID) # exchange the pipeline's OIDC token
|
|
163
|
+
kt run kutrion.yml # console reporter, exits non-zero on failure
|
|
164
|
+
kt run kutrion.yml --reporter junit --out report.xml
|
|
165
|
+
kt run kutrion.yml --dry-run # validate and list the steps, touch nothing
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
On GitLab, the job's OIDC token comes from an `id_tokens:` block that exposes it as `KT_ID_TOKEN`,
|
|
169
|
+
with the audience your trust is registered for. On GitHub Actions, give the job
|
|
170
|
+
`permissions: id-token: write` and `ci-login` requests the token itself.
|
|
171
|
+
|
|
172
|
+
**How a pipeline authenticates.** With `KT_AGENT_TOKEN` set, `kt run` runs
|
|
173
|
+
as that pipeline principal and sends its steps to the server's agent endpoint (`/mcp`) rather than to
|
|
174
|
+
the REST API. It has to: a principal from `ci-login` is an opaque `vta_` token, and every REST route
|
|
175
|
+
verifies a JWT, so an exchanged principal authenticates nothing there. Routing through the agent
|
|
176
|
+
endpoint is also what subjects each step to the organization's agent tool policy and records it in
|
|
177
|
+
the flight recorder against the pipeline's identity. Without that variable `kt run` uses your own
|
|
178
|
+
stored session over REST, unchanged. `--dry-run` needs neither.
|
|
179
|
+
|
|
180
|
+
Two things differ on the pipeline path. A `get` step receives the file as broker-redacted UTF-8 text
|
|
181
|
+
capped at 1MB rather than raw bytes, and the step says so in its output — do not use it to move a
|
|
182
|
+
binary. And a step the organization gates on a human approval is refused rather than waited on, since
|
|
183
|
+
a pipeline has nobody to ask.
|
|
184
|
+
|
|
185
|
+
Reporters are `console`, `junit` and `json`. The JUnit document is standard `<testsuites>` XML; a
|
|
186
|
+
GitLab job renders it once the job declares `artifacts:reports:junit`, and GitHub needs a reporting
|
|
187
|
+
action — the file alone is not picked up. With `--out` the report is written to a file and progress
|
|
188
|
+
goes to stderr, so the console stays readable while a machine consumes the file.
|