@kutrion/cli 1.2.14 → 1.2.21
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 +160 -11
- package/dist/index.js +4970 -732
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,13 +34,18 @@ kt --base https://kutrion.com login --email you@example.com
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
`login` prompts for the password (and a 2FA code when the account has one), so neither lands in shell
|
|
37
|
-
history.
|
|
38
|
-
|
|
37
|
+
history. An account with no authenticator app or security key is emailed a 6-digit code at every password
|
|
38
|
+
sign-in, and `login` prompts for it; a blank entry or `r` at the prompt sends a new code. `--code` passes an
|
|
39
|
+
authenticator-app code. A password piped on stdin is for an account that has an authenticator app:
|
|
39
40
|
|
|
40
41
|
```sh
|
|
41
|
-
printf '%s\n' "$KT_PASSWORD" | kt login --email you@example.com --password-stdin
|
|
42
|
+
printf '%s\n' "$KT_PASSWORD" | kt login --email you@example.com --password-stdin --code 123456
|
|
42
43
|
```
|
|
43
44
|
|
|
45
|
+
Without `--code`, an emailed-code account stops there with a message, because stdin cannot also answer the
|
|
46
|
+
prompt. Scripts and CI should not sign in with a password at all: they use an agent token (`KT_AGENT_TOKEN`
|
|
47
|
+
with `kt exec`, `kt run` or `kt action run`).
|
|
48
|
+
|
|
44
49
|
With no `--email` and no `--password-stdin`, `login` first asks the Kutrion app on this computer to sign the CLI in,
|
|
45
50
|
and falls back to the email and password when no app answers. Without a terminal it asks only an app this CLI has
|
|
46
51
|
already paired with, because the first pairing needs you to type `yes`. When a later command is refused because the
|
|
@@ -74,11 +79,32 @@ kt ssh <connId>
|
|
|
74
79
|
kt --help
|
|
75
80
|
```
|
|
76
81
|
|
|
82
|
+
When `kt conn add` or `kt conn set` leaves a credential not yet authorized for the connection's host, a terminal is asked to type yes and the saved value authorizes it. `--yes` skips the question, `--no-authorize` skips the step, and `--json` never asks.
|
|
83
|
+
|
|
77
84
|
`kt get` prints a credential with its value hidden; add `--reveal` to print the value. `kt cred add`
|
|
78
|
-
and `kt cred set` prompt for the value, or read it from stdin with `--value-stdin`. `kt export <vaultId>
|
|
85
|
+
and `kt cred set` prompt for the value, or read it from stdin with `--value-stdin`. `--value-stdin` stores a value that contains a newline (an SSH key, a PEM block, a `.env`) byte-for-byte, final newline included, so it equals its source file; a single-line value (`echo pw | kt ...`) loses its one terminating newline. `--password-stdin` follows the same rule. `kt export <vaultId>
|
|
79
86
|
<file>` and `kt audit export <file>` write the file owner-only (mode 0600), tightening an existing file
|
|
80
87
|
before writing to it.
|
|
81
88
|
|
|
89
|
+
`kt export` releases a whole vault in plain text, so it needs a request confirmed in the same session: it
|
|
90
|
+
asks for your authenticator code, then for the code mailed to your account email for that request. Without
|
|
91
|
+
a terminal, `kt export <vaultId> --request` prints the request id and exits 19; then, in the same session
|
|
92
|
+
and within 10 minutes, run `kt step-up` and pipe the mailed code to
|
|
93
|
+
`kt export <vaultId> [file] --code-stdin <requestId>`. A passkey cannot be checked in a terminal, so an account
|
|
94
|
+
with no authenticator app exports from the web app. Unattended, scheduled export is not supported.
|
|
95
|
+
|
|
96
|
+
In a Private vault, `kt cred add` and `kt cred set` ask for your vault passphrase (or read it with
|
|
97
|
+
`--vault-passphrase-stdin`, as `kt get --reveal` does), seal the value and notes on this machine and
|
|
98
|
+
send only the ciphertext. With both `--value-stdin` and `--vault-passphrase-stdin`, stdin carries the
|
|
99
|
+
passphrase on its first line and the value after it:
|
|
100
|
+
`printf '%s\n%s' "$PASSPHRASE" "$VALUE" | kt cred add <vaultId> --name db --value-stdin --vault-passphrase-stdin`.
|
|
101
|
+
`kt cred dest add <vaultId> <credId> --host <h>` lists the hosts and asks you to type yes; without a terminal pass `--yes` to authorize without the question (it exits 2 without it). `--enter-value` always needs a terminal.
|
|
102
|
+
A rename alone needs no passphrase. `kt import` refuses a Private vault, because the export file is
|
|
103
|
+
parsed by the server; nothing is sent. A write with `--vault-passphrase-stdin` to a vault the server
|
|
104
|
+
says is not Private is refused before stdin is read. The CLI also remembers, per server, the ids of
|
|
105
|
+
the vaults it has seen Private (`~/.config/kutrion/private-vaults.json`, mode 0600, ids only), and
|
|
106
|
+
refuses a write when one of them later answers as not Private.
|
|
107
|
+
|
|
82
108
|
Secrets passed as flags (`--password`, `--value`, and `--token` on `kt run`, `kt mcp` and
|
|
83
109
|
`kt ci-login`) are refused before anything is sent: a flag lands in shell history and in the process
|
|
84
110
|
list. Use `--password-stdin`, `--value-stdin`, `KT_AGENT_TOKEN` or `KT_ID_TOKEN` instead.
|
|
@@ -121,12 +147,19 @@ The exit status says what kind of failure it was, so a script can branch without
|
|
|
121
147
|
| 10 | Not signed in, or the session was refused (HTTP 401) |
|
|
122
148
|
| 11 | Needs a plan the organization does not have (402) |
|
|
123
149
|
| 12 | Refused: permission, policy or step-up (403) |
|
|
124
|
-
| 13 | Not found (404) |
|
|
150
|
+
| 13 | Not found (404), including an id that is not well formed |
|
|
125
151
|
| 14 | Conflict with the current state (409) |
|
|
126
152
|
| 15 | Rate limited (429) |
|
|
127
153
|
| 16 | Server error (5xx) |
|
|
154
|
+
| 17 | A download stalled: no bytes for `--stall-timeout` seconds (`kt sftp get`, `kt file get`); the error prints the `--resume` command |
|
|
155
|
+
| 18 | `kt exec` lost its connection after the request was sent, or the session ended without an exit status: the command may have run (see below) |
|
|
156
|
+
| 19 | `kt export --request`: the export request was made and is pending until confirmed in this session; `kt grant wait`: the request is still pending after 540 seconds |
|
|
157
|
+
| 20 | `kt mcp status`: an agent credential expired, is due its 90-day confirm, or reached the end of its confirmed lifetime; its owner can renew or confirm it |
|
|
158
|
+
| 21 | `kt mcp status`: an agent credential was revoked; reissue it with `kt mcp install` |
|
|
159
|
+
|
|
160
|
+
`kt grant wait <id>` follows a request for up to 540 seconds and exits 0 when it is approved or done, 12 when it was denied or refused, 13 when it expired or is no longer available, 14 when it was canceled or replaced by a newer request, and 19 when it is still pending at the limit; run it again to keep following.
|
|
128
161
|
|
|
129
|
-
`kt exec` exits with the remote command's own status, with or without `--json`.
|
|
162
|
+
`kt exec` exits with the remote command's own status, with or without `--json`. Status 18 is the exception, and only it: the connection to Kutrion dropped after the command was sent (or the host ended the session with no exit status, as when a command restarts the host's SSH service), so the command may have run. Check with a status command or `kt audit --action SSH_EXEC_STARTED` before repeating it. For a command that ends the session or the broker on purpose, `kt exec --detach <conn> <command...>` starts it on the host outside the session under `nohup` (the command is sent in plain text as the literal `sh -c` argument, so the risk gate, exec policy and audit row see it as typed; the job's pid, output and exit code go in `${XDG_STATE_HOME:-$HOME/.local/state}/kt-detach`, which must be a mode 700 directory owned by the user, not a symlink) and prints a handle at once with status 0. `kt exec <conn> --status <handle>` then reports `running`, `finished` with its exit code, or `unknown`, followed by the last 4096 bytes of the output; Kutrion stores no output. The start and each check are audited as ordinary execs with their command. `--detach` is refused when exec is limited to an allowlist, because the wrapper is a different command from the one allowed. A connection that fails before anything is sent keeps its ordinary error and status.
|
|
130
163
|
|
|
131
164
|
With `--json`, stdout carries only JSON. A failure is one object on stdout,
|
|
132
165
|
`{ "error": "<code>", "status": <http status>, "exit_code": <n>, "message": "<text>" }` (`status` only
|
|
@@ -141,14 +174,84 @@ needs agent access:
|
|
|
141
174
|
|
|
142
175
|
```sh
|
|
143
176
|
kt mcp install --client claude-code # or claude-desktop, cursor, copilot
|
|
144
|
-
kt mcp status # each client's agent, and which clients are wired
|
|
177
|
+
kt mcp status # each client's agent (live, expiring, expired or revoked), and which clients are wired
|
|
145
178
|
kt mcp uninstall --client claude-code # revoke the agent and remove the config entry
|
|
146
179
|
```
|
|
147
180
|
|
|
148
181
|
`install` mints an agent for that client, keeps its credential in the OS keychain, writes the
|
|
149
182
|
client's config to launch `kt mcp`, and starts the bridge once to check that it launches. The config
|
|
150
183
|
holds the server address and the client name, never a credential. Follow the next step it prints for
|
|
151
|
-
that client to load the new server.
|
|
184
|
+
that client to load the new server. The default scopes cannot run commands; add `--scopes` with
|
|
185
|
+
`ssh.exec` for that, and the install asks for an authenticator code first.
|
|
186
|
+
|
|
187
|
+
### Keep an installed agent alive (`kt agent keepalive`)
|
|
188
|
+
|
|
189
|
+
An agent with ssh or files access is granted at most 7 days at a time. Keepalive replaces its agent token
|
|
190
|
+
before then, so the client keeps working without a reinstall. It is off unless you turn it on:
|
|
191
|
+
|
|
192
|
+
```sh
|
|
193
|
+
kt mcp install --client claude-code --keepalive # at install
|
|
194
|
+
kt agent keepalive --on --client claude-code # for an existing install
|
|
195
|
+
kt agent keepalive --off [--client claude-code] # stop; the timer goes when no client is left
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
A timer (a launchd LaunchAgent on macOS, a `systemd --user` timer on Linux) runs
|
|
199
|
+
`kt agent keepalive --run` every 6 hours. The timer holds only the path to `kt` and those arguments,
|
|
200
|
+
no credential and no `KT_` variable, and logs ids and outcomes to `~/.config/kutrion/keepalive.log`
|
|
201
|
+
(mode 0600). A run rotates an agent, with your `kt` session, when it has under 3 days left and the
|
|
202
|
+
`kt mcp` bridge used it on this machine in the last 7 days, so the agent token is replaced about every 4
|
|
203
|
+
days while the machine is in use. It writes the new agent token to the client's keychain slot, and a
|
|
204
|
+
running bridge picks it up on its next call. A run skips the agent when the keychain is locked or the
|
|
205
|
+
slot holds a different agent's token, and stops trying when the server refuses the rotate (revoked,
|
|
206
|
+
step-up required, or its 90-day confirm is due). `kt mcp status` shows the days left and what the last
|
|
207
|
+
run did, with the remedy when there is one; it is usually `kt mcp install --client <id> --keepalive`.
|
|
208
|
+
|
|
209
|
+
An agent with `ssh.exec` is not kept alive: rotating or renewing it asks for a step-up, which the timer
|
|
210
|
+
cannot give. It lasts up to 7 days unless renewed with a step-up in the web app, and is then minted again
|
|
211
|
+
with `kt mcp install`, which asks for a code;
|
|
212
|
+
`--keepalive` is refused together with `ssh.exec`, and a reinstall that adds `ssh.exec` turns an earlier
|
|
213
|
+
opt-in off.
|
|
214
|
+
|
|
215
|
+
Every rotate is recorded in the audit log. Windows has no timer yet, and keepalive needs the OS keychain
|
|
216
|
+
(not `KT_TOKEN_STORE=file`). A bridge fed by `KT_AGENT_TOKEN` must not opt in: keepalive replaces the
|
|
217
|
+
agent token in the keychain slot, which that bridge never reads, so the rotate would strand it.
|
|
218
|
+
|
|
219
|
+
## List your agents (`kt agent ls`)
|
|
220
|
+
|
|
221
|
+
`kt agent ls` lists your agents, including expired and revoked ones, with each one's state (`live`,
|
|
222
|
+
`expiring` within 14 days, `expired` or `revoked`), its expiry and when it was last used. `--json` prints
|
|
223
|
+
the same fields as JSON.
|
|
224
|
+
|
|
225
|
+
## Mint an agent for a server (`kt agent mint`)
|
|
226
|
+
|
|
227
|
+
For a cron job or service that has no browser to sign in with:
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
kt agent mint --scope-vault prod --write-env /etc/kutrion/agent.env
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
It mints an agent for the signed-in account and prints the token once. `--scope-vault` and
|
|
234
|
+
`--scope-conn` (repeatable, id or name) limit it to those vaults or connections; with none it reaches
|
|
235
|
+
everything you can. `--scopes`, `--days`, `--egress lan|cloud` and `--name` set its scopes, lifetime,
|
|
236
|
+
AI egress class and label. `--write-env <file>` writes `KT_AGENT_TOKEN` and `KT_API_BASE` to a 0600
|
|
237
|
+
file instead and never prints the token; an existing file needs `--yes`. Source the file in the job
|
|
238
|
+
and run `kt run` or other commands as that agent.
|
|
239
|
+
|
|
240
|
+
When the organization gates agent minting behind a passkey approval, `kt agent mint` requests the approval,
|
|
241
|
+
prints a presence code once on stderr, and waits. Open the link mailed to your account email, approve with
|
|
242
|
+
your passkey and type the code on the approve page; kt then mints the agent from the same session and prints the
|
|
243
|
+
token once. The approval must be finished within 5 minutes. If kt is interrupted after the approval,
|
|
244
|
+
`kt grant wait <requestId> --complete` finishes the mint from the same session (the presence code cannot be shown
|
|
245
|
+
again). An account that approves with a mailed code instead cannot mint from the CLI; use the web app.
|
|
246
|
+
|
|
247
|
+
### Workloads (`kt agent mint --workload`)
|
|
248
|
+
|
|
249
|
+
For an unattended engine such as n8n: `kt agent mint --workload client_secret --scope-vault prod --name n8n` mints a
|
|
250
|
+
workload and prints its `client_id` and `client_secret` once; `--workload static` prints a bearer token instead (and
|
|
251
|
+
accepts `--write-env`). A workload must name its vaults with `--scope-vault`; `--exec-mode allowlist|unrestricted` sets
|
|
252
|
+
what its commands may be. Minting needs a fresh step-up. `kt agent rotate <id>` issues a new secret (the old one stops
|
|
253
|
+
working at once) and `kt agent revoke <id>` ends one agent or workload. Refused token requests for a workload client are
|
|
254
|
+
audit rows: `kt audit --action AGENT_TOKEN_REFUSED`.
|
|
152
255
|
|
|
153
256
|
## Job files in CI (`kt run`)
|
|
154
257
|
|
|
@@ -192,7 +295,8 @@ still carries one is refused with that reason rather than silently ignored. Rota
|
|
|
192
295
|
`kt rotation run` or from the web app, where a person is present.
|
|
193
296
|
|
|
194
297
|
```sh
|
|
195
|
-
|
|
298
|
+
KT_AGENT_TOKEN=$(kt ci-login --trust $KT_OIDC_TRUST_ID) || exit 1 # exchange the pipeline's OIDC token
|
|
299
|
+
export KT_AGENT_TOKEN
|
|
196
300
|
kt run kutrion.yml # console reporter, exits non-zero on failure
|
|
197
301
|
kt run kutrion.yml --reporter junit --out report.xml
|
|
198
302
|
kt run kutrion.yml --dry-run # validate and list the steps, touch nothing
|
|
@@ -200,7 +304,48 @@ kt run kutrion.yml --dry-run # validate and list the steps, touch
|
|
|
200
304
|
|
|
201
305
|
On GitLab, the job's OIDC token comes from an `id_tokens:` block that exposes it as `KT_ID_TOKEN`,
|
|
202
306
|
with the audience your trust is registered for. On GitHub Actions, give the job
|
|
203
|
-
`permissions: id-token: write` and `ci-login` requests the token itself
|
|
307
|
+
`permissions: id-token: write` and `ci-login` requests the token itself, for the audience in
|
|
308
|
+
`KT_OIDC_AUDIENCE` (default `kutrion`). A trust registered with a different audience needs
|
|
309
|
+
`KT_OIDC_AUDIENCE` set to that value in the job, or a new trust with `--audience kutrion`.
|
|
310
|
+
|
|
311
|
+
Register the trust with `kt trust add --issuer <url> --audience <aud> --subject <pattern> --vault <id>`.
|
|
312
|
+
`--vault` and `--connection` are repeatable. Any `ssh.*` or `files.*` scope needs at least one
|
|
313
|
+
`--connection <id>` (`--conn` is an alias), and `ssh.exec` also needs `--exec-mode unrestricted`, a full
|
|
314
|
+
shell as the connection's account and the only mode available today.
|
|
315
|
+
|
|
316
|
+
`kt trust ls` shows each trust's status (`live`, `confirm_due`, `confirm_overdue` or `suspended`) and, for a
|
|
317
|
+
suspended trust, the reason. A credential event, or a bulk revoke run with `--include-trusts`, suspends a
|
|
318
|
+
trust: its exchanges are refused until its owner runs `kt trust resume <id>`. Resuming is confirmed like
|
|
319
|
+
creating: an authenticator code, then the code mailed to you (`--request` and `--code-stdin <requestId>`
|
|
320
|
+
split it for scripts). Only the owner can resume a trust.
|
|
321
|
+
|
|
322
|
+
Grants have two approval paths, and the server picks one from the account's factors; `kt` follows whichever it
|
|
323
|
+
answers (`path` in the request). `kt` always says it can follow the link path (`x-grant-paths: link`).
|
|
324
|
+
|
|
325
|
+
- `code`: the code mailed to your email, after an authenticator app check, as above. An account with no passkey stays
|
|
326
|
+
on this path, so its only factors are the mailbox and the authenticator app: a person who holds both can approve.
|
|
327
|
+
Adding a passkey moves the account to the `link` path.
|
|
328
|
+
- `link`: an account with a passkey gets an approve link by email and no code. Open it in a browser, approve with
|
|
329
|
+
the passkey and type the presence code `kt` printed to stderr as `XXXX-XXXX` when the request was made. The
|
|
330
|
+
code comes only from that terminal; Kutrion never sends it. `kt` shows it once and never writes it to a file,
|
|
331
|
+
the config, a log or the keychain; `--json` output includes it. `kt trust add` then waits for the approval.
|
|
332
|
+
`kt trust add` and `kt trust resume` need a terminal for either path unless `--request`, `--json` or
|
|
333
|
+
`--code-stdin` is given, and say so before any request is sent.
|
|
334
|
+
|
|
335
|
+
`kt grant wait <id>` follows a request by id (polling every 10 seconds, for up to 540 seconds; exit status above) and resumes after a crash or Ctrl-C, but it
|
|
336
|
+
cannot show the presence code again; if it was lost, request again. A `--request` on the link path prints the id,
|
|
337
|
+
the presence code and exits 19; run `kt grant wait <id>` to follow it. If an earlier request was already approved
|
|
338
|
+
and is waiting to be finished, the new request is refused (`grant_request_approved`); `kt` names the device that
|
|
339
|
+
asked and follows that request, and it can be replaced 5 minutes after its approval. A request ends as `denied`,
|
|
340
|
+
`expired`, `cancelled`, `superseded` or `refused`; `refused` means it was refused before it was approved, so
|
|
341
|
+
request again.
|
|
342
|
+
|
|
343
|
+
```sh
|
|
344
|
+
kt agent revoke --filter owner --owner <userId> --reason "laptop lost" --include-trusts
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`kt agent revoke` revokes agents in bulk by `--filter all|owner|client|scope`. `--include-trusts` also
|
|
348
|
+
suspends the trusts in scope (not with `--filter client`) and prints `trusts_suspended`.
|
|
204
349
|
|
|
205
350
|
**How a pipeline authenticates.** With `KT_AGENT_TOKEN` set, `kt run` runs
|
|
206
351
|
as that pipeline principal and sends its steps to the server's agent endpoint (`/mcp`) rather than to
|
|
@@ -208,7 +353,11 @@ the REST API. It has to: a principal from `ci-login` is an opaque `vta_` token,
|
|
|
208
353
|
verifies a JWT, so an exchanged principal authenticates nothing there. Routing through the agent
|
|
209
354
|
endpoint is also what subjects each step to the organization's agent tool policy and records it in
|
|
210
355
|
the flight recorder against the pipeline's identity. Without that variable `kt run` uses your own
|
|
211
|
-
stored session over REST, unchanged
|
|
356
|
+
stored session over REST, unchanged, except in CI (`CI` set to anything but empty, `false` or `0`): there a missing `KT_AGENT_TOKEN` is an
|
|
357
|
+
error and `kt run` never falls back to a stored login. `--dry-run` needs neither.
|
|
358
|
+
|
|
359
|
+
Assign the token on its own line, as above. `export KT_AGENT_TOKEN=$(kt ci-login ...)` returns the exit
|
|
360
|
+
status of `export`, so a failed login would not stop the job.
|
|
212
361
|
|
|
213
362
|
Two things differ on the pipeline path. A `get` step receives the file as broker-redacted UTF-8 text
|
|
214
363
|
capped at 1MB rather than raw bytes, and the step says so in its output — do not use it to move a
|