@datarails-ai/dr-cli 1.6.104 → 1.6.105
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.
|
|
11
|
+
version: "1.6.105",
|
|
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.11.
|
|
43
|
+
"@datarails-ai/dr-appspace-cli": "0.11.2",
|
|
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-
|
|
72
|
+
var BUILD_TIME = "2026-10-07T13:28:02.898Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -1165,13 +1165,8 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
|
|
|
1165
1165
|
inFlightExchanges.set(key, entry);
|
|
1166
1166
|
return joinExchange(entry, opts.signal);
|
|
1167
1167
|
}
|
|
1168
|
-
async function
|
|
1169
|
-
const
|
|
1170
|
-
if (stored === void 0) {
|
|
1171
|
-
throw new NotLoggedInError(`not logged in: ${serverKey}:${email}`);
|
|
1172
|
-
}
|
|
1173
|
-
const keyed = usesApiKey(stored);
|
|
1174
|
-
const unsaved = keyed ? unsavedExchanges.get(`${serverKey}:${email}`) : void 0;
|
|
1168
|
+
async function exchangeKeyedSession(store, serverKey, email, stored, opts) {
|
|
1169
|
+
const unsaved = unsavedExchanges.get(`${serverKey}:${email}`);
|
|
1175
1170
|
const session = unsaved !== void 0 && unsaved.apiKey === stored.apiKey ? {
|
|
1176
1171
|
...stored,
|
|
1177
1172
|
jwtAccess: unsaved.jwtAccess,
|
|
@@ -1184,16 +1179,40 @@ async function ensureJwt(store, serverKey, email, opts) {
|
|
|
1184
1179
|
// The store still holds whatever the failed save meant to remove.
|
|
1185
1180
|
jwtRefresh: void 0
|
|
1186
1181
|
} : stored;
|
|
1187
|
-
if (!opts.force &&
|
|
1188
|
-
session,
|
|
1189
|
-
keyed ? apiKeyStaleBuffer(session, `${serverKey}:${email}`, opts.buffer) : opts.buffer
|
|
1190
|
-
)) {
|
|
1182
|
+
if (!opts.force && mintedByStoredApiKey(session) && session.jwtAccess !== void 0 && session.jwtAccess !== "" && !jwtExpiringSoon(session, apiKeyStaleBuffer(session, `${serverKey}:${email}`, opts.buffer))) {
|
|
1191
1183
|
return session;
|
|
1192
1184
|
}
|
|
1193
1185
|
const host = opts.host ?? resolve(serverKey).host;
|
|
1194
|
-
|
|
1195
|
-
|
|
1186
|
+
return exchangeForSession(store, serverKey, email, session, host, opts);
|
|
1187
|
+
}
|
|
1188
|
+
async function ensureApiKeyJwt(store, serverKey, email, options) {
|
|
1189
|
+
const stored = options?.session ?? await store.getSession(serverKey, email);
|
|
1190
|
+
if (stored === void 0) {
|
|
1191
|
+
throw new NotLoggedInError(`not logged in: ${serverKey}:${email}`);
|
|
1192
|
+
}
|
|
1193
|
+
if (!usesApiKey(stored)) {
|
|
1194
|
+
throw new Error(`${serverKey}:${email} is not an api-key identity`);
|
|
1196
1195
|
}
|
|
1196
|
+
return exchangeKeyedSession(store, serverKey, email, stored, {
|
|
1197
|
+
buffer: options?.buffer ?? 0,
|
|
1198
|
+
force: false,
|
|
1199
|
+
signal: options?.signal,
|
|
1200
|
+
host: options?.host
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
async function ensureJwt(store, serverKey, email, opts) {
|
|
1204
|
+
const stored = await store.getSession(serverKey, email);
|
|
1205
|
+
if (stored === void 0) {
|
|
1206
|
+
throw new NotLoggedInError(`not logged in: ${serverKey}:${email}`);
|
|
1207
|
+
}
|
|
1208
|
+
if (usesApiKey(stored)) {
|
|
1209
|
+
return exchangeKeyedSession(store, serverKey, email, stored, opts);
|
|
1210
|
+
}
|
|
1211
|
+
const session = stored;
|
|
1212
|
+
if (!opts.force && session.jwtAccess !== void 0 && session.jwtAccess !== "" && !jwtExpiringSoon(session, opts.buffer)) {
|
|
1213
|
+
return session;
|
|
1214
|
+
}
|
|
1215
|
+
const host = opts.host ?? resolve(serverKey).host;
|
|
1197
1216
|
if (session.impersonatorEmail !== void 0 && session.impersonatorEmail !== "") {
|
|
1198
1217
|
return recoverImpersonatedJwt(store, serverKey, email, session, host, opts);
|
|
1199
1218
|
}
|
|
@@ -2101,7 +2120,8 @@ var AuthorizedClient = class _AuthorizedClient {
|
|
|
2101
2120
|
* How this identity gets a usable JWT again. `dr login` is refused over an
|
|
2102
2121
|
* api-key credential, and it re-authenticates the operator rather than the
|
|
2103
2122
|
* impersonated user, so naming it for either sends the reader at a command
|
|
2104
|
-
* that cannot help — the same loop `runWhoami`
|
|
2123
|
+
* that cannot help — the same loop `runWhoami` avoids by never naming
|
|
2124
|
+
* `dr login` for either.
|
|
2105
2125
|
*
|
|
2106
2126
|
* `cause` splits the keyed answer, which is the only one that differs: a
|
|
2107
2127
|
* token that never arrived is not a token whose `organization_id` is wrong,
|
|
@@ -4807,7 +4827,7 @@ function declaredWithValueBelow(flag, cmd) {
|
|
|
4807
4827
|
// src/commands/whoami.ts
|
|
4808
4828
|
function registerWhoamiCommand(root) {
|
|
4809
4829
|
const cmd = root.command("whoami").description(
|
|
4810
|
-
"whoami decodes the locally-stored JWT for a server and prints the identity claims (user_id, username, email, expiration).
|
|
4830
|
+
"whoami decodes the locally-stored JWT for a server and prints the identity claims (user_id, username, email, expiration). Offline, except that an api-key identity holding no token its current key minted is exchanged once to obtain one."
|
|
4811
4831
|
).summary("Show the identity encoded in the stored JWT for a server").option("--json", "Output the full claims map as JSON", false).option("--verbose", "Print every claim in a table, sorted alphabetically", false).action(
|
|
4812
4832
|
async (opts) => {
|
|
4813
4833
|
const fileStore = await resolveFileStore(opts.server);
|
|
@@ -4845,19 +4865,34 @@ async function runWhoami(cfg) {
|
|
|
4845
4865
|
}
|
|
4846
4866
|
emitApiKeyExpiryWarning(session, `${cfg.serverKey}:${email}`);
|
|
4847
4867
|
const foreignJwt = usesApiKey(session) && session.jwtAccess !== void 0 && session.jwtAccess !== "" && !mintedByStoredApiKey(session);
|
|
4868
|
+
let active = session;
|
|
4848
4869
|
if (session.jwtAccess === void 0 || session.jwtAccess === "" || foreignJwt) {
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4870
|
+
if (usesApiKey(session)) {
|
|
4871
|
+
try {
|
|
4872
|
+
active = await ensureApiKeyJwt(cfg.store, cfg.serverKey, email, { session });
|
|
4873
|
+
} catch (err) {
|
|
4874
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
4875
|
+
throw new Error(`cannot show identity for ${cfg.serverKey}:${email}: ${reason}`, {
|
|
4876
|
+
cause: err
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4879
|
+
} else {
|
|
4880
|
+
const missing = `no JWT stored for ${cfg.serverKey}:${email}`;
|
|
4881
|
+
throw new Error(
|
|
4882
|
+
isImpersonatedSession(session) ? `${missing}; re-run dr login-as` : `${missing}; run dr login --server ${cfg.serverKey} to refresh`
|
|
4883
|
+
);
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
if (active.jwtAccess === void 0 || active.jwtAccess === "") {
|
|
4887
|
+
throw new Error(`no JWT stored for ${cfg.serverKey}:${email}`);
|
|
4853
4888
|
}
|
|
4854
4889
|
try {
|
|
4855
|
-
setTelemetryTarget(cfg.serverKey, deriveIdentity(
|
|
4890
|
+
setTelemetryTarget(cfg.serverKey, deriveIdentity(active));
|
|
4856
4891
|
} catch {
|
|
4857
4892
|
}
|
|
4858
4893
|
let claims;
|
|
4859
4894
|
try {
|
|
4860
|
-
claims = decodeJwtClaims(
|
|
4895
|
+
claims = decodeJwtClaims(active.jwtAccess);
|
|
4861
4896
|
} catch (err) {
|
|
4862
4897
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4863
4898
|
throw new Error(`decode JWT for ${cfg.serverKey}: ${msg}`);
|
|
@@ -4867,7 +4902,7 @@ async function runWhoami(cfg) {
|
|
|
4867
4902
|
host = resolve(cfg.serverKey).host;
|
|
4868
4903
|
} catch {
|
|
4869
4904
|
}
|
|
4870
|
-
const impersonatorEmail =
|
|
4905
|
+
const impersonatorEmail = active.impersonatorEmail !== void 0 && active.impersonatorEmail !== "" ? active.impersonatorEmail : void 0;
|
|
4871
4906
|
if (cfg.asJson) {
|
|
4872
4907
|
writeWhoamiJSON(cfg.out, cfg.serverKey, host, claims, impersonatorEmail);
|
|
4873
4908
|
return;
|
|
@@ -5657,6 +5692,10 @@ async function runApiKeyLogin(cfg) {
|
|
|
5657
5692
|
apiKey: cfg.apiKey,
|
|
5658
5693
|
jwtAccess: tok.access,
|
|
5659
5694
|
jwtExpiresAt: tok.expiresAt,
|
|
5695
|
+
// Records which key minted this token, as every exchange does. Without it
|
|
5696
|
+
// `mintedByStoredApiKey` reads the token as unknown provenance, so `whoami`
|
|
5697
|
+
// and the next command would exchange again instead of using it.
|
|
5698
|
+
jwtApiKeyFingerprint: apiKeyFingerprint(cfg.apiKey),
|
|
5660
5699
|
savedAt: /* @__PURE__ */ new Date(),
|
|
5661
5700
|
cookieless: true
|
|
5662
5701
|
};
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.105",
|
|
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": {
|
|
@@ -66,16 +66,21 @@ per server can be logged in at once.
|
|
|
66
66
|
required unless a credential file already names the server; don't run the
|
|
67
67
|
piped form under shell tracing or command logging). Never pass the key as an
|
|
68
68
|
argument or paste it into chat. `dr` exchanges it for a JWT itself, so no
|
|
69
|
-
re-login is ever needed while the key is live
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
re-login is ever needed while the key is live — and `login --api-key` does that
|
|
70
|
+
exchange **at login** and stores the JWT, so `whoami` and the next command work
|
|
71
|
+
right away. Keys come from an operator today (no self-service command yet).
|
|
72
|
+
`login-as` cannot run from an API-key identity.
|
|
72
73
|
- `dr login-as <email> --server US` — impersonate a user, **JWT only, no
|
|
73
74
|
browser**. Needs an operator `login` first; stores a switchable identity you
|
|
74
75
|
then target with `--account <email>`. `--as <operator-email>` picks the
|
|
75
76
|
operator if several are stored; `--user-id <n>` skips the email lookup.
|
|
76
77
|
Refreshes/auto-remints silently — no re-prompt until the operator expires.
|
|
77
78
|
- `dr whoami --server US` — confirm who/where you are. Offline (decodes the local
|
|
78
|
-
JWT, no network)
|
|
79
|
+
JWT, no network) — **except** an API-key credential holding no token its current
|
|
80
|
+
key minted (a key written straight into a session file — e.g. a managed-agent
|
|
81
|
+
session — or a key rotated since its token was stored), which `whoami` exchanges
|
|
82
|
+
once to obtain one and saves, so it works even as the very first command.
|
|
83
|
+
**Always sanity-check after login or a server switch.**
|
|
79
84
|
Shows `Impersonated by:` for a `login-as` identity, and a `Scope:` row for
|
|
80
85
|
every identity (see below).
|
|
81
86
|
- `dr logout --server US` / `dr logout --all`.
|
|
@@ -92,7 +92,8 @@ Run a single Bash call:
|
|
|
92
92
|
dr whoami --server <X> --account <email> 2>&1 || true
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`whoami` is
|
|
95
|
+
`whoami` is offline (decodes the locally-stored JWT, no network), except that an
|
|
96
|
+
API-key identity holding no token its current key minted is exchanged once.
|
|
96
97
|
Classify the output:
|
|
97
98
|
|
|
98
99
|
| Result | Meaning |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datarails-ai/dr-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.105",
|
|
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.11.
|
|
20
|
+
"@datarails-ai/dr-appspace-cli": "0.11.2",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
22
|
"@napi-rs/keyring": "^1.3.0",
|
|
23
23
|
"commander": "^12.1.0",
|