@datarails-ai/dr-cli 1.6.172 → 1.6.174

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -302,10 +302,15 @@ interactively instead. An API-key file is different: see below.
302
302
  > `dr users can-login-as`, mints an impersonation JWT from the monolith
303
303
  > (`POST /jwt/api/loginas/`), and stores it as a switchable identity keyed by
304
304
  > `(server, email)` — so later commands target it with
305
- > `--account <email>`. The impersonated token refreshes silently, and a dead
306
- > refresh auto-remints from the operator, so you're never re-prompted until
307
- > the operator's own login expires. Use `--as <operator-email>` to pick the
308
- > operator when several are stored, or `--user-id <n>` to skip the lookup.
305
+ > `--account <email>`. The impersonated token refreshes silently, and unless
306
+ > the customer approves each login-as, a dead refresh auto-remints from the
307
+ > operator, so you're never re-prompted until the operator's own login
308
+ > expires. Where the customer does approve each login-as, the first
309
+ > `dr login-as` sends their admins a request and exits 4 until it is
310
+ > approved; once the access ends or can no longer be renewed, commands on
311
+ > that identity exit 5, the identity is dropped and nothing re-logs in by
312
+ > itself. Use `--as <operator-email>` to pick the operator when several are
313
+ > stored, or `--user-id <n>` to skip the lookup.
309
314
 
310
315
  ### Tenant pin (one server, one account)
311
316
 
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@datarails-ai/dr-cli",
11
- version: "1.6.172",
11
+ version: "1.6.174",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -40,7 +40,7 @@ var package_default = {
40
40
  postinstall: "node scripts/postinstall.mjs"
41
41
  },
42
42
  dependencies: {
43
- "@datarails-ai/dr-appspace-cli": "0.12.43",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.47",
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "@napi-rs/keyring": "^1.3.0",
46
46
  commander: "^12.1.0",
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-27T10:08:40.001Z";
72
+ var BUILD_TIME = "2026-10-28T22:47:13.870Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -780,12 +780,16 @@ var LoginAsError = class _LoginAsError extends Error {
780
780
  httpStatus;
781
781
  errorCode;
782
782
  detail;
783
+ requestId;
784
+ expiresAt;
783
785
  constructor(httpStatus, info = {}) {
784
786
  super(_LoginAsError.buildMessage(httpStatus, info));
785
787
  this.name = "LoginAsError";
786
788
  this.httpStatus = httpStatus;
787
789
  this.errorCode = info.code;
788
790
  this.detail = info.detail;
791
+ this.requestId = info.requestId;
792
+ this.expiresAt = info.expiresAt;
789
793
  }
790
794
  /** Alias so callers/tests can read `.code` symmetrically with the sibling errors. */
791
795
  get code() {
@@ -800,6 +804,41 @@ var LoginAsError = class _LoginAsError extends Error {
800
804
  return `login-as failed: HTTP ${status2}${body}`;
801
805
  }
802
806
  };
807
+ var LOGIN_AS_AWAITING_APPROVAL_EXIT_CODE = 4;
808
+ var LOGIN_AS_GRANT_ENDED_EXIT_CODE = 5;
809
+ var LoginAsAwaitingApprovalError = class _LoginAsAwaitingApprovalError extends Error {
810
+ exitCode = LOGIN_AS_AWAITING_APPROVAL_EXIT_CODE;
811
+ code;
812
+ requestId;
813
+ expiresAt;
814
+ constructor(err, targetEmail) {
815
+ super(_LoginAsAwaitingApprovalError.buildMessage(err, targetEmail), { cause: err });
816
+ this.name = "LoginAsAwaitingApprovalError";
817
+ this.code = err.code ?? "";
818
+ this.requestId = err.requestId;
819
+ this.expiresAt = err.expiresAt;
820
+ }
821
+ static isAwaitingCode(code) {
822
+ return code === "login_as_requested" || code === "login_as_pending";
823
+ }
824
+ static buildMessage(err, targetEmail) {
825
+ const facts = [
826
+ ...err.requestId !== void 0 ? [`request ${err.requestId}`] : [],
827
+ ...err.expiresAt !== void 0 ? [`expires ${err.expiresAt}`] : []
828
+ ];
829
+ const tail = facts.length > 0 ? ` (${facts.join(", ")})` : "";
830
+ const head = err.code === "login_as_requested" ? `Login as ${targetEmail} needs the customer's approval: a request was sent to their admins${tail}.` : `Login as ${targetEmail} is still waiting for the customer's admins to approve${tail}.`;
831
+ return `${head} Run dr login-as again once it is approved; do not retry before that. (${err.code ?? ""})`;
832
+ }
833
+ };
834
+ var LoginAsGrantEndedError = class extends Error {
835
+ code = "login_as_grant_ended";
836
+ exitCode = LOGIN_AS_GRANT_ENDED_EXIT_CODE;
837
+ constructor(message, options) {
838
+ super(message ?? "login-as access ended", options);
839
+ this.name = "LoginAsGrantEndedError";
840
+ }
841
+ };
803
842
  var AmbiguousAccountError = class extends Error {
804
843
  code = "ERR_AMBIGUOUS_ACCOUNT";
805
844
  /** The emails stored for the server, sorted most-recently-used first. */
@@ -1061,6 +1100,21 @@ async function refreshJwt(host, refreshToken, options) {
1061
1100
  }
1062
1101
  return { access: out.access, refresh: out.refresh, expiresAt };
1063
1102
  }
1103
+ function isGrantEndedResponse(status2, text2) {
1104
+ if (status2 !== 401) return false;
1105
+ try {
1106
+ const parsed2 = JSON.parse(text2);
1107
+ return parsed2 !== null && typeof parsed2 === "object" && parsed2["code"] === "login_as_grant_ended";
1108
+ } catch {
1109
+ return false;
1110
+ }
1111
+ }
1112
+ var RefreshRejectedError = class extends Error {
1113
+ constructor(message) {
1114
+ super(message);
1115
+ this.name = "RefreshRejectedError";
1116
+ }
1117
+ };
1064
1118
  async function refreshExternalJwt(host, refreshToken, options) {
1065
1119
  const base = trimTrailingSlash(host);
1066
1120
  const res = await fetch(`${base}/jwt/api/external/token/refresh/`, {
@@ -1070,8 +1124,12 @@ async function refreshExternalJwt(host, refreshToken, options) {
1070
1124
  signal: options?.signal
1071
1125
  });
1072
1126
  const text2 = await res.text();
1127
+ if (isGrantEndedResponse(res.status, text2)) {
1128
+ throw new LoginAsGrantEndedError("jwt external refresh: HTTP 401 (login_as_grant_ended)");
1129
+ }
1073
1130
  if (!res.ok) {
1074
- throw new Error(`jwt external refresh: HTTP ${res.status}: ${truncate(text2, TRUNCATE)}`);
1131
+ const message = `jwt external refresh: HTTP ${res.status}: ${truncate(text2, TRUNCATE)}`;
1132
+ throw res.status === 401 ? new RefreshRejectedError(message) : new Error(message);
1075
1133
  }
1076
1134
  const out = parseTokenBody(text2, "refresh");
1077
1135
  if (out.access === "") {
@@ -1172,13 +1230,23 @@ async function loginAs(host, operatorAccessToken, userId, options) {
1172
1230
  if (!res.ok) {
1173
1231
  let code;
1174
1232
  let detail;
1233
+ let requestId;
1234
+ let expiresAt2;
1175
1235
  try {
1176
1236
  const parsed2 = JSON.parse(text2);
1177
1237
  if (typeof parsed2["code"] === "string") code = parsed2["code"];
1178
1238
  if (typeof parsed2["detail"] === "string") detail = parsed2["detail"];
1239
+ if (Number.isSafeInteger(parsed2["request_id"])) requestId = parsed2["request_id"];
1240
+ if (typeof parsed2["expires_at"] === "string") expiresAt2 = parsed2["expires_at"];
1179
1241
  } catch {
1180
1242
  }
1181
- throw new LoginAsError(res.status, { code, detail, body: truncate(text2, TRUNCATE) });
1243
+ throw new LoginAsError(res.status, {
1244
+ code,
1245
+ detail,
1246
+ requestId,
1247
+ expiresAt: expiresAt2,
1248
+ body: truncate(text2, TRUNCATE)
1249
+ });
1182
1250
  }
1183
1251
  let raw2;
1184
1252
  try {
@@ -1531,7 +1599,42 @@ async function ensureJwt(store, serverKey, email, opts) {
1531
1599
  const detail = reasons.length > 0 ? reasons.join("; ") : "no recovery path available";
1532
1600
  throw new SessionExpiredError(`${serverKey}:${email} (${detail})`);
1533
1601
  }
1602
+ var APPROVAL_REQUIRED_GRANT_MODE = "approval_required";
1603
+ function isApprovalRequiredGrant(session) {
1604
+ for (const token of [session.jwtAccess, session.jwtRefresh]) {
1605
+ if (token === void 0 || token === "") continue;
1606
+ try {
1607
+ return decodeJwtClaims(token)["grant_mode"] === APPROVAL_REQUIRED_GRANT_MODE;
1608
+ } catch {
1609
+ continue;
1610
+ }
1611
+ }
1612
+ return false;
1613
+ }
1614
+ async function dropLoginAs(store, serverKey, email, session) {
1615
+ if (!hasApiKey(session)) {
1616
+ await store.removeSession(serverKey, email).catch(() => false);
1617
+ return;
1618
+ }
1619
+ await store.saveSession(serverKey, {
1620
+ email,
1621
+ sessionId: "",
1622
+ csrfToken: "",
1623
+ savedAt: /* @__PURE__ */ new Date(),
1624
+ apiKey: session.apiKey,
1625
+ ...session.apiKeyExpiresAt !== void 0 && { apiKeyExpiresAt: session.apiKeyExpiresAt }
1626
+ }).catch(() => void 0);
1627
+ }
1628
+ async function endImpersonation(store, serverKey, email, session, message, cause) {
1629
+ await dropLoginAs(store, serverKey, email, session);
1630
+ return new LoginAsGrantEndedError(message, { cause });
1631
+ }
1632
+ function accessEndedMessage(serverKey, email) {
1633
+ return `login-as access to ${serverKey}:${email} has ended (the customer's admin ended it, or it expired). Run dr login-as ${email} --server ${serverKey} to log in again; where the customer approves logins, that sends them a new request.`;
1634
+ }
1534
1635
  async function recoverImpersonatedJwt(store, serverKey, email, session, host, opts) {
1636
+ let refreshRejected = session.jwtRefresh === void 0 || session.jwtRefresh === "";
1637
+ let refreshErr = null;
1535
1638
  if (session.jwtRefresh !== void 0 && session.jwtRefresh !== "" && host !== "") {
1536
1639
  try {
1537
1640
  const tok = await refreshExternalJwt(host, session.jwtRefresh, { signal: opts.signal });
@@ -1543,9 +1646,37 @@ async function recoverImpersonatedJwt(store, serverKey, email, session, host, op
1543
1646
  };
1544
1647
  await store.saveSession(serverKey, next2);
1545
1648
  return next2;
1546
- } catch {
1649
+ } catch (err) {
1650
+ if (err instanceof LoginAsGrantEndedError) {
1651
+ throw await endImpersonation(
1652
+ store,
1653
+ serverKey,
1654
+ email,
1655
+ session,
1656
+ accessEndedMessage(serverKey, email),
1657
+ err
1658
+ );
1659
+ }
1660
+ refreshRejected = err instanceof RefreshRejectedError;
1661
+ refreshErr = err;
1547
1662
  }
1548
1663
  }
1664
+ if (isApprovalRequiredGrant(session)) {
1665
+ if (!refreshRejected) {
1666
+ throw new Error(
1667
+ `login-as session ${serverKey}:${email} could not be renewed (${msgOf4(refreshErr)}); try again shortly.`,
1668
+ { cause: refreshErr }
1669
+ );
1670
+ }
1671
+ throw await endImpersonation(
1672
+ store,
1673
+ serverKey,
1674
+ email,
1675
+ session,
1676
+ `login-as session ${serverKey}:${email} could not be renewed, and this customer approves each login, so dr does not log in again on its own. Run dr login-as ${email} --server ${serverKey}; if the access has ended, that sends the customer's admins a new request.`,
1677
+ refreshErr
1678
+ );
1679
+ }
1549
1680
  const operatorEmail2 = session.impersonatorEmail;
1550
1681
  const userId = session.impersonatedUserId;
1551
1682
  if (userId === void 0) {
@@ -1570,10 +1701,29 @@ async function recoverImpersonatedJwt(store, serverKey, email, session, host, op
1570
1701
  `${serverKey}:${email} (operator ${operatorEmail2} has no JWT \u2014 re-run dr login --server ${serverKey})`
1571
1702
  );
1572
1703
  }
1573
- const result = await loginAs(host, operatorAccess, userId, {
1574
- prevRefresh: session.jwtRefresh,
1575
- signal: opts.signal
1576
- });
1704
+ let result;
1705
+ try {
1706
+ result = await loginAs(host, operatorAccess, userId, {
1707
+ prevRefresh: session.jwtRefresh,
1708
+ signal: opts.signal
1709
+ });
1710
+ } catch (err) {
1711
+ if (err instanceof LoginAsError && LoginAsAwaitingApprovalError.isAwaitingCode(err.code)) {
1712
+ await dropLoginAs(store, serverKey, email, session);
1713
+ throw new LoginAsAwaitingApprovalError(err, email);
1714
+ }
1715
+ if (err instanceof LoginAsError && err.code === "login_as_approval_required") {
1716
+ throw await endImpersonation(
1717
+ store,
1718
+ serverKey,
1719
+ email,
1720
+ session,
1721
+ accessEndedMessage(serverKey, email),
1722
+ err
1723
+ );
1724
+ }
1725
+ throw err;
1726
+ }
1577
1727
  const next = {
1578
1728
  ...session,
1579
1729
  jwtAccess: result.access,
@@ -2454,9 +2604,11 @@ var AuthorizedClient = class _AuthorizedClient {
2454
2604
  this._session = fresh;
2455
2605
  res = await this.doWithAuth(method, path13, body, options, true);
2456
2606
  } catch (err) {
2607
+ _AuthorizedClient.throwIfLoginAsOutcome(err);
2457
2608
  _AuthorizedClient.logRefreshFailure("post-401 force-refresh", err);
2458
2609
  }
2459
2610
  await _AuthorizedClient.throwIfScopeDenied(method, path13, res);
2611
+ await this.throwIfGrantEnded(method, path13, res);
2460
2612
  }
2461
2613
  if (!this.isAuthRejected(res) && res.ok) {
2462
2614
  try {
@@ -2466,6 +2618,12 @@ var AuthorizedClient = class _AuthorizedClient {
2466
2618
  }
2467
2619
  return res;
2468
2620
  }
2621
+ /** A login-as outcome (DR-56077) is the answer, not a refresh failure to route around. */
2622
+ static throwIfLoginAsOutcome(err) {
2623
+ if (err instanceof LoginAsGrantEndedError || err instanceof LoginAsAwaitingApprovalError) {
2624
+ throw err;
2625
+ }
2626
+ }
2469
2627
  /**
2470
2628
  * Surface an auto-refresh failure on stderr. Always logs (no env-var gate)
2471
2629
  * — silent failures here historically masked server-side issues (refresh
@@ -2808,12 +2966,14 @@ var AuthorizedClient = class _AuthorizedClient {
2808
2966
  refreshed = true;
2809
2967
  res = await this.doWithAuth(method, path13, body, options, true);
2810
2968
  } catch (err) {
2969
+ _AuthorizedClient.throwIfLoginAsOutcome(err);
2811
2970
  refreshErr = err;
2812
2971
  }
2813
2972
  }
2814
2973
  if (this.isAuthRejected(res)) {
2815
2974
  const text3 = await res.text();
2816
2975
  _AuthorizedClient.throwIfScopeDeniedBody(method, path13, res.status, text3);
2976
+ await this.throwIfGrantEndedBody(method, path13, res.status, text3);
2817
2977
  if (refreshed || triedSessionOnly) {
2818
2978
  const attempted = refreshed ? "session-only retry and JWT refresh" : "session-only retry";
2819
2979
  throw new Error(
@@ -2876,6 +3036,7 @@ var AuthorizedClient = class _AuthorizedClient {
2876
3036
  });
2877
3037
  this._session = fresh;
2878
3038
  } catch (err) {
3039
+ _AuthorizedClient.throwIfLoginAsOutcome(err);
2879
3040
  _AuthorizedClient.logRefreshFailure("pre-flight", err);
2880
3041
  }
2881
3042
  }
@@ -2916,6 +3077,26 @@ var AuthorizedClient = class _AuthorizedClient {
2916
3077
  }
2917
3078
  _AuthorizedClient.throwIfScopeDeniedBody(method, path13, res.status, text2);
2918
3079
  }
3080
+ /** The ladder ended on 401 `login_as_grant_ended` even though the refresh passed: drop the
3081
+ * identity and exit 5, as when the refresh itself reports the end. */
3082
+ async throwIfGrantEnded(method, path13, res) {
3083
+ if (res.status !== 401) return;
3084
+ let text2;
3085
+ try {
3086
+ text2 = await res.clone().text();
3087
+ } catch {
3088
+ return;
3089
+ }
3090
+ await this.throwIfGrantEndedBody(method, path13, res.status, text2);
3091
+ }
3092
+ /** {@link AuthorizedClient.throwIfGrantEnded} for a body already in hand. */
3093
+ async throwIfGrantEndedBody(method, path13, status2, text2) {
3094
+ if (!isGrantEndedResponse(status2, text2)) return;
3095
+ await dropLoginAs(this._store, this._serverKey, this._email, this._session);
3096
+ throw new LoginAsGrantEndedError(
3097
+ `${method} ${path13}: ${accessEndedMessage(this._serverKey, this._email)}`
3098
+ );
3099
+ }
2919
3100
  /** {@link AuthorizedClient.throwIfScopeDenied} for a body already in hand — the multipart
2920
3101
  * methods and `requestJson`'s final branch have read theirs and must not read it twice. */
2921
3102
  static throwIfScopeDeniedBody(method, path13, status2, text2) {
@@ -6941,6 +7122,9 @@ function friendlyLoginAsError(err, targetEmail, explicitOperator) {
6941
7122
  explicitOperator
6942
7123
  );
6943
7124
  }
7125
+ if (LoginAsAwaitingApprovalError.isAwaitingCode(err.code)) {
7126
+ return new LoginAsAwaitingApprovalError(err, targetEmail);
7127
+ }
6944
7128
  switch (err.code) {
6945
7129
  case "not_permitted":
6946
7130
  return new Error(`You are not permitted to impersonate ${targetEmail}.`);
@@ -6948,6 +7132,11 @@ function friendlyLoginAsError(err, targetEmail, explicitOperator) {
6948
7132
  return new Error(`${targetEmail} is inactive and cannot be impersonated.`);
6949
7133
  case "impersonation_unavailable":
6950
7134
  return new Error("Impersonation service is temporarily unavailable; try again shortly.");
7135
+ case "login_as_approval_required":
7136
+ return new Error(
7137
+ `The approval for ${targetEmail} ended while logging in. Run dr login-as again; where the customer approves logins, that sends their admins a new request.`,
7138
+ { cause: err }
7139
+ );
6951
7140
  default:
6952
7141
  return err;
6953
7142
  }
@@ -79038,6 +79227,9 @@ function exitCodeFor(err) {
79038
79227
  if (err instanceof ScopeFilterError) return err.exitCode;
79039
79228
  if (err instanceof ScopeDeniedError) return SCOPE_EXIT_CODE;
79040
79229
  if (err instanceof TenantPinError) return err.exitCode;
79230
+ if (err instanceof LoginAsAwaitingApprovalError || err instanceof LoginAsGrantEndedError) {
79231
+ return err.exitCode;
79232
+ }
79041
79233
  return 1;
79042
79234
  }
79043
79235
  var UNKNOWN_OPTION_PATTERN = /(unknown option ')(-[\s\S]*)'/g;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.172",
3
+ "cliVersion": "1.6.174",
4
4
  "_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
5
5
  "commands": {
6
6
  "login": {
@@ -75,7 +75,10 @@ per server can be logged in at once.
75
75
  browser**. Needs an operator `login` first; stores a switchable identity you
76
76
  then target with `--account <email>`. `--as <operator-email>` picks the
77
77
  operator if several are stored; `--user-id <n>` skips the email lookup.
78
- Refreshes/auto-remints silently — no re-prompt until the operator expires.
78
+ Refreshes/auto-remints silently — no re-prompt until the operator expires,
79
+ except where the customer approves each login-as: exit **4** means it awaits
80
+ that approval, exit **5** that the access ended. Do not retry either; see
81
+ `dr-switch-user`.
79
82
  - `dr whoami --server US` — confirm who/where you are. Offline (decodes the local
80
83
  JWT, no network) — **except** an API-key credential holding no token its current
81
84
  key minted (a key written straight into a session file — e.g. a managed-agent
@@ -192,9 +195,11 @@ permission / tenant / feature-flag refusal, still exits **1**, and is worth a
192
195
  second look at `--server`, the org context, and the request body.
193
196
 
194
197
  **JWT refresh is automatic** — the CLI refreshes before each request and on
195
- 401/403, except on the two scope codes above, which no refresh can lift. Only
196
- re-`login` when you see `session expired` as the *final* error after an
197
- auto-refresh attempt. Do not re-login proactively on an expiry countdown.
198
+ 401/403, except on the two scope codes above, which no refresh can lift, and
199
+ on an impersonated identity that exits **4** or **5** (see `dr login-as`
200
+ above). Only re-`login` when you see `session expired` as the *final* error
201
+ after an auto-refresh attempt. Do not re-login proactively on an expiry
202
+ countdown.
198
203
 
199
204
  **Connectors over VPN (TLS):** `dr connectors …` reaches the control-plane over
200
205
  the corporate VPN. A TLS/certificate error there (`self signed certificate in
@@ -153,6 +153,17 @@ switchable `(server, email)` identity — no browser, no TTY prompt.
153
153
  - **Denials** surface as clear messages — *not permitted to impersonate*,
154
154
  *inactive*, *impersonation service unavailable*. Surface verbatim; do not
155
155
  retry; do not record a default.
156
+ - **Exit 4 — waiting for the customer's approval.** Some customers approve
157
+ each login-as. The first `dr login-as` sends their admins a request; until
158
+ an admin approves it, every run exits 4 with *a request was sent* or
159
+ *still waiting*. Surface it, stop, and tell the user to run `dr login-as`
160
+ again after the approval. Never retry in a loop.
161
+ - **Exit 5 — access ended.** Any command on an impersonated identity exits 5
162
+ when the customer's admin ends the access or it expires, or when an
163
+ approved login-as can no longer be renewed. The CLI drops the identity and
164
+ does not log in again by itself. Surface the message and stop; run
165
+ `dr login-as` again only if the user asks (it may send the customer's
166
+ admins a new request).
156
167
  - **`login-as` needs dr ≥ 1.3.24.** Older installs answer `unknown command
157
168
  'login-as'`. If the browser Login-As works but the CLI "can't", check
158
169
  `dr --version` / `dr update` first — that's the common cause, not permissions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.172",
3
+ "version": "1.6.174",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@datarails-ai/dr-appspace-cli": "0.12.43",
20
+ "@datarails-ai/dr-appspace-cli": "0.12.47",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "@napi-rs/keyring": "^1.3.0",
23
23
  "commander": "^12.1.0",