@datarails-ai/dr-cli 1.6.99 → 1.6.100

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/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.99",
11
+ version: "1.6.100",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-05T12:03:05.855Z";
72
+ var BUILD_TIME = "2026-10-05T12:55:55.787Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -5007,7 +5007,7 @@ async function runLogout(cfg) {
5007
5007
  const session2 = await cfg.store.getSession(key, meta.email);
5008
5008
  if (session2 === void 0) continue;
5009
5009
  try {
5010
- await revoker(host2, session2);
5010
+ writeNotes(cfg.out, await revoker(host2, session2));
5011
5011
  } catch {
5012
5012
  }
5013
5013
  await cfg.store.removeSession(key, meta.email);
@@ -5040,32 +5040,95 @@ async function runLogout(cfg) {
5040
5040
  }
5041
5041
  const host = resolve(cfg.serverKey).host;
5042
5042
  try {
5043
- await revoker(host, session);
5043
+ writeNotes(cfg.out, await revoker(host, session));
5044
5044
  } catch {
5045
5045
  }
5046
5046
  await cfg.store.removeSession(cfg.serverKey, email);
5047
5047
  cfg.out.write(`Logged out of ${cfg.serverKey} as ${email}.
5048
5048
  `);
5049
5049
  }
5050
+ function writeNotes(out, notes) {
5051
+ for (const line of notes ?? []) out.write(`${line}
5052
+ `);
5053
+ }
5054
+ var REVOKE_TIMEOUT_MS = 5e3;
5050
5055
  async function bestEffortLogout(host, session) {
5051
- if (host === "" || isCookielessSession(session)) return;
5056
+ if (host === "") return [];
5052
5057
  const base = host.replace(/\/+$/, "");
5053
- const controller = new AbortController();
5054
- const timeout = setTimeout(() => controller.abort(), 5e3);
5055
- try {
5056
- await fetch(`${base}/oauth/revoke/`, {
5058
+ if (usesApiKey(session)) {
5059
+ return [
5060
+ "Note: the API key is still valid server-side until it is revoked where it was issued; only the local copy was removed."
5061
+ ];
5062
+ }
5063
+ const refresh = session.jwtRefresh ?? "";
5064
+ const hasCookieSession = !isCookielessSession(session) && session.sessionId !== "" && session.csrfToken !== "";
5065
+ if (refresh === "" && !hasCookieSession) {
5066
+ return [
5067
+ `Warning: no refresh token is stored for this identity, so nothing was revoked on ${base}; only the local copy was removed.`
5068
+ ];
5069
+ }
5070
+ const [refreshFailure, sessionFailure] = await Promise.all([
5071
+ refresh === "" ? void 0 : revokeRefreshToken(base, refresh),
5072
+ hasCookieSession ? endCookieSession(base, session) : void 0
5073
+ ]);
5074
+ const notes = [];
5075
+ if (refreshFailure !== void 0) {
5076
+ notes.push(
5077
+ `Warning: could not revoke the refresh token on ${base} (${refreshFailure}); it stays valid server-side until it expires.`
5078
+ );
5079
+ }
5080
+ if (sessionFailure !== void 0) {
5081
+ notes.push(
5082
+ `Warning: could not end the browser session on ${base} (${sessionFailure}); it stays valid server-side until it expires.`
5083
+ );
5084
+ }
5085
+ return notes;
5086
+ }
5087
+ async function revokeRefreshToken(base, refresh) {
5088
+ return withTimeout(async (signal) => {
5089
+ const res = await fetch(`${base}/jwt/api/token/revoke/`, {
5090
+ method: "POST",
5091
+ headers: { "Content-Type": "application/json" },
5092
+ body: JSON.stringify({ refresh }),
5093
+ signal
5094
+ });
5095
+ await discardBody(res);
5096
+ return res.status === 204 ? void 0 : `HTTP ${res.status}`;
5097
+ });
5098
+ }
5099
+ async function endCookieSession(base, session) {
5100
+ return withTimeout(async (signal) => {
5101
+ const res = await fetch(`${base}/api/accounts/logout/`, {
5057
5102
  method: "POST",
5058
5103
  headers: {
5059
5104
  Cookie: `sessionid=${session.sessionId}; csrftoken=${session.csrfToken}`,
5060
- "X-CSRFToken": session.csrfToken
5105
+ "X-CSRFToken": session.csrfToken,
5106
+ Referer: `${base}/`
5061
5107
  },
5062
- signal: controller.signal
5108
+ redirect: "manual",
5109
+ signal
5063
5110
  });
5111
+ await discardBody(res);
5112
+ return res.status >= 200 && res.status < 400 ? void 0 : `HTTP ${res.status}`;
5113
+ });
5114
+ }
5115
+ async function withTimeout(run) {
5116
+ const controller = new AbortController();
5117
+ const timeout = setTimeout(() => controller.abort(), REVOKE_TIMEOUT_MS);
5118
+ try {
5119
+ return await run(controller.signal);
5064
5120
  } catch {
5121
+ return controller.signal.aborted ? "timed out" : "network error";
5065
5122
  } finally {
5066
5123
  clearTimeout(timeout);
5067
5124
  }
5068
5125
  }
5126
+ async function discardBody(res) {
5127
+ try {
5128
+ await res.text();
5129
+ } catch {
5130
+ }
5131
+ }
5069
5132
 
5070
5133
  // src/commands/login.ts
5071
5134
  import { Option } from "commander";
@@ -5172,7 +5235,7 @@ function tryParseErrorCode(text2) {
5172
5235
  return typeof errVal === "string" ? errVal : "";
5173
5236
  }
5174
5237
  async function pollForToken(host, deviceCode, options = {}) {
5175
- return poll(host, PATHS.legacy.token, deviceCode, options, (text2) => {
5238
+ return poll(host, PATHS.legacy.token, deviceCode, options, {}, (text2) => {
5176
5239
  const tok = parseTokenResponse(text2);
5177
5240
  if (!tok.sessionId || !tok.csrfToken) {
5178
5241
  throw new Error("token response missing session_id or csrf_token");
@@ -5181,7 +5244,7 @@ async function pollForToken(host, deviceCode, options = {}) {
5181
5244
  });
5182
5245
  }
5183
5246
  async function pollForV2Token(host, deviceCode, options = {}) {
5184
- return poll(host, PATHS.v2.token, deviceCode, options, (text2) => {
5247
+ return poll(host, PATHS.v2.token, deviceCode, options, V2_TERMINAL_HINTS, (text2) => {
5185
5248
  const obj = parseObject(text2);
5186
5249
  const access = typeof obj.access === "string" ? obj.access : "";
5187
5250
  const refresh = typeof obj.refresh === "string" ? obj.refresh : "";
@@ -5191,7 +5254,11 @@ async function pollForV2Token(host, deviceCode, options = {}) {
5191
5254
  return { access, refresh };
5192
5255
  });
5193
5256
  }
5194
- async function poll(host, path11, deviceCode, options, onSuccess) {
5257
+ var V2_TERMINAL_HINTS = {
5258
+ access_denied: " (the login was refused, expired, or already used: run `dr login` again)",
5259
+ token_issuance_failed: " (the server could not issue the token: run `dr login` again to start a new login)"
5260
+ };
5261
+ async function poll(host, path11, deviceCode, options, terminalHints, onSuccess) {
5195
5262
  const interval = options.interval ?? 2e3;
5196
5263
  const maxWait = options.maxWait ?? 6e5;
5197
5264
  const now = options.now ?? Date.now;
@@ -5227,7 +5294,8 @@ async function poll(host, path11, deviceCode, options, onSuccess) {
5227
5294
  continue;
5228
5295
  }
5229
5296
  if (errCode) {
5230
- throw new Error(`token error: ${errCode}`);
5297
+ const hint = Object.hasOwn(terminalHints, errCode) ? terminalHints[errCode] : "";
5298
+ throw new Error(`token error: ${errCode}${hint}`);
5231
5299
  }
5232
5300
  throw new Error(`token HTTP ${res.status}: ${text2.trim()}`);
5233
5301
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.99",
3
+ "cliVersion": "1.6.100",
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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.99",
3
+ "version": "1.6.100",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {