@kutrion/cli 1.2.16 → 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.
Files changed (3) hide show
  1. package/README.md +148 -10
  2. package/dist/index.js +3802 -579
  3. 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. When the account signs in with an emailed code, a blank entry or `r` at the prompt sends a new
38
- code. A script pipes the password instead, with `--code` when the account has a second factor:
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,16 +79,26 @@ 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
+
82
96
  In a Private vault, `kt cred add` and `kt cred set` ask for your vault passphrase (or read it with
83
97
  `--vault-passphrase-stdin`, as `kt get --reveal` does), seal the value and notes on this machine and
84
98
  send only the ciphertext. With both `--value-stdin` and `--vault-passphrase-stdin`, stdin carries the
85
99
  passphrase on its first line and the value after it:
86
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.
87
102
  A rename alone needs no passphrase. `kt import` refuses a Private vault, because the export file is
88
103
  parsed by the server; nothing is sent. A write with `--vault-passphrase-stdin` to a vault the server
89
104
  says is not Private is refused before stdin is read. The CLI also remembers, per server, the ids of
@@ -136,8 +151,15 @@ The exit status says what kind of failure it was, so a script can branch without
136
151
  | 14 | Conflict with the current state (409) |
137
152
  | 15 | Rate limited (429) |
138
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.
139
161
 
140
- `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.
141
163
 
142
164
  With `--json`, stdout carries only JSON. A failure is one object on stdout,
143
165
  `{ "error": "<code>", "status": <http status>, "exit_code": <n>, "message": "<text>" }` (`status` only
@@ -152,14 +174,84 @@ needs agent access:
152
174
 
153
175
  ```sh
154
176
  kt mcp install --client claude-code # or claude-desktop, cursor, copilot
155
- 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
156
178
  kt mcp uninstall --client claude-code # revoke the agent and remove the config entry
157
179
  ```
158
180
 
159
181
  `install` mints an agent for that client, keeps its credential in the OS keychain, writes the
160
182
  client's config to launch `kt mcp`, and starts the bridge once to check that it launches. The config
161
183
  holds the server address and the client name, never a credential. Follow the next step it prints for
162
- 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`.
163
255
 
164
256
  ## Job files in CI (`kt run`)
165
257
 
@@ -203,7 +295,8 @@ still carries one is refused with that reason rather than silently ignored. Rota
203
295
  `kt rotation run` or from the web app, where a person is present.
204
296
 
205
297
  ```sh
206
- export KT_AGENT_TOKEN=$(kt ci-login --trust $KT_OIDC_TRUST_ID) # exchange the pipeline's OIDC token
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
207
300
  kt run kutrion.yml # console reporter, exits non-zero on failure
208
301
  kt run kutrion.yml --reporter junit --out report.xml
209
302
  kt run kutrion.yml --dry-run # validate and list the steps, touch nothing
@@ -211,7 +304,48 @@ kt run kutrion.yml --dry-run # validate and list the steps, touch
211
304
 
212
305
  On GitLab, the job's OIDC token comes from an `id_tokens:` block that exposes it as `KT_ID_TOKEN`,
213
306
  with the audience your trust is registered for. On GitHub Actions, give the job
214
- `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`.
215
349
 
216
350
  **How a pipeline authenticates.** With `KT_AGENT_TOKEN` set, `kt run` runs
217
351
  as that pipeline principal and sends its steps to the server's agent endpoint (`/mcp`) rather than to
@@ -219,7 +353,11 @@ the REST API. It has to: a principal from `ci-login` is an opaque `vta_` token,
219
353
  verifies a JWT, so an exchanged principal authenticates nothing there. Routing through the agent
220
354
  endpoint is also what subjects each step to the organization's agent tool policy and records it in
221
355
  the flight recorder against the pipeline's identity. Without that variable `kt run` uses your own
222
- stored session over REST, unchanged. `--dry-run` needs neither.
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.
223
361
 
224
362
  Two things differ on the pipeline path. A `get` step receives the file as broker-redacted UTF-8 text
225
363
  capped at 1MB rather than raw bytes, and the step says so in its output — do not use it to move a