@kutrion/cli 1.2.11 → 1.2.14

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 +8 -0
  2. package/dist/index.js +480 -241
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24054,6 +24054,49 @@ function auditResultLabel(raw) {
24054
24054
  return humanizeAction(raw);
24055
24055
  }
24056
24056
 
24057
+ // ../packages/shared/src/refresh-refusal-copy.ts
24058
+ init_define_KT_HUB_FILES();
24059
+ var REFRESH_REFUSAL_COPY = {
24060
+ refresh_hub_signed_out: { title: "This session was signed out", why: "You signed out in Kutrion Hub." },
24061
+ refresh_hub_revoked: { title: "This session was signed out", why: "This app was signed out because its Kutrion Hub was removed." },
24062
+ refresh_credentials_changed: {
24063
+ title: "Your password changed",
24064
+ why: "Your password changed, so Kutrion signed this {app} out.",
24065
+ action: "Sign in again with your new password."
24066
+ },
24067
+ device_revoked: {
24068
+ title: "This device was signed out",
24069
+ why: "This {app}'s access was withdrawn from your Kutrion account.",
24070
+ action: "Sign in again to re-register it."
24071
+ },
24072
+ account_inactive: {
24073
+ title: "This account has ended",
24074
+ why: "Your Kutrion account is no longer active, so this {app} was signed out.",
24075
+ action: "Your administrator can restore it. Signing in again won't work until they do."
24076
+ },
24077
+ user_inactive: {
24078
+ title: "This account has ended",
24079
+ why: "Your Kutrion account is no longer active, so this {app} was signed out.",
24080
+ action: "Your administrator can restore it. Signing in again won't work until they do."
24081
+ },
24082
+ account_expired: {
24083
+ title: "This account has expired",
24084
+ why: "Your Kutrion account has expired, so this {app} was signed out.",
24085
+ action: "Your administrator can restore it. Signing in again won't work until they do."
24086
+ },
24087
+ org_mismatch: {
24088
+ title: "This session was signed out",
24089
+ why: "You're no longer a member of the organization this {app} was signed in to.",
24090
+ action: "Sign in again to continue with your current organization."
24091
+ }
24092
+ };
24093
+ function refreshRefusalCopy(code, noun = "app") {
24094
+ if (typeof code !== "string" || !Object.prototype.hasOwnProperty.call(REFRESH_REFUSAL_COPY, code)) return null;
24095
+ const c = REFRESH_REFUSAL_COPY[code];
24096
+ const fill = (s) => s.replace(/\{app\}/g, noun);
24097
+ return { title: c.title, why: fill(c.why), ...c.action ? { action: fill(c.action) } : {} };
24098
+ }
24099
+
24057
24100
  // ../packages/shared/src/client.ts
24058
24101
  init_define_KT_HUB_FILES();
24059
24102
 
@@ -27621,6 +27664,9 @@ var ApiError = class extends Error {
27621
27664
  this.headers = headers;
27622
27665
  this.name = "ApiError";
27623
27666
  }
27667
+ // #3445: the `error` code /auth/refresh gave when it ended the session behind this 401, set only by
27668
+ // the SDK's own refresh-then-replay path. `body` and `message` stay the original request's.
27669
+ refreshCode;
27624
27670
  // #2747: the error class slug — the server's `error` code, or the code the SDK minted itself — or
27625
27671
  // null when the reply carried none (the message is then a status phrase, not a code).
27626
27672
  get slug() {
@@ -28053,15 +28099,22 @@ var KutrionClient = class {
28053
28099
  });
28054
28100
  const { body: signed, signerThrew } = await this.signedRefreshBody(rt);
28055
28101
  let res = await post(signed);
28056
- if (res.status === 401 && signed.signature && isRefreshSignatureRefusal(await readBodyWithin(res, REFRESH_BODY_MS))) {
28057
- res = await post(this.refreshBody(rt));
28058
- if (res.ok) this.refreshSigningRefused = true;
28102
+ let refusalBody;
28103
+ if (res.status === 401 && signed.signature) {
28104
+ refusalBody = await readBodyWithin(res, REFRESH_BODY_MS);
28105
+ if (isRefreshSignatureRefusal(refusalBody)) {
28106
+ res = await post(this.refreshBody(rt));
28107
+ refusalBody = void 0;
28108
+ if (res.ok) this.refreshSigningRefused = true;
28109
+ }
28059
28110
  }
28060
28111
  if (res.status === 401) {
28061
- if (signerThrew && !signed.signature && isRefreshSignatureRefusal(await readBodyWithin(res, REFRESH_BODY_MS))) {
28112
+ if (refusalBody === void 0) refusalBody = await readBodyWithin(res, REFRESH_BODY_MS);
28113
+ if (signerThrew && !signed.signature && isRefreshSignatureRefusal(refusalBody)) {
28062
28114
  return { outcome: "indeterminate", status: 401 };
28063
28115
  }
28064
- return { outcome: "rejected", status: 401 };
28116
+ const code = refusalBody?.error;
28117
+ return typeof code === "string" && code ? { outcome: "rejected", status: 401, code } : { outcome: "rejected", status: 401 };
28065
28118
  }
28066
28119
  if (!res.ok) {
28067
28120
  const body = res.status === 403 ? await readBodyWithin(res, REFRESH_BODY_MS) : void 0;
@@ -28147,6 +28200,7 @@ var KutrionClient = class {
28147
28200
  data = JSON.parse(await decryptEnvelope(this.sessionKey, data.enc));
28148
28201
  }
28149
28202
  if (!res.ok) {
28203
+ let refreshCode;
28150
28204
  if (res.status === 401 && auth) {
28151
28205
  if (_retry) {
28152
28206
  this.onUnauthorized?.();
@@ -28156,6 +28210,9 @@ var KutrionClient = class {
28156
28210
  case "refreshed":
28157
28211
  return this.req(method, path, body, auth, true);
28158
28212
  case "rejected":
28213
+ refreshCode = r.code;
28214
+ this.onUnauthorized?.(r.code);
28215
+ break;
28159
28216
  case "no_token":
28160
28217
  this.onUnauthorized?.();
28161
28218
  break;
@@ -28170,7 +28227,9 @@ var KutrionClient = class {
28170
28227
  }
28171
28228
  }
28172
28229
  }
28173
- throw new ApiError(res.status, apiErrorMessage(res.status, data), data, res.headers);
28230
+ const err = new ApiError(res.status, apiErrorMessage(res.status, data), data, res.headers);
28231
+ if (refreshCode) err.refreshCode = refreshCode;
28232
+ throw err;
28174
28233
  }
28175
28234
  return data;
28176
28235
  }
@@ -28420,7 +28479,7 @@ var KutrionClient = class {
28420
28479
  // #2578: clears a factor's pending-approval mark. A step-up-pending session is refused with 403
28421
28480
  // step_up_required; `approved: false` means the factor was already approved.
28422
28481
  approveFactor(kind, id) {
28423
- return this.req("POST", `/auth/factors/${kind}/${encodeURIComponent(id)}/approve`);
28482
+ return this.req("POST", `/auth/factors/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/approve`);
28424
28483
  }
28425
28484
  mfaGenerateBackupCodes() {
28426
28485
  return this.req("POST", "/auth/mfa/backup-codes/generate");
@@ -28447,7 +28506,7 @@ var KutrionClient = class {
28447
28506
  // (the iOS AutoFill extension) so it rotates independently of the app and never trips the app's
28448
28507
  // reuse-detection. The extension persists its own rotations; the app must not share its token.
28449
28508
  provisionCompanionSession(deviceId) {
28450
- return this.req("POST", `/auth/devices/${deviceId}/companion`, {});
28509
+ return this.req("POST", `/auth/devices/${encodeURIComponent(deviceId)}/companion`, {});
28451
28510
  }
28452
28511
  // #768: `drain_limits` is what each device's `drain.state` may and may not be said to prove. The
28453
28512
  // server ships the sentence so three clients cannot each invent their own; optional here only
@@ -28456,7 +28515,7 @@ var KutrionClient = class {
28456
28515
  return this.req("GET", "/auth/devices");
28457
28516
  }
28458
28517
  revokeDevice(id) {
28459
- return this.req("DELETE", `/auth/devices/${id}`, void 0);
28518
+ return this.req("DELETE", `/auth/devices/${encodeURIComponent(id)}`, void 0);
28460
28519
  }
28461
28520
  // Rotate tokens explicitly (req() also does this transparently on 401).
28462
28521
  async refreshTokens(refreshToken) {
@@ -28493,7 +28552,7 @@ var KutrionClient = class {
28493
28552
  // nowhere else afterwards, so a caller that drops it must either re-issue (same key while it lives)
28494
28553
  // or rotate — and rotating deliberately kills whatever the old key was protecting.
28495
28554
  issueCacheKey(deviceId, opts2) {
28496
- return this.req("POST", `/auth/devices/${deviceId}/cache-key`, { rotate: opts2?.rotate === true });
28555
+ return this.req("POST", `/auth/devices/${encodeURIComponent(deviceId)}/cache-key`, { rotate: opts2?.rotate === true });
28497
28556
  }
28498
28557
  // #712: drain this device's local reveal journal. The batch is MAC'd under the cache KEK; a broken
28499
28558
  // chain answers 409 with the head the server holds, never a silent success.
@@ -28508,10 +28567,10 @@ var KutrionClient = class {
28508
28567
  return this.req("POST", "/audit/journal-discarded", input);
28509
28568
  }
28510
28569
  cacheKeyStatus(deviceId) {
28511
- return this.req("GET", `/auth/devices/${deviceId}/cache-key`);
28570
+ return this.req("GET", `/auth/devices/${encodeURIComponent(deviceId)}/cache-key`);
28512
28571
  }
28513
28572
  revokeCacheKey(deviceId) {
28514
- return this.req("DELETE", `/auth/devices/${deviceId}/cache-key`);
28573
+ return this.req("DELETE", `/auth/devices/${encodeURIComponent(deviceId)}/cache-key`);
28515
28574
  }
28516
28575
  listCacheKeys() {
28517
28576
  return this.req("GET", "/auth/cache-keys");
@@ -28532,21 +28591,24 @@ var KutrionClient = class {
28532
28591
  return this.req("GET", "/auth/webauthn");
28533
28592
  }
28534
28593
  deletePasskey(id) {
28535
- return this.req("DELETE", `/auth/webauthn/${id}`);
28594
+ return this.req("DELETE", `/auth/webauthn/${encodeURIComponent(id)}`);
28595
+ }
28596
+ renamePasskey(id, label) {
28597
+ return this.req("PATCH", `/auth/webauthn/${encodeURIComponent(id)}`, { label });
28536
28598
  }
28537
28599
  // VT-120: linked sign-in methods (SSO/social identities) + set a first password for social-only accounts.
28538
28600
  listIdentities() {
28539
28601
  return this.req("GET", "/auth/identities");
28540
28602
  }
28541
28603
  unlinkIdentity(id) {
28542
- return this.req("DELETE", `/auth/identities/${id}`);
28604
+ return this.req("DELETE", `/auth/identities/${encodeURIComponent(id)}`);
28543
28605
  }
28544
28606
  // #2678: begin linking a platform provider. The browser then navigates to `url`; the callback hands
28545
28607
  // back a handle that completeIdentityLink attaches from this same session. #2847: a native app passes
28546
28608
  // its registered client and the challenge for a verifier it keeps, then completes with that verifier.
28547
28609
  startIdentityLink(ref, binding, currentPassword) {
28548
28610
  const how = typeof binding === "string" ? { cb: binding } : { native: true, native_client: binding.nativeClient, native_challenge: binding.challenge };
28549
- return this.req("POST", `/auth/sso/${ref}/link`, { ...how, ...currentPassword ? { current_password: currentPassword } : {} });
28611
+ return this.req("POST", `/auth/sso/${encodeURIComponent(ref)}/link`, { ...how, ...currentPassword ? { current_password: currentPassword } : {} });
28550
28612
  }
28551
28613
  completeIdentityLink(handle, verifier) {
28552
28614
  return this.req("POST", "/auth/sso/link/complete", { handle, ...verifier ? { verifier } : {} });
@@ -28587,15 +28649,15 @@ var KutrionClient = class {
28587
28649
  // Works for both OIDC and SAML providers — /start dispatches on the provider's protocol. `ref`
28588
28650
  // is the provider id (preferred, unambiguous across orgs) or a platform provider's slug.
28589
28651
  ssoStartUrl(ref) {
28590
- return `${this.baseUrl}/auth/sso/${ref}/start`;
28652
+ return `${this.baseUrl}/auth/sso/${encodeURIComponent(ref)}/start`;
28591
28653
  }
28592
28654
  // SP metadata URL to hand a SAML IdP (entityID + ACS endpoint). Keyed by provider id.
28593
28655
  samlMetadataUrl(ref) {
28594
- return `${this.baseUrl}/auth/sso/${ref}/saml/metadata`;
28656
+ return `${this.baseUrl}/auth/sso/${encodeURIComponent(ref)}/saml/metadata`;
28595
28657
  }
28596
28658
  // OIDC redirect URI to register at the IdP, keyed by provider id (per-org safe).
28597
28659
  ssoCallbackUrl(ref) {
28598
- return `${this.baseUrl}/auth/sso/${ref}/callback`;
28660
+ return `${this.baseUrl}/auth/sso/${encodeURIComponent(ref)}/callback`;
28599
28661
  }
28600
28662
  // --- SSO provider admin (MT-3, org admin/owner only; secrets are write-only) ---
28601
28663
  listSsoProvidersAdmin() {
@@ -28686,10 +28748,10 @@ var KutrionClient = class {
28686
28748
  return this.req("POST", "/workspaces", input);
28687
28749
  }
28688
28750
  updateWorkspace(id, input) {
28689
- return this.req("PUT", `/workspaces/${id}`, input);
28751
+ return this.req("PUT", `/workspaces/${encodeURIComponent(id)}`, input);
28690
28752
  }
28691
28753
  deleteWorkspace(id) {
28692
- return this.req("DELETE", `/workspaces/${id}`);
28754
+ return this.req("DELETE", `/workspaces/${encodeURIComponent(id)}`);
28693
28755
  }
28694
28756
  // --- relay (#458 vault-synced API-client documents; #464 team grants) ---
28695
28757
  listRelayDocuments() {
@@ -28705,7 +28767,7 @@ var KutrionClient = class {
28705
28767
  return this.req("POST", `/relay/documents/${encodeURIComponent(clientId)}/grants`, { team_id, role });
28706
28768
  }
28707
28769
  relayUnshare(clientId, teamId) {
28708
- return this.req("DELETE", `/relay/documents/${encodeURIComponent(clientId)}/grants/${teamId}`);
28770
+ return this.req("DELETE", `/relay/documents/${encodeURIComponent(clientId)}/grants/${encodeURIComponent(teamId)}`);
28709
28771
  }
28710
28772
  // --- vault ---
28711
28773
  listVaults() {
@@ -28715,13 +28777,13 @@ var KutrionClient = class {
28715
28777
  return this.req("POST", "/vault/create", { name, team_id });
28716
28778
  }
28717
28779
  renameVault(vaultId, name) {
28718
- return this.req("PATCH", `/vault/${vaultId}`, { name });
28780
+ return this.req("PATCH", `/vault/${encodeURIComponent(vaultId)}`, { name });
28719
28781
  }
28720
28782
  deleteVault(vaultId) {
28721
- return this.req("DELETE", `/vault/${vaultId}`);
28783
+ return this.req("DELETE", `/vault/${encodeURIComponent(vaultId)}`);
28722
28784
  }
28723
28785
  listCredentials(vaultId) {
28724
- return this.req("GET", `/vault/${vaultId}/credentials`);
28786
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials`);
28725
28787
  }
28726
28788
  // #1122: listVaults + every listCredentials in one round trip, with the same permissions gating.
28727
28789
  // Metadata only — revealing a secret is still the audited per-credential call.
@@ -28747,19 +28809,19 @@ var KutrionClient = class {
28747
28809
  return this.req("GET", `/vault/host-matches?host=${encodeURIComponent(host2)}`);
28748
28810
  }
28749
28811
  getCredential(vaultId, cid) {
28750
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}`);
28812
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}`);
28751
28813
  }
28752
28814
  // #717: `mutation_id` and `base_updated_at` are what a replayed offline write adds — the first
28753
28815
  // makes a retry a no-op, the second lets the server tell an edit from an overwrite.
28754
28816
  storeCredential(vaultId, input) {
28755
- return this.req("POST", `/vault/${vaultId}/credentials`, input);
28817
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials`, input);
28756
28818
  }
28757
28819
  updateCredential(vaultId, cid, input) {
28758
- return this.req("PATCH", `/vault/${vaultId}/credentials/${cid}`, input);
28820
+ return this.req("PATCH", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}`, input);
28759
28821
  }
28760
28822
  deleteCredential(vaultId, cid, opts2) {
28761
28823
  const q = opts2?.mutation_id ? `?mutation_id=${encodeURIComponent(opts2.mutation_id)}` : "";
28762
- return this.req("DELETE", `/vault/${vaultId}/credentials/${cid}${q}`);
28824
+ return this.req("DELETE", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}` + q);
28763
28825
  }
28764
28826
  // #2622: snippets. A brokered shell runs one over its socket (snippetFrame); useSnippet is for a
28765
28827
  // terminal the server does not broker, and records the use the same way.
@@ -28787,23 +28849,23 @@ var KutrionClient = class {
28787
28849
  return this.req("GET", `/vault/conflicts${opts2?.open === false ? "?open=false" : ""}`);
28788
28850
  }
28789
28851
  acknowledgeConflict(id) {
28790
- return this.req("POST", `/vault/conflicts/${id}/ack`);
28852
+ return this.req("POST", `/vault/conflicts/${encodeURIComponent(id)}/ack`);
28791
28853
  }
28792
28854
  conflictPrevious(id) {
28793
- return this.req("GET", `/vault/conflicts/${id}/previous`);
28855
+ return this.req("GET", `/vault/conflicts/${encodeURIComponent(id)}/previous`);
28794
28856
  }
28795
28857
  // VT-487: group RBAC on a vault (collection) + item folders.
28796
28858
  listVaultGrants(vaultId) {
28797
- return this.req("GET", `/vault/${vaultId}/grants`);
28859
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/grants`);
28798
28860
  }
28799
28861
  grantVaultToTeam(vaultId, input) {
28800
- return this.req("POST", `/vault/${vaultId}/grants`, input);
28862
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/grants`, input);
28801
28863
  }
28802
28864
  revokeVaultGrant(vaultId, grantId) {
28803
- return this.req("DELETE", `/vault/${vaultId}/grants/${grantId}`);
28865
+ return this.req("DELETE", `/vault/${encodeURIComponent(vaultId)}/grants/${encodeURIComponent(grantId)}`);
28804
28866
  }
28805
28867
  listVaultFolders(vaultId) {
28806
- return this.req("GET", `/vault/${vaultId}/folders`);
28868
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/folders`);
28807
28869
  }
28808
28870
  // #1169: the credential's destination allow-list — where its secret may be presented. The routes
28809
28871
  // live in their own module mounted on /vault (server/src/index.ts:310), so the paths hang off the
@@ -28820,20 +28882,20 @@ var KutrionClient = class {
28820
28882
  // No write response carries the three list-only fields; re-list rather than deriving them.
28821
28883
  // #2634: who revealed or used one credential. Vault owners and admins only; never carries the value.
28822
28884
  credentialProvenance(vaultId, cid) {
28823
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}/provenance`);
28885
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/provenance`);
28824
28886
  }
28825
28887
  // #3074: password history. The list carries no values; revealing one is a gated read like getCredential.
28826
28888
  listCredentialHistory(vaultId, cid) {
28827
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}/history`);
28889
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/history`);
28828
28890
  }
28829
28891
  revealCredentialHistory(vaultId, cid, hid) {
28830
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}/history/${hid}`);
28892
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/history/${encodeURIComponent(hid)}`);
28831
28893
  }
28832
28894
  restoreCredentialHistory(vaultId, cid, hid) {
28833
- return this.req("POST", `/vault/${vaultId}/credentials/${cid}/history/${hid}/restore`);
28895
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/history/${encodeURIComponent(hid)}/restore`);
28834
28896
  }
28835
28897
  listCredentialDestinations(vaultId, cid) {
28836
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}/destinations`);
28898
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations`);
28837
28899
  }
28838
28900
  // `port` omitted means this protocol's default, resolved and stored server-side — NOT a wildcard, and
28839
28901
  // not something to fill in here: a client-supplied default would come back as a chosen port.
@@ -28849,7 +28911,7 @@ var KutrionClient = class {
28849
28911
  // names it. Sent on any other protocol it answers 400 `domain_not_supported`; unexpressible values
28850
28912
  // answer 400 `invalid_domain`.
28851
28913
  addCredentialDestination(vaultId, cid, input) {
28852
- return this.req("POST", `/vault/${vaultId}/credentials/${cid}/destinations`, input);
28914
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations`, input);
28853
28915
  }
28854
28916
  // Gated on the secret for the same reason adding is, but NOT because removal widens — #1281 reversed
28855
28917
  // that. Emptying a protocol while rows survive elsewhere leaves the credential claimed, so that
@@ -28861,7 +28923,7 @@ var KutrionClient = class {
28861
28923
  removeCredentialDestination(vaultId, cid, destinationId, secret) {
28862
28924
  return this.req(
28863
28925
  "DELETE",
28864
- `/vault/${vaultId}/credentials/${cid}/destinations/${destinationId}`,
28926
+ `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations/${encodeURIComponent(destinationId)}`,
28865
28927
  { secret }
28866
28928
  );
28867
28929
  }
@@ -28901,20 +28963,20 @@ var KutrionClient = class {
28901
28963
  return this.req("POST", "/vault/breach-alerts/scan");
28902
28964
  }
28903
28965
  updateBreachAlert(id, status) {
28904
- return this.req("PATCH", `/vault/breach-alerts/${id}`, { status });
28966
+ return this.req("PATCH", `/vault/breach-alerts/${encodeURIComponent(id)}`, { status });
28905
28967
  }
28906
28968
  setBreachMonitor(enabled) {
28907
28969
  return this.req("PUT", "/vault/breach-alerts/settings", { enabled });
28908
28970
  }
28909
28971
  importEnv(vaultId, name, dotenv) {
28910
- return this.req("POST", `/vault/${vaultId}/env-import`, { name, dotenv });
28972
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/env-import`, { name, dotenv });
28911
28973
  }
28912
28974
  readEnv(vaultId, cid) {
28913
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}/env`);
28975
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/env`);
28914
28976
  }
28915
28977
  // #96: live TOTP code for a TOTP credential (secret stays on the broker).
28916
28978
  getTotpCode(vaultId, cid) {
28917
- return this.req("GET", `/vault/${vaultId}/credentials/${cid}/totp`);
28979
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/totp`);
28918
28980
  }
28919
28981
  // --- passkey provider (VT-405g): Kutrion as a roaming WebAuthn authenticator for OTHER sites
28920
28982
  // (distinct from listPasskeys/deletePasskey above, which are passkeys for logging into Kutrion). ---
@@ -28935,7 +28997,7 @@ var KutrionClient = class {
28935
28997
  return this.req("POST", "/share", { credential_id: credentialId, ...opts2 });
28936
28998
  }
28937
28999
  redeemShare(token) {
28938
- return this.req("GET", `/share/${token}`, void 0, false);
29000
+ return this.req("GET", `/share/${encodeURIComponent(token)}`, void 0, false);
28939
29001
  }
28940
29002
  // #869: with a credential id, every link issued for that credential (needs read on it); without
28941
29003
  // one, only the links the caller issued.
@@ -28943,14 +29005,14 @@ var KutrionClient = class {
28943
29005
  return this.req("GET", `/share${credentialId ? `?credential_id=${encodeURIComponent(credentialId)}` : ""}`);
28944
29006
  }
28945
29007
  revokeShare(id) {
28946
- return this.req("DELETE", `/share/${id}`);
29008
+ return this.req("DELETE", `/share/${encodeURIComponent(id)}`);
28947
29009
  }
28948
29010
  // --- import / export ---
28949
29011
  exportVault(vaultId) {
28950
- return this.req("GET", `/vault/${vaultId}/export`);
29012
+ return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/export`);
28951
29013
  }
28952
29014
  importVault(vaultId, items) {
28953
- return this.req("POST", `/vault/${vaultId}/import`, { items });
29015
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/import`, { items });
28954
29016
  }
28955
29017
  importProviders() {
28956
29018
  return this.req("GET", "/vault/import/providers");
@@ -28988,7 +29050,7 @@ var KutrionClient = class {
28988
29050
  * a move's `enabled:false` after the caller's fields precisely so one request can never both repoint a
28989
29051
  * partner and turn it back on, and a client that batched them would be asking to be refused. */
28990
29052
  setAs2PartnerEnabled(id, enabled) {
28991
- return this.req("PATCH", `/as2/partners/${id}`, { enabled });
29053
+ return this.req("PATCH", `/as2/partners/${encodeURIComponent(id)}`, { enabled });
28992
29054
  }
28993
29055
  listAs2Messages() {
28994
29056
  return this.req("GET", "/as2/messages");
@@ -29001,33 +29063,33 @@ var KutrionClient = class {
29001
29063
  return this.req("POST", "/teams", { name });
29002
29064
  }
29003
29065
  listMembers(teamId) {
29004
- return this.req("GET", `/teams/${teamId}/members`);
29066
+ return this.req("GET", `/teams/${encodeURIComponent(teamId)}/members`);
29005
29067
  }
29006
29068
  addMember(teamId, email, role) {
29007
- return this.req("POST", `/teams/${teamId}/members`, { email, role });
29069
+ return this.req("POST", `/teams/${encodeURIComponent(teamId)}/members`, { email, role });
29008
29070
  }
29009
29071
  removeMember(teamId, userId) {
29010
- return this.req("DELETE", `/teams/${teamId}/members/${userId}`);
29072
+ return this.req("DELETE", `/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`);
29011
29073
  }
29012
29074
  // #2775: org owner or admin only. 409 team_has_vaults or scim_managed.
29013
29075
  deleteTeam(teamId) {
29014
- return this.req("DELETE", `/teams/${teamId}`);
29076
+ return this.req("DELETE", `/teams/${encodeURIComponent(teamId)}`);
29015
29077
  }
29016
29078
  // --- team invitations (VT-102) ---
29017
29079
  inviteMember(teamId, email, role) {
29018
- return this.req("POST", `/teams/${teamId}/invitations`, { email, role });
29080
+ return this.req("POST", `/teams/${encodeURIComponent(teamId)}/invitations`, { email, role });
29019
29081
  }
29020
29082
  listInvitations(teamId) {
29021
- return this.req("GET", `/teams/${teamId}/invitations`);
29083
+ return this.req("GET", `/teams/${encodeURIComponent(teamId)}/invitations`);
29022
29084
  }
29023
29085
  revokeInvitation(teamId, invId) {
29024
- return this.req("DELETE", `/teams/${teamId}/invitations/${invId}`);
29086
+ return this.req("DELETE", `/teams/${encodeURIComponent(teamId)}/invitations/${encodeURIComponent(invId)}`);
29025
29087
  }
29026
29088
  invitePreview(token) {
29027
- return this.req("GET", `/teams/invitations/${token}`, void 0, false);
29089
+ return this.req("GET", `/teams/invitations/${encodeURIComponent(token)}`, void 0, false);
29028
29090
  }
29029
29091
  acceptInvitation(token) {
29030
- return this.req("POST", `/teams/invitations/${token}/accept`);
29092
+ return this.req("POST", `/teams/invitations/${encodeURIComponent(token)}/accept`);
29031
29093
  }
29032
29094
  // --- org onboarding (MT-4 #115): admin/owner-gated unless noted ---
29033
29095
  getOrg() {
@@ -29088,10 +29150,10 @@ var KutrionClient = class {
29088
29150
  return this.req("POST", "/emergency/contacts", input);
29089
29151
  }
29090
29152
  removeEmergencyContact(id) {
29091
- return this.req("DELETE", `/emergency/contacts/${id}`);
29153
+ return this.req("DELETE", `/emergency/contacts/${encodeURIComponent(id)}`);
29092
29154
  }
29093
29155
  denyEmergencyRequest(id) {
29094
- return this.req("POST", `/emergency/requests/${id}/deny`);
29156
+ return this.req("POST", `/emergency/requests/${encodeURIComponent(id)}/deny`);
29095
29157
  }
29096
29158
  listEmergencyGrants() {
29097
29159
  return this.req("GET", "/emergency/grants");
@@ -29100,10 +29162,10 @@ var KutrionClient = class {
29100
29162
  return this.req("POST", "/emergency/grants/accept", { token });
29101
29163
  }
29102
29164
  requestEmergencyAccess(grantId) {
29103
- return this.req("POST", `/emergency/grants/${grantId}/request`);
29165
+ return this.req("POST", `/emergency/grants/${encodeURIComponent(grantId)}/request`);
29104
29166
  }
29105
29167
  completeEmergencyAccess(requestId) {
29106
- return this.req("POST", `/emergency/requests/${requestId}/complete`);
29168
+ return this.req("POST", `/emergency/requests/${encodeURIComponent(requestId)}/complete`);
29107
29169
  }
29108
29170
  // VT-443: the user-facing announcement feed (filtered server-side by the org's tier/status).
29109
29171
  listAnnouncements() {
@@ -29193,7 +29255,7 @@ var KutrionClient = class {
29193
29255
  // #1959: org-admin surface grants for one member of the caller's own org. Distinct from
29194
29256
  // selfGrantSurface, which only ever acts on the caller.
29195
29257
  listUserSurfaceGrants(userId) {
29196
- return this.req("GET", `/surfaces/users/${userId}`);
29258
+ return this.req("GET", `/surfaces/users/${encodeURIComponent(userId)}`);
29197
29259
  }
29198
29260
  grantUserSurface(userId, surface, opts2) {
29199
29261
  return this.req("POST", "/surfaces/grants", {
@@ -29294,15 +29356,15 @@ var KutrionClient = class {
29294
29356
  return this.req("GET", "/org/members");
29295
29357
  }
29296
29358
  setOrgMemberRole(id, org_role) {
29297
- return this.req("PATCH", `/org/members/${id}`, { org_role });
29359
+ return this.req("PATCH", `/org/members/${encodeURIComponent(id)}`, { org_role });
29298
29360
  }
29299
29361
  removeOrgMember(id) {
29300
- return this.req("DELETE", `/org/members/${id}`);
29362
+ return this.req("DELETE", `/org/members/${encodeURIComponent(id)}`);
29301
29363
  }
29302
29364
  // VT-483: admin-mediated recovery — email a locked-out member a password-reset link/code. The admin
29303
29365
  // never sets or learns the new password.
29304
29366
  resetOrgMemberPassword(id) {
29305
- return this.req("POST", `/org/members/${id}/password-reset`);
29367
+ return this.req("POST", `/org/members/${encodeURIComponent(id)}/password-reset`);
29306
29368
  }
29307
29369
  // #1433: admin-mediated ACCOUNT RECOVERY, for a member who cannot sign in AT ALL. Distinct from the
29308
29370
  // password reset above, which refuses exactly the accounts that most need it: an SSO- or
@@ -29314,7 +29376,7 @@ var KutrionClient = class {
29314
29376
  // step_up_required), refuses a suspended member (409 member_inactive), and refuses a non-owner admin
29315
29377
  // who targets an owner (403 owner_recovery_requires_owner).
29316
29378
  recoverOrgMember(id) {
29317
- return this.req("POST", `/org/members/${id}/recover`);
29379
+ return this.req("POST", `/org/members/${encodeURIComponent(id)}/recover`);
29318
29380
  }
29319
29381
  inviteToOrg(email, org_role) {
29320
29382
  return this.req("POST", "/org/invitations", { email, org_role });
@@ -29323,13 +29385,13 @@ var KutrionClient = class {
29323
29385
  return this.req("GET", "/org/invitations");
29324
29386
  }
29325
29387
  revokeOrgInvitation(id) {
29326
- return this.req("DELETE", `/org/invitations/${id}`);
29388
+ return this.req("DELETE", `/org/invitations/${encodeURIComponent(id)}`);
29327
29389
  }
29328
29390
  orgInvitePreview(token) {
29329
- return this.req("GET", `/org/invitations/${token}`, void 0, false);
29391
+ return this.req("GET", `/org/invitations/${encodeURIComponent(token)}`, void 0, false);
29330
29392
  }
29331
29393
  async acceptOrgInvitation(token) {
29332
- const r = await this.req("POST", `/org/invitations/${token}/accept`);
29394
+ const r = await this.req("POST", `/org/invitations/${encodeURIComponent(token)}/accept`);
29333
29395
  this.onToken?.(r.token);
29334
29396
  return r;
29335
29397
  }
@@ -29342,18 +29404,18 @@ var KutrionClient = class {
29342
29404
  return this.req("POST", "/msp/clients", { client_email, role, ...expires_days ? { expires_days } : {} });
29343
29405
  }
29344
29406
  revokeMspClient(id) {
29345
- return this.req("POST", `/msp/clients/${id}/revoke`);
29407
+ return this.req("POST", `/msp/clients/${encodeURIComponent(id)}/revoke`);
29346
29408
  }
29347
29409
  // Mint a read-only "view as client" scoped session (#415c). The token is used as the active bearer;
29348
29410
  // the existing impersonation guard makes it read-only. Revoking the relationship kills it instantly.
29349
29411
  viewAsMspClient(id) {
29350
- return this.req("POST", `/msp/clients/${id}/view-as`);
29412
+ return this.req("POST", `/msp/clients/${encodeURIComponent(id)}/view-as`);
29351
29413
  }
29352
29414
  // #432: enter a WRITE-capable "act as client" session at the clamped effective role. Requires a fresh
29353
29415
  // step-up; writes are bounded by the deny-by-default server allowlist. Returns 403 step_up_required if
29354
29416
  // the operator has not recently verified a passkey/MFA, 409 read_only_relationship for a VIEWER ceiling.
29355
29417
  actAsMspClient(id, role = "EDITOR") {
29356
- return this.req("POST", `/msp/clients/${id}/act-as`, { role });
29418
+ return this.req("POST", `/msp/clients/${encodeURIComponent(id)}/act-as`, { role });
29357
29419
  }
29358
29420
  // #415d: the MSP's own access ledger across managed clients (sessions opened, last access).
29359
29421
  mspClientsActivity() {
@@ -29377,13 +29439,13 @@ var KutrionClient = class {
29377
29439
  return this.req("GET", "/msp/providers");
29378
29440
  }
29379
29441
  acceptMspProvider(id) {
29380
- return this.req("POST", `/msp/providers/${id}/accept`);
29442
+ return this.req("POST", `/msp/providers/${encodeURIComponent(id)}/accept`);
29381
29443
  }
29382
29444
  declineMspProvider(id) {
29383
- return this.req("POST", `/msp/providers/${id}/decline`);
29445
+ return this.req("POST", `/msp/providers/${encodeURIComponent(id)}/decline`);
29384
29446
  }
29385
29447
  revokeMspProvider(id) {
29386
- return this.req("POST", `/msp/providers/${id}/revoke`);
29448
+ return this.req("POST", `/msp/providers/${encodeURIComponent(id)}/revoke`);
29387
29449
  }
29388
29450
  // #434: what my providers did in my org (read-only-session opens etc.), for client-side transparency.
29389
29451
  mspProviderActivity() {
@@ -29417,13 +29479,13 @@ var KutrionClient = class {
29417
29479
  setMemberActive(memberId, active, reason) {
29418
29480
  return this.req(
29419
29481
  "PATCH",
29420
- `/org/members/${memberId}/active`,
29482
+ `/org/members/${encodeURIComponent(memberId)}/active`,
29421
29483
  { active, ...reason ? { reason } : {} }
29422
29484
  );
29423
29485
  }
29424
29486
  // Offboarding: kill every offline copy a member holds, from the admin side.
29425
29487
  revokeMemberCacheKeys(memberId) {
29426
- return this.req("DELETE", `/org/members/${memberId}/cache-keys`);
29488
+ return this.req("DELETE", `/org/members/${encodeURIComponent(memberId)}/cache-keys`);
29427
29489
  }
29428
29490
  // #815: the other half of that revoke — which devices came back and were told to wipe, and which
29429
29491
  // have not been seen since. Org-admin only.
@@ -29439,7 +29501,7 @@ var KutrionClient = class {
29439
29501
  return this.req("POST", "/org/equivalent-domains", { domains, note: note ?? null });
29440
29502
  }
29441
29503
  removeEquivalentDomains(id) {
29442
- return this.req("DELETE", `/org/equivalent-domains/${id}`);
29504
+ return this.req("DELETE", `/org/equivalent-domains/${encodeURIComponent(id)}`);
29443
29505
  }
29444
29506
  // #904: the caller's OWN remembered host bindings. Per user and per (credential, host) — never per
29445
29507
  // org, which would make phishing one member a lateral-movement primitive. Creation is screened
@@ -29449,10 +29511,10 @@ var KutrionClient = class {
29449
29511
  return this.req("GET", "/vault/site-bindings");
29450
29512
  }
29451
29513
  createSiteBinding(vaultId, cid, targetUrl, sourceUrl) {
29452
- return this.req("POST", `/vault/${vaultId}/credentials/${cid}/site-bindings`, { target_url: targetUrl, source_url: sourceUrl });
29514
+ return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/site-bindings`, { target_url: targetUrl, source_url: sourceUrl });
29453
29515
  }
29454
29516
  revokeSiteBinding(id) {
29455
- return this.req("DELETE", `/vault/site-bindings/${id}`);
29517
+ return this.req("DELETE", `/vault/site-bindings/${encodeURIComponent(id)}`);
29456
29518
  }
29457
29519
  listOrgDomains() {
29458
29520
  return this.req("GET", "/org/domains");
@@ -29461,10 +29523,10 @@ var KutrionClient = class {
29461
29523
  return this.req("POST", "/org/domains", { domain });
29462
29524
  }
29463
29525
  verifyOrgDomain(id) {
29464
- return this.req("POST", `/org/domains/${id}/verify`);
29526
+ return this.req("POST", `/org/domains/${encodeURIComponent(id)}/verify`);
29465
29527
  }
29466
29528
  removeOrgDomain(id) {
29467
- return this.req("DELETE", `/org/domains/${id}`);
29529
+ return this.req("DELETE", `/org/domains/${encodeURIComponent(id)}`);
29468
29530
  }
29469
29531
  // VT-485/486: enterprise API tokens for SCIM provisioning and SIEM event export. The secret is
29470
29532
  // returned only on create; thereafter only the prefix is shown.
@@ -29475,7 +29537,7 @@ var KutrionClient = class {
29475
29537
  return this.req("POST", "/api-tokens", input);
29476
29538
  }
29477
29539
  revokeApiToken(id) {
29478
- return this.req("DELETE", `/api-tokens/${id}`);
29540
+ return this.req("DELETE", `/api-tokens/${encodeURIComponent(id)}`);
29479
29541
  }
29480
29542
  // #662 agent access plane: non-human identities (#664), their activity (#665) and the kill
29481
29543
  // switch (#675). The credential is returned only on create; thereafter only the prefix is shown.
@@ -29490,23 +29552,23 @@ var KutrionClient = class {
29490
29552
  return this.req("POST", "/agents", input);
29491
29553
  }
29492
29554
  renewAgent(id, ttl_minutes) {
29493
- return this.req("POST", `/agents/${id}/renew`, { ttl_minutes });
29555
+ return this.req("POST", `/agents/${encodeURIComponent(id)}/renew`, { ttl_minutes });
29494
29556
  }
29495
29557
  revokeAgent(id) {
29496
- return this.req("DELETE", `/agents/${id}`);
29558
+ return this.req("DELETE", `/agents/${encodeURIComponent(id)}`);
29497
29559
  }
29498
29560
  // #2700: PUT replaces the whole resource scope; an omitted or empty list means unrestricted.
29499
29561
  agentScopeOptions() {
29500
29562
  return this.req("GET", "/agents/scope-options");
29501
29563
  }
29502
29564
  setAgentResourceScope(id, scope) {
29503
- return this.req("PUT", `/agents/${id}/resource-scope`, scope);
29565
+ return this.req("PUT", `/agents/${encodeURIComponent(id)}/resource-scope`, scope);
29504
29566
  }
29505
29567
  listAgentOidcTrusts() {
29506
29568
  return this.req("GET", "/agents/oidc/trusts");
29507
29569
  }
29508
29570
  setAgentOidcTrustResourceScope(id, scope, opts2 = {}) {
29509
- return this.req("PUT", `/agents/oidc/trusts/${id}/resource-scope`, { ...scope, ...opts2 });
29571
+ return this.req("PUT", `/agents/oidc/trusts/${encodeURIComponent(id)}/resource-scope`, { ...scope, ...opts2 });
29510
29572
  }
29511
29573
  revokeAgents(input) {
29512
29574
  return this.req(
@@ -29523,7 +29585,7 @@ var KutrionClient = class {
29523
29585
  return this.req("GET", qs ? `/agents/sessions?${qs}` : "/agents/sessions");
29524
29586
  }
29525
29587
  getAgentSession(id) {
29526
- return this.req("GET", `/agents/sessions/${id}`);
29588
+ return this.req("GET", `/agents/sessions/${encodeURIComponent(id)}`);
29527
29589
  }
29528
29590
  listAgentActivity(opts2 = {}) {
29529
29591
  const q = new URLSearchParams();
@@ -29533,7 +29595,7 @@ var KutrionClient = class {
29533
29595
  }
29534
29596
  /** Relative path for the auditor export; the caller downloads it with its own auth. */
29535
29597
  agentSessionExportPath(id) {
29536
- return `/agents/sessions/${id}/export`;
29598
+ return `/agents/sessions/${encodeURIComponent(id)}/export`;
29537
29599
  }
29538
29600
  // #669: the human half of just-in-time agent approval. These routes are deliberately unreachable
29539
29601
  // from an MCP client — the agent opens a request and settles nothing, a person in an authenticated
@@ -29544,13 +29606,13 @@ var KutrionClient = class {
29544
29606
  return this.req("GET", "/agent-approvals");
29545
29607
  }
29546
29608
  getAgentApproval(id) {
29547
- return this.req("GET", `/agent-approvals/${id}`);
29609
+ return this.req("GET", `/agent-approvals/${encodeURIComponent(id)}`);
29548
29610
  }
29549
29611
  approveAgentApproval(id, note) {
29550
- return this.req("POST", `/agent-approvals/${id}/approve`, note ? { note } : {});
29612
+ return this.req("POST", `/agent-approvals/${encodeURIComponent(id)}/approve`, note ? { note } : {});
29551
29613
  }
29552
29614
  denyAgentApproval(id, note) {
29553
- return this.req("POST", `/agent-approvals/${id}/deny`, note ? { note } : {});
29615
+ return this.req("POST", `/agent-approvals/${encodeURIComponent(id)}/deny`, note ? { note } : {});
29554
29616
  }
29555
29617
  // #670 (epic #662): the outbound MCP gateway's control plane. Curation is ORG ADMIN; only the
29556
29618
  // report-what-I-run call is open to any member, and that asymmetry is deliberate — the machines
@@ -29573,14 +29635,14 @@ var KutrionClient = class {
29573
29635
  /** Aim a credential at an approved entry. Authorised by WRITE on the credential's own vault, not by
29574
29636
  * org admin — binding is an act of delegation, so 403 here is about the vault and not the console. */
29575
29637
  bindMcpCredential(entryId, credential_id) {
29576
- return this.req("POST", `/mcp/gateway/servers/${entryId}/credential`, { credential_id });
29638
+ return this.req("POST", `/mcp/gateway/servers/${encodeURIComponent(entryId)}/credential`, { credential_id });
29577
29639
  }
29578
29640
  unbindMcpCredential(entryId) {
29579
- return this.req("DELETE", `/mcp/gateway/servers/${entryId}/credential`);
29641
+ return this.req("DELETE", `/mcp/gateway/servers/${encodeURIComponent(entryId)}/credential`);
29580
29642
  }
29581
29643
  /** Stops traffic on the next call, with nothing cached in between. */
29582
29644
  revokeMcpServer(entryId) {
29583
- return this.req("DELETE", `/mcp/gateway/servers/${entryId}`);
29645
+ return this.req("DELETE", `/mcp/gateway/servers/${encodeURIComponent(entryId)}`);
29584
29646
  }
29585
29647
  listMcpObserved() {
29586
29648
  return this.req("GET", "/mcp/gateway/observed");
@@ -29613,10 +29675,10 @@ var KutrionClient = class {
29613
29675
  // which leaves it as it is — a connection could otherwise never be made direct again.
29614
29676
  // #2621: `vault_id` shares a personal host into that vault, one way; its credential must live there.
29615
29677
  updateConnection(connId, input) {
29616
- return this.req("PATCH", `/ssh/connections/${connId}`, input);
29678
+ return this.req("PATCH", `/ssh/connections/${encodeURIComponent(connId)}`, input);
29617
29679
  }
29618
29680
  deleteConnection(connId) {
29619
- return this.req("DELETE", `/ssh/connections/${connId}`);
29681
+ return this.req("DELETE", `/ssh/connections/${encodeURIComponent(connId)}`);
29620
29682
  }
29621
29683
  // #414: self-serve secure-session demo. status drives the empty-state affordance; provision is
29622
29684
  // idempotent (reattaches to an existing demo connection); reset removes the demo artifacts.
@@ -29632,7 +29694,7 @@ var KutrionClient = class {
29632
29694
  // Pre-flight risk guardrail (#141): a high-risk command 403s with { error: "step_up_required" }
29633
29695
  // until the session completes WebAuthn step-up; retry after stepping up.
29634
29696
  exec(connId, command) {
29635
- return this.req("POST", `/ssh/connections/${connId}/exec`, { command });
29697
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/exec`, { command });
29636
29698
  }
29637
29699
  validateCommand(command) {
29638
29700
  return this.req("POST", "/ssh/validate", { command });
@@ -29715,7 +29777,7 @@ var KutrionClient = class {
29715
29777
  }
29716
29778
  /** 403 step_up_required until the session has a fresh passkey/MFA check; retry after stepping up. */
29717
29779
  clearSshHostKey(id) {
29718
- return this.req("DELETE", `/ssh/host-keys/${id}`);
29780
+ return this.req("DELETE", `/ssh/host-keys/${encodeURIComponent(id)}`);
29719
29781
  }
29720
29782
  /**
29721
29783
  * #2743: pins the key a host_key_unconfirmed refusal showed this caller. For a first-use refusal the server accepts only the
@@ -29733,7 +29795,7 @@ var KutrionClient = class {
29733
29795
  }
29734
29796
  /** 404 when the session already ended, or is not in the caller's org. */
29735
29797
  endLiveSession(kind, id) {
29736
- return this.req("DELETE", `/org/live-sessions/${kind}/${encodeURIComponent(id)}`);
29798
+ return this.req("DELETE", `/org/live-sessions/${encodeURIComponent(kind)}/${encodeURIComponent(id)}`);
29737
29799
  }
29738
29800
  // --- SSH certificate authority (VT-484): mint short-lived user certs from a per-org internal CA ---
29739
29801
  getSshCa() {
@@ -29828,29 +29890,29 @@ var KutrionClient = class {
29828
29890
  // It does NOT mean "the whole directory": the routes default to limit=1000 and report `hasMore`.
29829
29891
  // --- SFTP file transfer (VT-112) ---
29830
29892
  sftpList(connId, path = ".", page) {
29831
- return this.req("GET", `/ssh/connections/${connId}/sftp/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
29893
+ return this.req("GET", `/ssh/connections/${encodeURIComponent(connId)}/sftp/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
29832
29894
  }
29833
29895
  sftpMkdir(connId, path) {
29834
- return this.req("POST", `/ssh/connections/${connId}/sftp/mkdir`, { path });
29896
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/mkdir`, { path });
29835
29897
  }
29836
29898
  sftpRename(connId, from, to) {
29837
- return this.req("POST", `/ssh/connections/${connId}/sftp/rename`, { from, to });
29899
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/rename`, { from, to });
29838
29900
  }
29839
29901
  sftpDelete(connId, path, isDir) {
29840
- return this.req("POST", `/ssh/connections/${connId}/sftp/delete`, { path, is_dir: isDir });
29902
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/delete`, { path, is_dir: isDir });
29841
29903
  }
29842
29904
  // #587: SFTP checksums + duplicate detection (mirror the file-connection routes).
29843
29905
  sftpChecksum(connId, path, algos = ["sha256"]) {
29844
- return this.req("GET", `/ssh/connections/${connId}/sftp/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
29906
+ return this.req("GET", `/ssh/connections/${encodeURIComponent(connId)}/sftp/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
29845
29907
  }
29846
29908
  sftpDuplicates(connId, path = ".", opts2) {
29847
- return this.req("POST", `/ssh/connections/${connId}/sftp/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
29909
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
29848
29910
  }
29849
29911
  // Download a remote file as a Blob (carries the auth token). With { redact: true } the server
29850
29912
  // routes the bytes through the #142 redaction gate (text only; binary → 415) before sending (#320c).
29851
29913
  async sftpDownload(connId, path, opts2) {
29852
29914
  const tok = this.getToken();
29853
- const res = await this.f(`${this.baseUrl}/ssh/connections/${connId}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
29915
+ const res = await this.f(`${this.baseUrl}/ssh/connections/${encodeURIComponent(connId)}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
29854
29916
  headers: this.rawHeaders(tok)
29855
29917
  });
29856
29918
  if (!res.ok) {
@@ -29906,12 +29968,12 @@ var KutrionClient = class {
29906
29968
  // Stream a remote file's bytes instead of buffering them into a Blob. `opts.start` resumes from an
29907
29969
  // offset via Range (#555); read `resumedAt` off the result rather than assuming the server obeyed.
29908
29970
  sftpDownloadStream(connId, path, opts2) {
29909
- return this.downloadStream(`${this.baseUrl}/ssh/connections/${connId}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
29971
+ return this.downloadStream(`${this.baseUrl}/ssh/connections/${encodeURIComponent(connId)}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
29910
29972
  }
29911
29973
  // Upload from a byte source without materialising it. `streamed` on the result reports whether this
29912
29974
  // runtime actually streamed the request body or had to collect it first.
29913
29975
  sftpUploadStream(connId, path, body, opts2) {
29914
- return this.uploadStream(`${this.baseUrl}/ssh/connections/${connId}/sftp/upload?path=${encodeURIComponent(path)}`, body, opts2);
29976
+ return this.uploadStream(`${this.baseUrl}/ssh/connections/${encodeURIComponent(connId)}/sftp/upload?path=${encodeURIComponent(path)}`, body, opts2);
29915
29977
  }
29916
29978
  // #2556: throws UploadTooLargeToInspectError when this org inspects uploads and `size` is over what the
29917
29979
  // inspection can hold, so the file is refused before any of it is sent. A policy that cannot be read
@@ -29926,7 +29988,7 @@ var KutrionClient = class {
29926
29988
  throw new UploadTooLargeToInspectError(size, insp.max_bytes, insp.enabled ? "too_large_for_scan" : "too_large_for_policy");
29927
29989
  }
29928
29990
  sftpUploadResumable(connId, path, source, opts2) {
29929
- return this.uploadResumable(`/ssh/connections/${connId}/sftp/upload`, path, source, opts2);
29991
+ return this.uploadResumable(`/ssh/connections/${encodeURIComponent(connId)}/sftp/upload`, path, source, opts2);
29930
29992
  }
29931
29993
  // #2556: one request per UPLOAD_CHUNK_BYTES over the #1566 resume protocol, so a file of any size
29932
29994
  // crosses an edge that caps a request body, and a dropped chunk is re-sent from the offset the
@@ -30055,26 +30117,26 @@ var KutrionClient = class {
30055
30117
  return this.req("POST", "/files/connections", input);
30056
30118
  }
30057
30119
  updateFileConnection(id, input) {
30058
- return this.req("PATCH", `/files/connections/${id}`, input);
30120
+ return this.req("PATCH", `/files/connections/${encodeURIComponent(id)}`, input);
30059
30121
  }
30060
30122
  deleteFileConnection(id) {
30061
- return this.req("DELETE", `/files/connections/${id}`);
30123
+ return this.req("DELETE", `/files/connections/${encodeURIComponent(id)}`);
30062
30124
  }
30063
30125
  fileList(connId, path = "/", page) {
30064
- return this.req("GET", `/files/connections/${connId}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
30126
+ return this.req("GET", `/files/connections/${encodeURIComponent(connId)}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
30065
30127
  }
30066
30128
  // #587: checksums + duplicate detection.
30067
30129
  fileChecksum(connId, path, algos = ["sha256"]) {
30068
- return this.req("GET", `/files/connections/${connId}/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
30130
+ return this.req("GET", `/files/connections/${encodeURIComponent(connId)}/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
30069
30131
  }
30070
30132
  fileDuplicates(connId, path = "/", opts2) {
30071
- return this.req("POST", `/files/connections/${connId}/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
30133
+ return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
30072
30134
  }
30073
30135
  // #586: recursive CONTENT search under `path`. Bytes are read on the server and only path/line/
30074
30136
  // snippet come back. `glob` filters which files are opened by name; it does not match names into
30075
30137
  // the result. Check `truncated` before presenting the matches as the whole answer.
30076
30138
  fileSearch(connId, path, query, opts2) {
30077
- return this.req("POST", `/files/connections/${connId}/search`, {
30139
+ return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/search`, {
30078
30140
  path,
30079
30141
  query,
30080
30142
  glob: opts2?.glob,
@@ -30084,39 +30146,39 @@ var KutrionClient = class {
30084
30146
  }
30085
30147
  // #588: time-limited presigned direct-download URL (S3 only).
30086
30148
  filePresign(connId, path, expiresSec = 3600) {
30087
- return this.req("GET", `/files/connections/${connId}/presign?path=${encodeURIComponent(path)}&expires=${expiresSec}`);
30149
+ return this.req("GET", `/files/connections/${encodeURIComponent(connId)}/presign?path=${encodeURIComponent(path)}&expires=${expiresSec}`);
30088
30150
  }
30089
30151
  // #574: OAuth cloud connectors (Dropbox/Drive/OneDrive/Box). Tokens stay server-side.
30090
30152
  cloudProviders() {
30091
30153
  return this.req("GET", "/cloud/providers");
30092
30154
  }
30093
30155
  cloudAuthorize(provider, name) {
30094
- return this.req("POST", `/cloud/${provider}/authorize`, { name });
30156
+ return this.req("POST", `/cloud/${encodeURIComponent(provider)}/authorize`, { name });
30095
30157
  }
30096
30158
  listCloudConnections() {
30097
30159
  return this.req("GET", "/cloud/connections");
30098
30160
  }
30099
30161
  deleteCloudConnection(id) {
30100
- return this.req("DELETE", `/cloud/connections/${id}`);
30162
+ return this.req("DELETE", `/cloud/connections/${encodeURIComponent(id)}`);
30101
30163
  }
30102
30164
  cloudList(connId, path = "/", page) {
30103
- return this.req("GET", `/cloud/connections/${connId}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
30165
+ return this.req("GET", `/cloud/connections/${encodeURIComponent(connId)}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
30104
30166
  }
30105
30167
  // #608: list + pin the connection's browse root (Shared Drive / SharePoint site / Box folder).
30106
30168
  cloudRoots(connId) {
30107
- return this.req("GET", `/cloud/connections/${connId}/roots`);
30169
+ return this.req("GET", `/cloud/connections/${encodeURIComponent(connId)}/roots`);
30108
30170
  }
30109
30171
  setCloudRoot(connId, root) {
30110
- return this.req("PUT", `/cloud/connections/${connId}/root`, { root });
30172
+ return this.req("PUT", `/cloud/connections/${encodeURIComponent(connId)}/root`, { root });
30111
30173
  }
30112
30174
  cloudMkdir(connId, path) {
30113
- return this.req("POST", `/cloud/connections/${connId}/mkdir`, { path });
30175
+ return this.req("POST", `/cloud/connections/${encodeURIComponent(connId)}/mkdir`, { path });
30114
30176
  }
30115
30177
  cloudRename(connId, from, to) {
30116
- return this.req("POST", `/cloud/connections/${connId}/rename`, { from, to });
30178
+ return this.req("POST", `/cloud/connections/${encodeURIComponent(connId)}/rename`, { from, to });
30117
30179
  }
30118
30180
  cloudDelete(connId, path, isDir) {
30119
- return this.req("POST", `/cloud/connections/${connId}/delete`, { path, is_dir: isDir });
30181
+ return this.req("POST", `/cloud/connections/${encodeURIComponent(connId)}/delete`, { path, is_dir: isDir });
30120
30182
  }
30121
30183
  // #1627: `redact` is the same opt-in the /files and /ssh download helpers take, and the cloud route
30122
30184
  // has honoured it since #1562 (server/src/modules/cloud.ts:461). It only ever ADDS masking — the
@@ -30124,7 +30186,7 @@ var KutrionClient = class {
30124
30186
  // a client whose users cannot ask for less, not one that is safer.
30125
30187
  async cloudDownload(connId, path, opts2) {
30126
30188
  const tok = this.getToken();
30127
- const res = await this.f(`${this.baseUrl}/cloud/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, { headers: this.rawHeaders(tok) });
30189
+ const res = await this.f(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, { headers: this.rawHeaders(tok) });
30128
30190
  if (!res.ok) {
30129
30191
  const errBody = await res.json().catch(() => void 0);
30130
30192
  throw new ApiError(res.status, apiErrorMessage(res.status, errBody), errBody);
@@ -30134,7 +30196,7 @@ var KutrionClient = class {
30134
30196
  // #2768: one request, so `signal` cancels it but there is no per-piece progress to report.
30135
30197
  async cloudUpload(connId, path, data, opts2) {
30136
30198
  const tok = this.getToken();
30137
- const res = await this.fUpload(`${this.baseUrl}/cloud/connections/${connId}/upload?path=${encodeURIComponent(path)}${opts2?.ifAbsent ? "&if_absent=1" : ""}`, {
30199
+ const res = await this.fUpload(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/upload?path=${encodeURIComponent(path)}${opts2?.ifAbsent ? "&if_absent=1" : ""}`, {
30138
30200
  method: "POST",
30139
30201
  headers: this.rawHeaders(tok, { "Content-Type": "application/octet-stream" }),
30140
30202
  body: data,
@@ -30155,27 +30217,27 @@ var KutrionClient = class {
30155
30217
  // (cloud.ts:453) — downloadStream only ever sends the open-ended form, so that refusal is
30156
30218
  // unreachable from here.
30157
30219
  cloudDownloadStream(connId, path, opts2) {
30158
- return this.downloadStream(`${this.baseUrl}/cloud/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
30220
+ return this.downloadStream(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
30159
30221
  }
30160
30222
  // POST /cloud/connections/:id/upload (cloud.ts:537) answers `{ ok }` plus the staging keys and NOT
30161
30223
  // `size` — it returns `uploadProgress()` alone, where the file and sftp routes add `path`/`size`
30162
30224
  // (files.ts:1428, ssh.ts:1070). Typed to what this route actually sends: claiming `size: number`
30163
30225
  // here would hand every caller a number that is undefined at runtime.
30164
30226
  cloudUploadStream(connId, path, body, opts2) {
30165
- return this.uploadStream(`${this.baseUrl}/cloud/connections/${connId}/upload?path=${encodeURIComponent(path)}`, body, opts2);
30227
+ return this.uploadStream(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/upload?path=${encodeURIComponent(path)}`, body, opts2);
30166
30228
  }
30167
30229
  fileMkdir(connId, path) {
30168
- return this.req("POST", `/files/connections/${connId}/mkdir`, { path });
30230
+ return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/mkdir`, { path });
30169
30231
  }
30170
30232
  fileRename(connId, from, to) {
30171
- return this.req("POST", `/files/connections/${connId}/rename`, { from, to });
30233
+ return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/rename`, { from, to });
30172
30234
  }
30173
30235
  fileDelete(connId, path, isDir) {
30174
- return this.req("POST", `/files/connections/${connId}/delete`, { path, is_dir: isDir });
30236
+ return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/delete`, { path, is_dir: isDir });
30175
30237
  }
30176
30238
  async fileDownload(connId, path, opts2) {
30177
30239
  const tok = this.getToken();
30178
- const res = await this.f(`${this.baseUrl}/files/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
30240
+ const res = await this.f(`${this.baseUrl}/files/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
30179
30241
  headers: this.rawHeaders(tok)
30180
30242
  });
30181
30243
  if (!res.ok) {
@@ -30186,13 +30248,13 @@ var KutrionClient = class {
30186
30248
  }
30187
30249
  // #1542: streaming + resumable counterparts of fileDownload/fileUpload. See downloadStream above.
30188
30250
  fileDownloadStream(connId, path, opts2) {
30189
- return this.downloadStream(`${this.baseUrl}/files/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
30251
+ return this.downloadStream(`${this.baseUrl}/files/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
30190
30252
  }
30191
30253
  fileUploadStream(connId, path, body, opts2) {
30192
- return this.uploadStream(`${this.baseUrl}/files/connections/${connId}/upload?path=${encodeURIComponent(path)}`, body, opts2);
30254
+ return this.uploadStream(`${this.baseUrl}/files/connections/${encodeURIComponent(connId)}/upload?path=${encodeURIComponent(path)}`, body, opts2);
30193
30255
  }
30194
30256
  fileUploadResumable(connId, path, source, opts2) {
30195
- return this.uploadResumable(`/files/connections/${connId}/upload`, path, source, opts2);
30257
+ return this.uploadResumable(`/files/connections/${encodeURIComponent(connId)}/upload`, path, source, opts2);
30196
30258
  }
30197
30259
  // #325: download a selection of files/folders as one zip, built server-side (works for every protocol).
30198
30260
  async fileArchive(input) {
@@ -30221,7 +30283,7 @@ var KutrionClient = class {
30221
30283
  return this.req("GET", "/files/shares");
30222
30284
  }
30223
30285
  revokeFileShare(id) {
30224
- return this.req("DELETE", `/files/shares/${id}`);
30286
+ return this.req("DELETE", `/files/shares/${encodeURIComponent(id)}`);
30225
30287
  }
30226
30288
  // #1636: the anonymous side of a Vault Drop — the recipient is outside the org and has no account.
30227
30289
  //
@@ -30263,12 +30325,12 @@ var KutrionClient = class {
30263
30325
  return this.req("GET", "/files/jobs");
30264
30326
  }
30265
30327
  getTransferJob(id) {
30266
- return this.req("GET", `/files/jobs/${id}`);
30328
+ return this.req("GET", `/files/jobs/${encodeURIComponent(id)}`);
30267
30329
  }
30268
30330
  // #583: reprioritise or cancel a job that is still QUEUED. A running or finished job answers 409
30269
30331
  // not_queued — the runner has already leased it and there is nothing left to reorder.
30270
30332
  patchTransferJob(id, input) {
30271
- return this.req("PATCH", `/files/jobs/${id}`, input);
30333
+ return this.req("PATCH", `/files/jobs/${encodeURIComponent(id)}`, input);
30272
30334
  }
30273
30335
  // #563: recurring transfer schedules (interval or daily UTC time).
30274
30336
  createSchedule(input) {
@@ -30278,10 +30340,10 @@ var KutrionClient = class {
30278
30340
  return this.req("GET", "/files/schedules");
30279
30341
  }
30280
30342
  setScheduleEnabled(id, enabled) {
30281
- return this.req("PATCH", `/files/schedules/${id}`, { enabled });
30343
+ return this.req("PATCH", `/files/schedules/${encodeURIComponent(id)}`, { enabled });
30282
30344
  }
30283
30345
  deleteSchedule(id) {
30284
- return this.req("DELETE", `/files/schedules/${id}`);
30346
+ return this.req("DELETE", `/files/schedules/${encodeURIComponent(id)}`);
30285
30347
  }
30286
30348
  // #614: folder watchers — poll a source directory and fire a transfer job on new/changed, size-stable
30287
30349
  // files matching the filters. The transfer runs as a durable job.
@@ -30292,10 +30354,10 @@ var KutrionClient = class {
30292
30354
  return this.req("GET", "/files/folder-watchers");
30293
30355
  }
30294
30356
  setFolderWatcherEnabled(id, enabled) {
30295
- return this.req("PATCH", `/files/folder-watchers/${id}`, { enabled });
30357
+ return this.req("PATCH", `/files/folder-watchers/${encodeURIComponent(id)}`, { enabled });
30296
30358
  }
30297
30359
  deleteFolderWatcher(id) {
30298
- return this.req("DELETE", `/files/folder-watchers/${id}`);
30360
+ return this.req("DELETE", `/files/folder-watchers/${encodeURIComponent(id)}`);
30299
30361
  }
30300
30362
  // #627: workflow templates. A saved edit is a NEW VERSION, not a PATCH — 0150's trigger refuses an
30301
30363
  // UPDATE, because a run names the version it used and reproducibility depends on a stored revision
@@ -30304,19 +30366,19 @@ var KutrionClient = class {
30304
30366
  return this.req("GET", "/files/workflow-templates");
30305
30367
  }
30306
30368
  getWorkflowTemplate(id) {
30307
- return this.req("GET", `/files/workflow-templates/${id}`);
30369
+ return this.req("GET", `/files/workflow-templates/${encodeURIComponent(id)}`);
30308
30370
  }
30309
30371
  createWorkflowTemplate(input) {
30310
30372
  return this.req("POST", "/files/workflow-templates", input);
30311
30373
  }
30312
30374
  addWorkflowTemplateVersion(id, input) {
30313
- return this.req("POST", `/files/workflow-templates/${id}/versions`, input);
30375
+ return this.req("POST", `/files/workflow-templates/${encodeURIComponent(id)}/versions`, input);
30314
30376
  }
30315
30377
  deleteWorkflowTemplate(id) {
30316
- return this.req("DELETE", `/files/workflow-templates/${id}`);
30378
+ return this.req("DELETE", `/files/workflow-templates/${encodeURIComponent(id)}`);
30317
30379
  }
30318
30380
  instantiateWorkflowTemplate(id, input) {
30319
- return this.req("POST", `/files/workflow-templates/${id}/instantiate`, input);
30381
+ return this.req("POST", `/files/workflow-templates/${encodeURIComponent(id)}/instantiate`, input);
30320
30382
  }
30321
30383
  // #559: directory sync / mirror. dry_run returns the plan without writing; omit it to apply.
30322
30384
  // #585: `two_way` and `mirror` are mutually exclusive and the server refuses the pair outright
@@ -30366,13 +30428,13 @@ var KutrionClient = class {
30366
30428
  }
30367
30429
  // #557: SFTP POSIX metadata (chmod / set mtime / create symlink).
30368
30430
  sftpChmod(connId, path, mode) {
30369
- return this.req("POST", `/ssh/connections/${connId}/sftp/chmod`, { path, mode });
30431
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/chmod`, { path, mode });
30370
30432
  }
30371
30433
  sftpChtime(connId, path, mtime) {
30372
- return this.req("POST", `/ssh/connections/${connId}/sftp/chtime`, { path, mtime });
30434
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/chtime`, { path, mtime });
30373
30435
  }
30374
30436
  sftpSymlink(connId, target, link) {
30375
- return this.req("POST", `/ssh/connections/${connId}/sftp/symlink`, { target, link });
30437
+ return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/symlink`, { target, link });
30376
30438
  }
30377
30439
  // #2556: resumable, as sftpUpload.
30378
30440
  async fileUpload(connId, path, data, opts2) {
@@ -30391,7 +30453,7 @@ var KutrionClient = class {
30391
30453
  return this.req("GET", "/ssh/recordings");
30392
30454
  }
30393
30455
  getRecording(id) {
30394
- return this.req("GET", `/ssh/recordings/${id}`);
30456
+ return this.req("GET", `/ssh/recordings/${encodeURIComponent(id)}`);
30395
30457
  }
30396
30458
  /** #2631: what was stored for one of the caller's recorded shell sessions, once it has ended. */
30397
30459
  shellSessionReceipt(sessionId) {
@@ -30406,11 +30468,11 @@ var KutrionClient = class {
30406
30468
  return this.req("DELETE", `/ssh/shell-sessions/${encodeURIComponent(sessionId)}`);
30407
30469
  }
30408
30470
  deleteRecording(id) {
30409
- return this.req("DELETE", `/ssh/recordings/${id}`);
30471
+ return this.req("DELETE", `/ssh/recordings/${encodeURIComponent(id)}`);
30410
30472
  }
30411
30473
  // VT-143: generate (or fetch cached) an AI session summary for a recording. regenerate forces recompute.
30412
30474
  summarizeRecording(id, regenerate = false) {
30413
- return this.req("POST", `/ssh/recordings/${id}/summary${regenerate ? "?regenerate=1" : ""}`);
30475
+ return this.req("POST", `/ssh/recordings/${encodeURIComponent(id)}/summary${regenerate ? "?regenerate=1" : ""}`);
30414
30476
  }
30415
30477
  // --- #2628: org recording review and retention, org owners and admins only ---
30416
30478
  getRecordingPolicy() {
@@ -30477,10 +30539,10 @@ var KutrionClient = class {
30477
30539
  return this.req("GET", `/exposure${s ? `?${s}` : ""}`);
30478
30540
  }
30479
30541
  resolveExposure(id, resolution) {
30480
- return this.req("POST", `/exposure/${id}/resolve`, { resolution });
30542
+ return this.req("POST", `/exposure/${encodeURIComponent(id)}/resolve`, { resolution });
30481
30543
  }
30482
30544
  suppressExposure(id, patterns = []) {
30483
- return this.req("POST", `/exposure/${id}/suppress`, { patterns });
30545
+ return this.req("POST", `/exposure/${encodeURIComponent(id)}/suppress`, { patterns });
30484
30546
  }
30485
30547
  // --- integrations hub: outbound alert + SIEM connectors (VT-310 / VT-485) ---
30486
30548
  integrationCatalog() {
@@ -30493,16 +30555,16 @@ var KutrionClient = class {
30493
30555
  return this.req("POST", "/integrations", input);
30494
30556
  }
30495
30557
  updateIntegration(id, input) {
30496
- return this.req("PATCH", `/integrations/${id}`, input);
30558
+ return this.req("PATCH", `/integrations/${encodeURIComponent(id)}`, input);
30497
30559
  }
30498
30560
  deleteIntegration(id) {
30499
- return this.req("DELETE", `/integrations/${id}`);
30561
+ return this.req("DELETE", `/integrations/${encodeURIComponent(id)}`);
30500
30562
  }
30501
30563
  testIntegration(id) {
30502
- return this.req("POST", `/integrations/${id}/test`);
30564
+ return this.req("POST", `/integrations/${encodeURIComponent(id)}/test`);
30503
30565
  }
30504
30566
  listIntegrationDeliveries(id, limit = 50) {
30505
- return this.req("GET", `/integrations/${id}/deliveries?limit=${limit}`);
30567
+ return this.req("GET", `/integrations/${encodeURIComponent(id)}/deliveries?limit=${limit}`);
30506
30568
  }
30507
30569
  // --- HashiCorp Vault secret sync (VT-306): ENTERPRISE-gated (secret_sync). Auth secret is write-only. ---
30508
30570
  listHcvConnections() {
@@ -30512,28 +30574,28 @@ var KutrionClient = class {
30512
30574
  return this.req("POST", "/hashicorp/connections", input);
30513
30575
  }
30514
30576
  updateHcvConnection(id, input) {
30515
- return this.req("PATCH", `/hashicorp/connections/${id}`, input);
30577
+ return this.req("PATCH", `/hashicorp/connections/${encodeURIComponent(id)}`, input);
30516
30578
  }
30517
30579
  deleteHcvConnection(id) {
30518
- return this.req("DELETE", `/hashicorp/connections/${id}`);
30580
+ return this.req("DELETE", `/hashicorp/connections/${encodeURIComponent(id)}`);
30519
30581
  }
30520
30582
  testHcvConnection(id) {
30521
- return this.req("POST", `/hashicorp/connections/${id}/test`);
30583
+ return this.req("POST", `/hashicorp/connections/${encodeURIComponent(id)}/test`);
30522
30584
  }
30523
30585
  listHcvMappings(connId) {
30524
- return this.req("GET", `/hashicorp/connections/${connId}/mappings`);
30586
+ return this.req("GET", `/hashicorp/connections/${encodeURIComponent(connId)}/mappings`);
30525
30587
  }
30526
30588
  createHcvMapping(connId, input) {
30527
- return this.req("POST", `/hashicorp/connections/${connId}/mappings`, input);
30589
+ return this.req("POST", `/hashicorp/connections/${encodeURIComponent(connId)}/mappings`, input);
30528
30590
  }
30529
30591
  deleteHcvMapping(mappingId) {
30530
- return this.req("DELETE", `/hashicorp/mappings/${mappingId}`);
30592
+ return this.req("DELETE", `/hashicorp/mappings/${encodeURIComponent(mappingId)}`);
30531
30593
  }
30532
30594
  syncHcvMapping(mappingId, resolve2) {
30533
- return this.req("POST", `/hashicorp/mappings/${mappingId}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
30595
+ return this.req("POST", `/hashicorp/mappings/${encodeURIComponent(mappingId)}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
30534
30596
  }
30535
30597
  syncHcvConnection(connId, resolve2) {
30536
- return this.req("POST", `/hashicorp/connections/${connId}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
30598
+ return this.req("POST", `/hashicorp/connections/${encodeURIComponent(connId)}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
30537
30599
  }
30538
30600
  // --- rdp broker (VT-490) ---
30539
30601
  listRdpConnections() {
@@ -30543,28 +30605,28 @@ var KutrionClient = class {
30543
30605
  return this.req("POST", "/rdp/connections", input);
30544
30606
  }
30545
30607
  updateRdpConnection(id, input) {
30546
- return this.req("PATCH", `/rdp/connections/${id}`, input);
30608
+ return this.req("PATCH", `/rdp/connections/${encodeURIComponent(id)}`, input);
30547
30609
  }
30548
30610
  deleteRdpConnection(id) {
30549
- return this.req("DELETE", `/rdp/connections/${id}`);
30611
+ return this.req("DELETE", `/rdp/connections/${encodeURIComponent(id)}`);
30550
30612
  }
30551
30613
  testRdpConnection(id) {
30552
- return this.req("POST", `/rdp/connections/${id}/test`);
30614
+ return this.req("POST", `/rdp/connections/${encodeURIComponent(id)}/test`);
30553
30615
  }
30554
30616
  startRdpSession(id) {
30555
- return this.req("POST", `/rdp/connections/${id}/session`);
30617
+ return this.req("POST", `/rdp/connections/${encodeURIComponent(id)}/session`);
30556
30618
  }
30557
30619
  listRdpSessions() {
30558
30620
  return this.req("GET", "/rdp/sessions");
30559
30621
  }
30560
30622
  endRdpSession(sessionId) {
30561
- return this.req("DELETE", `/rdp/sessions/${sessionId}`);
30623
+ return this.req("DELETE", `/rdp/sessions/${encodeURIComponent(sessionId)}`);
30562
30624
  }
30563
30625
  // Stream a brokered session's recording (broker proxies it from the gateway). Binary, so it bypasses
30564
30626
  // the JSON req() helper — same shape as sftpDownload/fileDownload.
30565
30627
  async getRdpRecording(sessionId) {
30566
30628
  const tok = this.getToken();
30567
- const res = await this.f(`${this.baseUrl}/rdp/sessions/${sessionId}/recording`, {
30629
+ const res = await this.f(`${this.baseUrl}/rdp/sessions/${encodeURIComponent(sessionId)}/recording`, {
30568
30630
  headers: this.rawHeaders(tok)
30569
30631
  });
30570
30632
  if (!res.ok) throw new ApiError(res.status, apiErrorMessage(res.status));
@@ -30607,7 +30669,7 @@ var KutrionClient = class {
30607
30669
  }
30608
30670
  // Files AI (#325): build a filename regex from natural language, summarize a listing, or propose a
30609
30671
  // rename plan. All run LAN-first; only file NAMES/metadata are sent (never file content), through the
30610
- // redaction gate. The returned regex is server-validated to compile and be free of ReDoS shapes.
30672
+ // redaction gate. The returned regex is server-validated to compile and to lack the most obvious ReDoS shapes only (isSafeRegex is a heuristic: (a|a)+ passes); clients must run it under a time budget.
30611
30673
  aiFilePattern(input) {
30612
30674
  return this.req("POST", "/ai/files/pattern", input);
30613
30675
  }
@@ -30639,16 +30701,16 @@ var KutrionClient = class {
30639
30701
  return this.req("GET", "/jit/requests");
30640
30702
  }
30641
30703
  getJitRequest(id) {
30642
- return this.req("GET", `/jit/requests/${id}`);
30704
+ return this.req("GET", `/jit/requests/${encodeURIComponent(id)}`);
30643
30705
  }
30644
30706
  approveJitRequest(id, note) {
30645
- return this.req("POST", `/jit/requests/${id}/approve`, { note });
30707
+ return this.req("POST", `/jit/requests/${encodeURIComponent(id)}/approve`, { note });
30646
30708
  }
30647
30709
  denyJitRequest(id, note) {
30648
- return this.req("POST", `/jit/requests/${id}/deny`, { note });
30710
+ return this.req("POST", `/jit/requests/${encodeURIComponent(id)}/deny`, { note });
30649
30711
  }
30650
30712
  revokeJitRequest(id) {
30651
- return this.req("POST", `/jit/requests/${id}/revoke`);
30713
+ return this.req("POST", `/jit/requests/${encodeURIComponent(id)}/revoke`);
30652
30714
  }
30653
30715
  // --- rotation copilot (VT-145) ---
30654
30716
  rotationServices() {
@@ -30672,13 +30734,13 @@ var KutrionClient = class {
30672
30734
  return this.req("POST", "/fleet/plan", input);
30673
30735
  }
30674
30736
  executeFleetRun(id) {
30675
- return this.req("POST", `/fleet/runs/${id}/execute`, { confirm: true });
30737
+ return this.req("POST", `/fleet/runs/${encodeURIComponent(id)}/execute`, { confirm: true });
30676
30738
  }
30677
30739
  fleetRuns() {
30678
30740
  return this.req("GET", "/fleet/runs");
30679
30741
  }
30680
30742
  fleetRun(id) {
30681
- return this.req("GET", `/fleet/runs/${id}`);
30743
+ return this.req("GET", `/fleet/runs/${encodeURIComponent(id)}`);
30682
30744
  }
30683
30745
  // --- anomaly baseline (VT-204) ---
30684
30746
  refreshBaseline() {
@@ -31222,6 +31284,9 @@ var SSH_REFUSAL_CLASSIFICATION = {
31222
31284
  // Something answered on that port and it was not an SSH server; what is listening has to change
31223
31285
  // first, even though the user is who can change it.
31224
31286
  protocol_error: { family: "failed", retry: "pointless" },
31287
+ // #3419: the login was accepted and the host refused a PTY or a shell. The account is set up that
31288
+ // way on the host, so every attempt earns the same answer.
31289
+ shell_refused: { family: "failed", retry: "pointless" },
31225
31290
  // The rest are genuine retry candidates: a transport fact can be different a second later.
31226
31291
  connection_refused: { family: "failed", retry: "possible" },
31227
31292
  host_not_found: { family: "failed", retry: "possible" },
@@ -32853,14 +32918,14 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
32853
32918
  function unknownFlags(flags) {
32854
32919
  return Object.keys(flags).filter((k) => !KNOWN_FLAGS.has(k));
32855
32920
  }
32856
- function parseArgs(argv) {
32921
+ function parseArgs(argv2) {
32857
32922
  const flags = {};
32858
32923
  let at = 0;
32859
- while (at < argv.length && argv[at].startsWith("--")) {
32860
- const key = argv[at].slice(2);
32924
+ while (at < argv2.length && argv2[at].startsWith("--")) {
32925
+ const key = argv2[at].slice(2);
32861
32926
  const kind = GLOBAL_FLAGS[key];
32862
32927
  if (!kind) break;
32863
- const next = argv[at + 1];
32928
+ const next = argv2[at + 1];
32864
32929
  if (kind === "value" && next !== void 0 && !next.startsWith("--")) {
32865
32930
  flags[key] = next;
32866
32931
  at += 2;
@@ -32869,8 +32934,8 @@ function parseArgs(argv) {
32869
32934
  at += 1;
32870
32935
  }
32871
32936
  }
32872
- const cmd = argv[at];
32873
- const rest = argv.slice(at + 1);
32937
+ const cmd = argv2[at];
32938
+ const rest = argv2.slice(at + 1);
32874
32939
  const positionals = [];
32875
32940
  for (let i = 0; i < rest.length; i++) {
32876
32941
  const a = rest[i];
@@ -34029,15 +34094,18 @@ async function borrowAndEnrol(deps) {
34029
34094
  if (enrolment === "device_clients_disabled" || enrolment === "satellite_update_required" || enrolment === "hub_not_bound") return { ok: false, reason: enrolment };
34030
34095
  return enrolment ? { ok: true, enrolment, hubPublicKey: borrowed.hubPublicKey } : { ok: false, reason: "enrol_refused" };
34031
34096
  }
34032
- function createBorrowSession(store, client, resolveDeps) {
34097
+ function createBorrowSession(store, client, resolveDeps, otherSigninHeld = () => false) {
34033
34098
  let pending = null;
34034
34099
  let held = null;
34035
34100
  let pin = null;
34036
34101
  let running = false;
34102
+ let autoTried = false;
34037
34103
  return {
34038
- begin() {
34039
- if (running) return { ok: false, reason: "already_enrolled" };
34040
- if (held) return { ok: false, reason: "already_enrolled" };
34104
+ begin(auto = false) {
34105
+ if (auto && autoTried) return { ok: false, reason: "auto_done" };
34106
+ if (auto) autoTried = true;
34107
+ if (running) return { ok: false, reason: "busy" };
34108
+ if (held || otherSigninHeld()) return { ok: false, reason: "signin_in_progress" };
34041
34109
  const p = prepareBorrow(store, client);
34042
34110
  pending = p.ok ? p : null;
34043
34111
  return p.ok ? { ok: true, fingerprint: p.fingerprint } : { ok: false, reason: p.reason };
@@ -34064,6 +34132,9 @@ function createBorrowSession(store, client, resolveDeps) {
34064
34132
  pin = deps.pin;
34065
34133
  return { ok: true, deviceId: out.deviceId, firstContact: out.held !== null };
34066
34134
  },
34135
+ suppressAuto() {
34136
+ autoTried = true;
34137
+ },
34067
34138
  cancel() {
34068
34139
  pending = null;
34069
34140
  },
@@ -34171,9 +34242,16 @@ function createFeatureLatch(store, log3) {
34171
34242
  }
34172
34243
  };
34173
34244
  }
34245
+ var unavailable = (reason) => ({ kind: "unavailable", reason });
34246
+ function fetchFailure(e) {
34247
+ const err = e;
34248
+ if (err?.name === "TimeoutError" || err?.name === "AbortError") return "timeout";
34249
+ const text2 = `${String(err?.message ?? "")} ${String(err?.cause?.message ?? "")}`.toLowerCase();
34250
+ return text2.includes("redirect") ? "redirect" : "network";
34251
+ }
34174
34252
  async function checkHubKey(p) {
34175
34253
  const origin = apiOrigin(p.apiBase);
34176
- if (!origin || !p.bearer) return "unavailable";
34254
+ if (!origin || !p.bearer) return unavailable("no_bearer");
34177
34255
  let res;
34178
34256
  try {
34179
34257
  res = await (p.fetchImpl ?? fetch)(`${p.apiBase}/auth/hub-channel-keys/check`, {
@@ -34183,15 +34261,15 @@ async function checkHubKey(p) {
34183
34261
  redirect: "error",
34184
34262
  signal: AbortSignal.timeout(p.timeoutMs ?? 1e4)
34185
34263
  });
34186
- } catch {
34187
- return "unavailable";
34264
+ } catch (e) {
34265
+ return unavailable(fetchFailure(e));
34188
34266
  }
34189
34267
  p.latch.observe(origin, res.headers);
34190
34268
  const body = await res.json().catch(() => null);
34191
34269
  if (isApiNotFound(res.status, body) && !p.latch.latched(origin)) return "legacy";
34192
- if (!res.ok) return "unavailable";
34270
+ if (!res.ok) return unavailable(`http_${res.status}`);
34193
34271
  const v = body?.verdict;
34194
- if (v !== "vouched" && v !== "first_contact" && v !== "new_hub" && v !== "refused") return "unavailable";
34272
+ if (v !== "vouched" && v !== "first_contact" && v !== "new_hub" && v !== "refused") return unavailable("bad_verdict");
34195
34273
  p.latch.verdictSeen(origin);
34196
34274
  return v;
34197
34275
  }
@@ -34234,14 +34312,29 @@ function joinedAccount(user, lastAccount) {
34234
34312
  accountChanged: lastAccount !== null && lastAccount !== user.id
34235
34313
  };
34236
34314
  }
34315
+ var HELD_TTL_MS = 3e5;
34237
34316
  function createBorrowConfirm(deps) {
34317
+ const ttl = deps.ttlMs ?? HELD_TTL_MS;
34318
+ const now = deps.now ?? Date.now;
34238
34319
  let account = null;
34239
34320
  let heldId = null;
34240
34321
  let heldName = null;
34322
+ let heldAt = 0;
34323
+ let timer = null;
34241
34324
  const remember = (user) => {
34242
34325
  if (deps.lastAccount.get() !== user.id) deps.lastAccount.set(user.id);
34243
34326
  };
34327
+ const expired = () => account !== null && now() - heldAt >= ttl;
34328
+ const reap = () => {
34329
+ if (!expired()) return false;
34330
+ void reject().then(() => deps.onExpired?.());
34331
+ return true;
34332
+ };
34244
34333
  const reject = async () => {
34334
+ if (timer) {
34335
+ clearTimeout(timer);
34336
+ timer = null;
34337
+ }
34245
34338
  account = null;
34246
34339
  heldId = null;
34247
34340
  heldName = null;
@@ -34255,19 +34348,28 @@ function createBorrowConfirm(deps) {
34255
34348
  account = user;
34256
34349
  heldId = deviceId;
34257
34350
  heldName = deviceName ?? null;
34351
+ heldAt = now();
34352
+ if (timer) clearTimeout(timer);
34353
+ timer = setTimeout(reap, ttl);
34354
+ timer.unref?.();
34258
34355
  },
34259
- pending: () => account ? { ...joinedAccount(account, deps.lastAccount.get()), deviceName: heldName } : null,
34356
+ pending: () => account && !reap() ? { ...joinedAccount(account, deps.lastAccount.get()), deviceName: heldName, deviceId: heldId } : null,
34260
34357
  seen(user) {
34261
34358
  if (!account && user) remember(user);
34262
34359
  },
34263
- async confirm() {
34360
+ async confirm(deviceId) {
34361
+ if (reap()) return false;
34264
34362
  const user = account;
34265
34363
  const id = heldId;
34266
- if (!user || !id) return false;
34364
+ if (!user || !id || id !== deviceId) return false;
34267
34365
  if (!deps.borrow.commit(id)) {
34268
34366
  await reject();
34269
34367
  return false;
34270
34368
  }
34369
+ if (timer) {
34370
+ clearTimeout(timer);
34371
+ timer = null;
34372
+ }
34271
34373
  account = null;
34272
34374
  heldId = null;
34273
34375
  remember(user);
@@ -34469,6 +34571,10 @@ function cliHubStores(base, kc, keychainAvailable) {
34469
34571
  };
34470
34572
  return { store, pin, latch, lastAccount, failedWrite: () => failed2 ? { deviceId, bearer } : null };
34471
34573
  }
34574
+ var lastAccountId = (base, kc) => read(kc, slot.lastAccount(base));
34575
+ function recordLastAccountId(base, kc, user) {
34576
+ if (user && typeof user.id === "string" && user.id) write(kc, slot.lastAccount(base), user.id);
34577
+ }
34472
34578
  function borrowedDeviceKey(base, kc) {
34473
34579
  return readDevice(kc, base).key;
34474
34580
  }
@@ -34569,6 +34675,7 @@ async function hubLogin(deps) {
34569
34675
  if (!deps.keychainAvailable()) return { kind: "fallback", note: null, reason: "no_keychain" };
34570
34676
  if (locationFault(deps.location)) return { kind: "fallback", note: null, reason: "hub_not_running" };
34571
34677
  const { store, pin, latch, lastAccount, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
34678
+ if (deps.pairedOnly && !pin.hubPublicKey) return { kind: "fallback", note: null, reason: "not_paired" };
34572
34679
  const revoke = (deviceId, bearer) => endServerSession(deps.call, deviceId, bearer);
34573
34680
  const session = createBorrowSession(store, "cli", () => ({
34574
34681
  location: deps.location,
@@ -34620,11 +34727,36 @@ async function hubLogin(deps) {
34620
34727
  const verdict = await presentJoined(confirm, deps.out, deps.ask);
34621
34728
  if (verdict === "not_kept") return { kind: "fallback", note: HUB_COPY.notKept, reason: "not_kept" };
34622
34729
  if (verdict === "not_you") return { kind: "fallback", note: HUB_COPY.notYou, reason: "not_you" };
34623
- if (!await confirm.confirm()) return { kind: "fallback", note: HUB_COPY.notKept, reason: "not_kept" };
34730
+ if (!await confirm.confirm(done.deviceId)) return { kind: "fallback", note: HUB_COPY.notKept, reason: "not_kept" };
34624
34731
  const lost = await unkept();
34625
34732
  if (lost) return lost;
34626
34733
  return { kind: "signed_in", user, firstContact: true };
34627
34734
  }
34735
+ var canPromptYes = (stdin, stderr) => Boolean(stdin.isTTY && stderr.isTTY);
34736
+ var reborrowFromHub = (deps) => hubLogin({ ...deps, pairedOnly: true });
34737
+ var loginTriesHub = (flags, accountRoot) => flags.email === void 0 && flags["password-stdin"] !== true && accountRoot !== null;
34738
+ var HubAccountSwitchedError = class extends Error {
34739
+ constructor(email) {
34740
+ super(`Signed in through Kutrion Hub as ${email === null ? "an account it could not name" : printable(email)}, which is not the account this command started on. Run it again to use that account.`);
34741
+ this.name = "HubAccountSwitchedError";
34742
+ }
34743
+ };
34744
+ async function retryAfterHubReborrow(run2, deps) {
34745
+ try {
34746
+ return await run2();
34747
+ } catch (e) {
34748
+ if (!deps.refusedSession(e) || deps.wrote()) throw e;
34749
+ const before = deps.lastAccount();
34750
+ const hub = await deps.reborrow();
34751
+ if (hub.kind !== "signed_in") {
34752
+ if (hub.note) deps.out(hub.note);
34753
+ throw e;
34754
+ }
34755
+ if (!before || !hub.user || hub.user.id !== before) throw new HubAccountSwitchedError(hub.user ? hub.user.email : null);
34756
+ deps.out(`Signed in again with the Kutrion app as ${printable(hub.user.email)}; running the command again.`);
34757
+ }
34758
+ return run2();
34759
+ }
34628
34760
 
34629
34761
  // src/hub/hub-seed.ts
34630
34762
  init_define_KT_HUB_FILES();
@@ -34684,7 +34816,7 @@ function lineReader(sock) {
34684
34816
  })
34685
34817
  };
34686
34818
  }
34687
- var failed = (reason) => ({ ok: false, reason });
34819
+ var failed = (reason, detail) => ({ ok: false, reason, ...detail ? { detail } : {} });
34688
34820
  function openConn(deps, endpoint) {
34689
34821
  const sock = (deps.connectTo ?? ((e) => (0, import_node_net2.connect)(e)))(endpoint);
34690
34822
  const rd = lineReader(sock);
@@ -34743,7 +34875,7 @@ async function seedOnce(deps) {
34743
34875
  const hubPk = met.offer.hub_pk;
34744
34876
  if (wrongHub(hubPk, pinned)) return failed("hub_identity_changed");
34745
34877
  const verdict = await deps.check(hubPk);
34746
- if (verdict === "unavailable") return failed("hub_check_unavailable");
34878
+ if (typeof verdict === "object") return failed("hub_check_unavailable", verdict.reason);
34747
34879
  if (verdict === "refused" || verdict === "new_hub") return failed("hub_not_vouched");
34748
34880
  if (pinned === null || verdict === "first_contact") {
34749
34881
  const answer = await confirmWithin(deps, { app: appCode, hub: keyFingerprint(identity.client, hubPk) });
@@ -34926,13 +35058,13 @@ async function seedHubAfterSignIn(deps) {
34926
35058
  const reachable = () => resolveEndpoint(deps.location, { platform: deps.platform }) !== null;
34927
35059
  if (!reachable()) {
34928
35060
  const launcher2 = deps.launcher;
34929
- if (!launcher2 || !await launcher2.installed()) return { kind: "retry" };
34930
- if (!await launcher2.launch()) return { kind: "retry" };
35061
+ if (!launcher2 || !await launcher2.installed()) return { kind: "retry", reason: "hub_not_installed" };
35062
+ if (!await launcher2.launch()) return { kind: "retry", reason: "hub_not_launched" };
34931
35063
  const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
34932
35064
  const now = deps.now ?? Date.now;
34933
35065
  const until = now() + LAUNCH_WAIT_MS;
34934
35066
  while (!reachable() && now() < until) await sleep(LAUNCH_POLL_MS);
34935
- if (!reachable()) return { kind: "retry" };
35067
+ if (!reachable()) return { kind: "retry", reason: "hub_not_up" };
34936
35068
  }
34937
35069
  const launcher = deps.launcher;
34938
35070
  const confirmHub = launcher?.foreground ? async (codes, signal) => {
@@ -34942,9 +35074,9 @@ async function seedHubAfterSignIn(deps) {
34942
35074
  const out = await seedOnce({ ...deps, confirmHub });
34943
35075
  if (out.ok) return { kind: "done", result: out.result };
34944
35076
  if (out.reason === "hub_other_account") return { kind: "other_account" };
34945
- if (out.reason === "hub_not_running") return { kind: "retry" };
35077
+ if (out.reason === "hub_not_running") return { kind: "retry", reason: "hub_not_running" };
34946
35078
  if (out.reason === "hub_timed_out") return { kind: "timed_out" };
34947
- if (out.reason === "hub_check_unavailable") return { kind: "retry" };
35079
+ if (out.reason === "hub_check_unavailable") return { kind: "retry", reason: "hub_check_unavailable", ...out.detail ? { detail: out.detail } : {} };
34948
35080
  return { kind: "stopped", reason: out.reason };
34949
35081
  }
34950
35082
 
@@ -35078,7 +35210,7 @@ async function seedHubFromLogin(deps) {
35078
35210
  },
35079
35211
  check: async (hubPk) => {
35080
35212
  const verdict = await checkHubKey({ apiBase: deps.base, bearer: bearer(), hubPk, latch, fetchImpl: deps.fetchImpl });
35081
- if (verdict === "unavailable") checkUnavailable = true;
35213
+ if (typeof verdict === "object") checkUnavailable = true;
35082
35214
  return verdict;
35083
35215
  },
35084
35216
  issue: async (devicePk, hubPk) => {
@@ -35510,6 +35642,7 @@ async function confirmChainedHostKey(deps, connId, k, own) {
35510
35642
  }
35511
35643
 
35512
35644
  // src/refusal.ts
35645
+ var refreshWhy = (code, noun = "session") => refreshRefusalCopy(code, noun)?.why ?? code;
35513
35646
  var REFUSALS = {
35514
35647
  // #1538 authoring time. Safe to key here even though `jump_cycle` and `jump_too_deep` are also
35515
35648
  // WebSocket codes: this table is read only by refusalLines() on the HTTP path, and wsRefusalLines()
@@ -35532,7 +35665,7 @@ var REFUSALS = {
35532
35665
  action: "Run `kt conns` and pass an id from the first column to --jump."
35533
35666
  },
35534
35667
  // auth-guard.ts:109 (social-session gate) and :167 (adaptive), plus rotation.ts:159, jit.ts:226,
35535
- // fleet.ts:190, agents.ts:146, modules/ssh.ts:727 and :1222. The remedy is mechanical and worth
35668
+ // fleet.ts:190, agents.ts:146, modules/ssh.ts:730 and :1228. The remedy is mechanical and worth
35536
35669
  // stating exactly, because it is not obvious that it can be satisfied anywhere but here: step-up
35537
35670
  // freshness is a per-USER Redis proof with a ~5 minute TTL (server/src/lib/step-up.ts:16), not a
35538
35671
  // claim baked into this CLI's token. Stepping up in the web app on the same account therefore
@@ -35561,6 +35694,49 @@ var REFUSALS = {
35561
35694
  why: "The token stored for this server is no longer accepted, so every command will be refused until this machine signs in again.",
35562
35695
  action: "Run `kt login` to sign in again."
35563
35696
  },
35697
+ // #3444/#3445: POST /auth/refresh terminal verdicts (server/src/modules/devices.ts). Each ends the
35698
+ // stored session, like device_revoked, so the only way forward is a fresh `kt login`. The Hub pair
35699
+ // reuses the extension's sentences word for word.
35700
+ refresh_credentials_changed: {
35701
+ title: "Your password changed",
35702
+ why: refreshWhy("refresh_credentials_changed"),
35703
+ action: "Run `kt login` with your new password."
35704
+ },
35705
+ refresh_hub_signed_out: {
35706
+ title: "This session was signed out",
35707
+ why: refreshWhy("refresh_hub_signed_out"),
35708
+ action: "Run `kt login` to sign in again."
35709
+ },
35710
+ refresh_hub_revoked: {
35711
+ title: "This session was signed out",
35712
+ why: refreshWhy("refresh_hub_revoked"),
35713
+ action: "Run `kt login` to sign in again."
35714
+ },
35715
+ device_revoked: {
35716
+ title: "This device was signed out",
35717
+ why: refreshWhy("device_revoked", "device"),
35718
+ action: "Run `kt login` to sign in again and register it anew."
35719
+ },
35720
+ account_inactive: {
35721
+ title: "This account has ended",
35722
+ why: refreshWhy("account_inactive"),
35723
+ action: "Your administrator can restore the account. Signing in again will not work until they do."
35724
+ },
35725
+ account_expired: {
35726
+ title: "This account has expired",
35727
+ why: refreshWhy("account_expired"),
35728
+ action: "Your administrator can extend the account. Signing in again will not work until they do."
35729
+ },
35730
+ user_inactive: {
35731
+ title: "This account has ended",
35732
+ why: refreshWhy("user_inactive"),
35733
+ action: "Your administrator can restore the account. Signing in again will not work until they do."
35734
+ },
35735
+ org_mismatch: {
35736
+ title: "This session was signed out",
35737
+ why: refreshWhy("org_mismatch"),
35738
+ action: "Run `kt login` to sign in again with your current organization."
35739
+ },
35564
35740
  // auth-guard.ts:164. The distinguishing fact is that this one CANNOT be stepped up out of — it is
35565
35741
  // sent precisely when the account holds no second factor to step up with.
35566
35742
  access_blocked: {
@@ -35732,7 +35908,7 @@ var REFUSALS = {
35732
35908
  why: "Kutrion does not display, copy, export or share this credential's value. It is still used to authenticate connections and integrations.",
35733
35909
  action: "Use the credential rather than reading it \u2014 `kt ssh <connId>` or `kt exec <connId> <command>` broker a session with it without the value passing through this terminal."
35734
35910
  },
35735
- // modules/ssh.ts:470, modules/rdp.ts:151.
35911
+ // modules/ssh.ts:473, modules/rdp.ts:151.
35736
35912
  inject_only_requires_write: {
35737
35913
  title: "Write access required for a No-reveal credential",
35738
35914
  why: "Pointing a connection at a No-reveal credential needs write access to the vault holding it, because the person doing it decides where the credential gets presented.",
@@ -35929,11 +36105,15 @@ var STATUS_ACTION = {
35929
36105
  422: "The server understood the request but refused its values. Check the values passed against `kt --help`.",
35930
36106
  429: "Too many requests in a short period. Wait before retrying rather than looping."
35931
36107
  };
36108
+ var HUB_SIGN_IN_ACTION = {
36109
+ status: "This machine's session was not accepted. Run `kt login`, it signs in through Kutrion Hub.",
36110
+ signed_out: "Run `kt login`, it signs in through Kutrion Hub."
36111
+ };
35932
36112
  var SERVER_REFUSALS = /* @__PURE__ */ new Set(["payment_unconfirmed"]);
35933
36113
  var sentenceKey = (t) => t.toLowerCase().replace(/[^a-z0-9]+/g, "");
35934
36114
  var sameSentence = (a, b) => b !== void 0 && sentenceKey(a) === sentenceKey(b);
35935
36115
  var SIGNIN_CODE_REFUSALS = /* @__PURE__ */ new Set(["email_code_required", "signin_code_cap"]);
35936
- function refusalLines(e, indent = " ") {
36116
+ function refusalLines(e, indent = " ", opts2 = {}) {
35937
36117
  if (!(e instanceof ApiError)) return null;
35938
36118
  if (e.status < 400 || e.status >= 500 && !SERVER_REFUSALS.has(refusalCode(e))) return null;
35939
36119
  const dest = parseDestinationRefusal(e.body);
@@ -35956,11 +36136,12 @@ function refusalLines(e, indent = " ") {
35956
36136
  `${indent}kt conn set <id> --jump none`
35957
36137
  ];
35958
36138
  }
35959
- const code = refusalCode(e);
36139
+ const fromRefresh = e.refreshCode !== void 0 && Object.hasOwn(REFUSALS, e.refreshCode);
36140
+ const code = fromRefresh ? e.refreshCode : refusalCode(e);
35960
36141
  const platformFlag = platformFlagRefusalText(code);
35961
36142
  if (platformFlag) return [`Turned off on this deployment (${code}, HTTP ${e.status})`, "", platformFlag];
35962
36143
  const copy = Object.hasOwn(REFUSALS, code) ? REFUSALS[code] : void 0;
35963
- const server = SIGNIN_CODE_REFUSALS.has(code) ? null : serverText(e.body);
36144
+ const server = fromRefresh || SIGNIN_CODE_REFUSALS.has(code) ? null : serverText(e.body);
35964
36145
  const context = contextLines(e.body, indent);
35965
36146
  const head = copy ? `${copy.title} (${code}, HTTP ${e.status})` : `Refused by the server (${code}, HTTP ${e.status})`;
35966
36147
  const lines = [head, ""];
@@ -35969,7 +36150,9 @@ function refusalLines(e, indent = " ") {
35969
36150
  if (!copy && !server) lines.push("The server sent no explanation with this refusal.");
35970
36151
  if (context.length) lines.push("", ...context);
35971
36152
  lines.push("");
35972
- if (copy?.action) lines.push(copy.action);
36153
+ const hubAction = !opts2.hubSignIn || e.status !== 401 ? null : code === "session_signed_out" ? HUB_SIGN_IN_ACTION.signed_out : copy ? null : HUB_SIGN_IN_ACTION.status;
36154
+ if (hubAction) lines.push(hubAction);
36155
+ else if (copy?.action) lines.push(copy.action);
35973
36156
  else if (copy) lines.push("Nothing at this terminal changes this answer.");
35974
36157
  else lines.push(STATUS_ACTION[e.status] ?? `Quote the code and status above when reporting this; they are the whole of what the server sent.`);
35975
36158
  return lines;
@@ -36226,6 +36409,18 @@ function refuseBodyRedirects(inner = fetch) {
36226
36409
  }
36227
36410
  };
36228
36411
  }
36412
+ function trackWrites(inner) {
36413
+ let wrote = false;
36414
+ return {
36415
+ fetch: async (input, init) => {
36416
+ const res = await inner(input, init);
36417
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
36418
+ if (res.ok && method !== "GET") wrote = true;
36419
+ return res;
36420
+ },
36421
+ wrote: () => wrote
36422
+ };
36423
+ }
36229
36424
 
36230
36425
  // src/mcp-install.ts
36231
36426
  var appData = () => process.env.APPDATA ?? (0, import_path4.join)((0, import_os4.homedir)(), "AppData", "Roaming");
@@ -37408,11 +37603,11 @@ function parsePurposes(v) {
37408
37603
  }
37409
37604
  return out.length ? out : { error: `--purposes is required: one or more of ${ENROLL_PURPOSES.join(", ")}` };
37410
37605
  }
37411
- function parseConfirms(argv) {
37606
+ function parseConfirms(argv2) {
37412
37607
  const out = /* @__PURE__ */ new Map();
37413
- for (let i = 0; i < argv.length; i++) {
37414
- if (argv[i] !== "--confirm") continue;
37415
- const v = argv[i + 1];
37608
+ for (let i = 0; i < argv2.length; i++) {
37609
+ if (argv2[i] !== "--confirm") continue;
37610
+ const v = argv2[i + 1];
37416
37611
  if (v === void 0 || v.startsWith("--")) return { error: "--confirm needs <connId|host>=<fingerprint>" };
37417
37612
  i++;
37418
37613
  for (const pair of v.split(",").map((s) => s.trim()).filter(Boolean)) {
@@ -38148,10 +38343,11 @@ var ROLES = ["OWNER", "ADMIN", "EDITOR", "VIEWER"];
38148
38343
  var TOKEN_SCOPES = ["scim", "events"];
38149
38344
  var ORG_ROLES = ["owner", "admin", "member"];
38150
38345
  var ROTATION_SERVICES = ["linux_password", "postgres_password", "mysql_password"];
38151
- var VERSION = true ? "1.2.11" : "dev";
38346
+ var VERSION = true ? "1.2.14" : "dev";
38152
38347
  var HELP = `kt \u2014 Kutrion CLI
38153
38348
  kt login [--email <e>] [--password-stdin] [--code <2fa>] prompts for the password, or reads it from stdin;
38154
- at a terminal with no --email, first asks the Kutrion desktop app,
38349
+ with no --email or --password-stdin, first asks the Kutrion desktop app
38350
+ (without a terminal, only one this CLI has already paired with),
38155
38351
  then falls back to your email and password;
38156
38352
  at the emailed-code prompt, a blank entry or r sends a new code
38157
38353
  kt login --browser not available; exits with an error
@@ -38465,6 +38661,34 @@ function runForward(base, token, connId, spec, ctx = {}) {
38465
38661
  console.log(`forwarding 127.0.0.1:${localPort} -> ${dstHost}:${dstPort} via ${connId} (Ctrl-C to stop)`);
38466
38662
  });
38467
38663
  }
38664
+ function hubCallFor(base) {
38665
+ return async (method, path, body, bearer) => {
38666
+ try {
38667
+ const res = await refuseBodyRedirects()(base + path, {
38668
+ method,
38669
+ headers: { Authorization: `Bearer ${bearer}`, Accept: "application/json", ...body === void 0 ? {} : { "Content-Type": "application/json" } },
38670
+ body: body === void 0 ? void 0 : JSON.stringify(body),
38671
+ signal: AbortSignal.timeout(15e3)
38672
+ });
38673
+ return { ok: res.ok, status: res.status, data: res.ok ? await res.json().catch(() => null) : null };
38674
+ } catch {
38675
+ return { ok: false, status: null, data: null };
38676
+ }
38677
+ };
38678
+ }
38679
+ var OWN_SIGN_IN = /* @__PURE__ */ new Set(["login", "logout", "mcp", "ci-login", "run", "enroll", "share-redeem", "help", "--help", "-h"]);
38680
+ var SESSION_REFUSED = /* @__PURE__ */ new Set(["unauthorized", "session_signed_out"]);
38681
+ var sessionWrites = trackWrites(refuseBodyRedirects());
38682
+ function refusedSavedSession(e, argv2) {
38683
+ if (!(e instanceof ApiError) || e.status !== 401 || !SESSION_REFUSED.has(refusalCode(e)) || OWN_SIGN_IN.has(argv2.cmd)) return false;
38684
+ if (Object.entries(argv2.flags).some(([k, v]) => k.endsWith("-stdin") && v === true)) return false;
38685
+ const base = resolveBase(argv2.flags.base);
38686
+ try {
38687
+ return Boolean(base && getToken(base));
38688
+ } catch {
38689
+ return false;
38690
+ }
38691
+ }
38468
38692
  async function main() {
38469
38693
  const { cmd, positionals, flags } = parseArgs(process.argv.slice(2));
38470
38694
  const asJson = flags.json === true;
@@ -38520,7 +38744,7 @@ run \`kt --help\` for the flags each command takes`);
38520
38744
  // blank User-Agent it never set. Relaxing only, and never required — an older CLI that omits it
38521
38745
  // is treated exactly as it is today.
38522
38746
  client: "cli",
38523
- fetchImpl: refuseBodyRedirects(),
38747
+ fetchImpl: sessionWrites.fetch,
38524
38748
  // #2984: only a hub borrow leaves a refresh token, in the OS keychain beside the access token.
38525
38749
  getRefreshToken: () => {
38526
38750
  try {
@@ -38555,19 +38779,7 @@ run \`kt --help\` for the flags each command takes`);
38555
38779
  }
38556
38780
  });
38557
38781
  const kc = { get: getToken, set: setToken, clear: clearToken };
38558
- const hubCall = async (method, path, body, bearer) => {
38559
- try {
38560
- const res = await refuseBodyRedirects()(cfg.base + path, {
38561
- method,
38562
- headers: { Authorization: `Bearer ${bearer}`, Accept: "application/json", ...body === void 0 ? {} : { "Content-Type": "application/json" } },
38563
- body: body === void 0 ? void 0 : JSON.stringify(body),
38564
- signal: AbortSignal.timeout(15e3)
38565
- });
38566
- return { ok: res.ok, status: res.status, data: res.ok ? await res.json().catch(() => null) : null };
38567
- } catch {
38568
- return { ok: false, status: null, data: null };
38569
- }
38570
- };
38782
+ const hubCall = hubCallFor(cfg.base);
38571
38783
  const connectionContext = async (connId) => {
38572
38784
  try {
38573
38785
  const rows = (await client.listConnections()).connections;
@@ -38677,7 +38889,8 @@ run \`kt --help\` for the flags each command takes`);
38677
38889
  console.error("kt login --browser is not available: browser sign-in is switched off. Sign in with the Kutrion app (`kt login`), or use `kt login --email <email> --password-stdin`.");
38678
38890
  process.exit(EXIT_ERROR);
38679
38891
  }
38680
- if (process.stdin.isTTY && flags.email === void 0 && !fromStdin && flags.code === void 0 && accountRoot) {
38892
+ const tty = canPromptYes(process.stdin, process.stderr);
38893
+ if (accountRoot && loginTriesHub(flags, accountRoot)) {
38681
38894
  const hub = await hubLogin({
38682
38895
  base: cfg.base,
38683
38896
  location: hubLocation(accountRoot),
@@ -38685,7 +38898,8 @@ run \`kt --help\` for the flags each command takes`);
38685
38898
  keychainAvailable: osKeychainAvailable,
38686
38899
  call: hubCall,
38687
38900
  out: (l) => console.error(l),
38688
- ask: (q) => promptLine(q)
38901
+ ask: (q) => promptLine(q),
38902
+ pairedOnly: !tty
38689
38903
  });
38690
38904
  if (hub.kind === "store_failed") die(hub.note);
38691
38905
  if (hub.kind === "signed_in") {
@@ -38695,7 +38909,7 @@ run \`kt --help\` for the flags each command takes`);
38695
38909
  break;
38696
38910
  }
38697
38911
  if (hub.note) console.error(hub.note);
38698
- if (hubAbsent(hub.reason)) console.error("Installing the Kutrion app... Run `kt login` again once it is open, or sign in with your email and password below.");
38912
+ if (tty && hubAbsent(hub.reason)) console.error("Installing the Kutrion app... Run `kt login` again once it is open, or sign in with your email and password below.");
38699
38913
  }
38700
38914
  if (flags.email === void 0 && !fromStdin) {
38701
38915
  const hint = otherAppsHint(accountRoot);
@@ -38755,6 +38969,7 @@ run \`kt --help\` for the flags each command takes`);
38755
38969
  })()
38756
38970
  }).catch(() => ({ kind: "skipped", note: null }));
38757
38971
  recordSignIn(accountRoot, signedIn);
38972
+ recordLastAccountId(cfg.base, kc, signedIn);
38758
38973
  console.log(`logged in (token stored in ${backendName()})`);
38759
38974
  if (rememberBase(cfg.base)) console.log(`server saved: ${cfg.base} (later commands need no --base)`);
38760
38975
  if (registered.kind === "skipped" && registered.note) console.error(registered.note);
@@ -40280,12 +40495,36 @@ run \`kt --help\` for the list of commands`);
40280
40495
  }
40281
40496
  }
40282
40497
  installFetchProxy();
40283
- main().catch((e) => {
40498
+ var argv = parseArgs(process.argv.slice(2));
40499
+ var hubSlots = { get: getToken, set: setToken, clear: clearToken };
40500
+ var hubSignInAvailable = (e) => e instanceof ApiError && e.status === 401 && appDataDir() !== null && osKeychainAvailable();
40501
+ retryAfterHubReborrow(main, {
40502
+ refusedSession: (e) => refusedSavedSession(e, argv),
40503
+ wrote: sessionWrites.wrote,
40504
+ lastAccount: () => lastAccountId(resolveBase(argv.flags.base), hubSlots),
40505
+ reborrow: async () => {
40506
+ const base = resolveBase(argv.flags.base);
40507
+ const root = appDataDir();
40508
+ if (!root) return { kind: "fallback", note: null, reason: "hub_not_running" };
40509
+ const hub = await reborrowFromHub({
40510
+ base,
40511
+ location: hubLocation(root),
40512
+ kc: hubSlots,
40513
+ keychainAvailable: osKeychainAvailable,
40514
+ call: hubCallFor(base),
40515
+ out: (l) => console.error(l),
40516
+ ask: (q) => promptLine(q)
40517
+ });
40518
+ if (hub.kind === "signed_in") recordSignIn(root, hub.user);
40519
+ return hub.kind === "fallback" && hubAbsent(hub.reason) ? { ...hub, note: null } : hub;
40520
+ },
40521
+ out: (l) => console.error(l)
40522
+ }).catch((e) => {
40284
40523
  const api3 = e instanceof ApiError ? e : null;
40285
40524
  const code = api3 ? exitForStatus(api3.status) : EXIT_ERROR;
40286
40525
  const message = redactProxyUrls(e instanceof Error ? e.message : String(e));
40287
- const lines = (storeFailure(e) ?? refusalLines(e) ?? [`error: ${message}`]).map(redactProxyUrls);
40288
- if (parseArgs(process.argv.slice(2)).flags.json === true) {
40526
+ const lines = (e instanceof HubAccountSwitchedError ? [e.message] : storeFailure(e) ?? refusalLines(e, " ", { hubSignIn: hubSignInAvailable(e) }) ?? [`error: ${message}`]).map(redactProxyUrls);
40527
+ if (argv.flags.json === true) {
40289
40528
  console.log(JSON.stringify({
40290
40529
  error: api3 ? refusalCode(api3) : message,
40291
40530
  ...api3 ? { status: api3.status } : {},