dsh-grok-subscription 2.0.3 → 2.0.4

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/lib/index.js CHANGED
@@ -16,6 +16,8 @@ var CLIENT_VERSION_HEADER = "x-grok-client-version";
16
16
  var CLIENT_IDENTIFIER = "grok-shell";
17
17
  var CLIENT_VERSION_FALLBACK = "1.0.5";
18
18
  var API_KEY_SCOPE = "xai::api_key";
19
+ var XAI_OAUTH_ISSUER = "https://auth.x.ai";
20
+ var GROK_OIDC_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
19
21
  var SESSION_AUTH_MODES = Object.freeze(["oidc", "external", "web_login", "grok"]);
20
22
  var API_KEY_AUTH_MODES = Object.freeze(["api_key"]);
21
23
  var AUTH_FILE_MAX_BYTES = 1048576;
@@ -37,19 +39,28 @@ var IMAGE_INPUT_MODEL_IDS = Object.freeze([
37
39
  var IMPORT_TIMEOUT_MS = 2500;
38
40
  var CREDENTIALS_IO_TIMEOUT_MS = 1500;
39
41
  var RPC_HANDLER_TIMEOUT_MS = 8e3;
40
- var LOGIN_START_TIMEOUT_MS = 1e4;
41
42
  var LOGIN_HANDLER_TIMEOUT_MS = 2e4;
42
43
 
43
44
  // src/session.js
44
- import { existsSync as existsSync3 } from "node:fs";
45
45
  import { spawn } from "node:child_process";
46
- import { join as join3 } from "node:path";
47
46
 
48
47
  // src/auth-file.js
49
- import { lstatSync, openSync, readFileSync, closeSync, constants as fsConstants } from "node:fs";
48
+ import { randomBytes } from "node:crypto";
49
+ import {
50
+ chmodSync,
51
+ closeSync,
52
+ constants as fsConstants,
53
+ lstatSync,
54
+ mkdirSync,
55
+ openSync,
56
+ readFileSync,
57
+ renameSync,
58
+ unlinkSync,
59
+ writeSync
60
+ } from "node:fs";
50
61
  import { homedir } from "node:os";
51
- import { join } from "node:path";
52
- var XAI_OAUTH_ISSUER = "https://auth.x.ai";
62
+ import { dirname, join } from "node:path";
63
+ var XAI_OAUTH_ISSUER2 = "https://auth.x.ai";
53
64
  function grokHome(env = process.env) {
54
65
  const override = typeof env.GROK_HOME === "string" && env.GROK_HOME.trim() ? env.GROK_HOME.trim() : void 0;
55
66
  return override ?? join(homedir(), ".grok");
@@ -165,6 +176,9 @@ function sessionFromEntry(scope, entry) {
165
176
  return Object.freeze({
166
177
  scope,
167
178
  accessToken: token,
179
+ refreshToken: asNonEmptyString(object.refresh_token ?? object.refreshToken),
180
+ oidcIssuer: asNonEmptyString(object.oidc_issuer ?? object.oidcIssuer),
181
+ oidcClientId: asNonEmptyString(object.oidc_client_id ?? object.oidcClientId),
168
182
  authMode,
169
183
  email,
170
184
  userId,
@@ -174,8 +188,8 @@ function sessionFromEntry(scope, entry) {
174
188
  }
175
189
  function preferredScopeScore(scope, entry) {
176
190
  const issuer = entry && typeof entry === "object" ? asNonEmptyString(entry.oidc_issuer ?? entry.oidcIssuer) : void 0;
177
- if (typeof scope === "string" && scope.startsWith(`${XAI_OAUTH_ISSUER}::`)) return 3;
178
- if (issuer === XAI_OAUTH_ISSUER) return 2;
191
+ if (typeof scope === "string" && scope.startsWith(`${XAI_OAUTH_ISSUER2}::`)) return 3;
192
+ if (issuer === XAI_OAUTH_ISSUER2) return 2;
179
193
  if (SESSION_AUTH_MODES.includes(normalizeAuthMode(entry?.auth_mode ?? entry?.authMode) ?? "")) return 1;
180
194
  return 0;
181
195
  }
@@ -213,6 +227,91 @@ function readGrokAuthSession(path = authJsonPath(), options = {}) {
213
227
  const document = readSecureJsonFile(path, options);
214
228
  return parseAuthDocument(document);
215
229
  }
230
+ function fileMeta(stat) {
231
+ return {
232
+ isSymbolicLink: typeof stat.isSymbolicLink === "function" ? stat.isSymbolicLink() : Boolean(stat.isSymbolicLink),
233
+ isFile: typeof stat.isFile === "function" ? stat.isFile() : Boolean(stat.isFile),
234
+ mode: stat.mode,
235
+ uid: stat.uid,
236
+ size: stat.size
237
+ };
238
+ }
239
+ function writeOauthSession(path, fields, options = {}) {
240
+ if (!asNonEmptyString(fields?.accessToken) || !asNonEmptyString(fields?.issuer) || !asNonEmptyString(fields?.clientId)) {
241
+ throw new Error("Refusing to store an OAuth session without an access token");
242
+ }
243
+ const lstat = options.lstat ?? ((target) => lstatSync(target, { throwIfNoEntry: false }));
244
+ const existing = lstat(path);
245
+ let document = {};
246
+ if (existing) {
247
+ const meta = fileMeta(existing);
248
+ if (meta.isSymbolicLink) throw new Error("Refusing to write a symbolic-link Grok auth.json");
249
+ if (!meta.isFile) throw new Error("Grok auth.json is not a regular file");
250
+ const platform = options.platform ?? process.platform;
251
+ const currentUid = options.uid ?? process.getuid?.();
252
+ if (platform !== "win32" && typeof currentUid === "number" && typeof meta.uid === "number" && meta.uid !== currentUid) {
253
+ throw new Error("Grok auth.json is not owned by the current user");
254
+ }
255
+ if (typeof meta.size === "number" && meta.size > AUTH_FILE_MAX_BYTES) {
256
+ throw new Error("Grok auth.json is larger than the allowed size");
257
+ }
258
+ document = readSecureJsonFile(path, options);
259
+ if (!document || typeof document !== "object" || Array.isArray(document)) {
260
+ throw new Error("Grok auth document is not an object");
261
+ }
262
+ }
263
+ const scope = `${fields.issuer}::${fields.clientId}`;
264
+ const previous = document[scope] && typeof document[scope] === "object" && !Array.isArray(document[scope]) ? document[scope] : {};
265
+ const next = { ...previous, key: fields.accessToken, auth_mode: "oidc", oidc_issuer: fields.issuer, oidc_client_id: fields.clientId };
266
+ if (asNonEmptyString(fields.expiresAt)) next.expires_at = fields.expiresAt;
267
+ if (asNonEmptyString(fields.refreshToken)) next.refresh_token = fields.refreshToken;
268
+ if (asNonEmptyString(fields.email)) next.email = fields.email;
269
+ if (asNonEmptyString(fields.userId)) next.user_id = fields.userId;
270
+ document[scope] = next;
271
+ writeSecureJsonFile(path, document, options);
272
+ return parseAuthDocument(document);
273
+ }
274
+ function writeSecureJsonFile(path, document, options = {}) {
275
+ const mkdir = options.mkdir ?? ((dir) => mkdirSync(dir, { recursive: true, mode: 448 }));
276
+ const open = options.open ?? ((target) => openSync(target, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384));
277
+ const write = options.write ?? ((fd2, payload2) => writeSync(fd2, payload2));
278
+ const close = options.close ?? closeSync;
279
+ const rename = options.rename ?? renameSync;
280
+ const unlink = options.unlink ?? unlinkSync;
281
+ const chmod = options.chmod ?? chmodSync;
282
+ mkdir(dirname(path));
283
+ const tmp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
284
+ const payload = `${JSON.stringify(document, null, 2)}
285
+ `;
286
+ const fd = open(tmp);
287
+ try {
288
+ write(fd, payload);
289
+ } catch (error) {
290
+ try {
291
+ close(fd);
292
+ } catch {
293
+ }
294
+ try {
295
+ unlink(tmp);
296
+ } catch {
297
+ }
298
+ throw error;
299
+ }
300
+ close(fd);
301
+ try {
302
+ rename(tmp, path);
303
+ } catch (error) {
304
+ try {
305
+ unlink(tmp);
306
+ } catch {
307
+ }
308
+ throw error;
309
+ }
310
+ try {
311
+ chmod(path, 384);
312
+ } catch {
313
+ }
314
+ }
216
315
  function publicSessionView(session) {
217
316
  if (!session) {
218
317
  return Object.freeze({
@@ -829,7 +928,7 @@ async function fetchBillingUsage(accessToken, options = {}) {
829
928
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
830
929
  import { createRequire } from "node:module";
831
930
  import { homedir as homedir2 } from "node:os";
832
- import { dirname, join as join2 } from "node:path";
931
+ import { dirname as dirname2, join as join2 } from "node:path";
833
932
  import { pathToFileURL } from "node:url";
834
933
  function withImportTimeout(promise, ms, message) {
835
934
  let timer;
@@ -850,16 +949,16 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
850
949
  const override = env.DSH_GROK_PEER_ROOT;
851
950
  if (typeof override === "string" && override.trim()) push(override.trim());
852
951
  if (typeof argv1 === "string" && argv1) {
853
- let dir = dirname(argv1);
854
- for (let depth = 0; depth < 6 && dir && dir !== dirname(dir); depth++) {
952
+ let dir = dirname2(argv1);
953
+ for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
855
954
  push(join2(dir, "node_modules"));
856
- dir = dirname(dir);
955
+ dir = dirname2(dir);
857
956
  }
858
957
  }
859
958
  const prefix = env.NPM_CONFIG_PREFIX;
860
959
  if (typeof prefix === "string" && prefix.trim()) push(join2(prefix.trim(), "lib", "node_modules"));
861
960
  if (typeof process.execPath === "string" && process.execPath) {
862
- push(join2(dirname(process.execPath), "..", "lib", "node_modules"));
961
+ push(join2(dirname2(process.execPath), "..", "lib", "node_modules"));
863
962
  }
864
963
  push(join2("/opt/homebrew", "lib", "node_modules"));
865
964
  push(join2("/usr/local", "lib", "node_modules"));
@@ -1149,40 +1248,209 @@ async function createGrokBuildAdapter(session, options = {}) {
1149
1248
  };
1150
1249
  }
1151
1250
 
1152
- // src/session.js
1153
- var WINDOWS_EXEC_EXTENSIONS = [".COM", ".EXE", ".BAT", ".CMD"];
1154
- var hasPathSeparator = (value) => value.includes("/") || value.includes("\\");
1155
- function executableNames(bin, platform, env) {
1156
- if (platform !== "win32") return [bin];
1157
- const declared = String(env.PATHEXT ?? "").split(";").map((part) => part.trim()).filter(Boolean).map((part) => part.startsWith(".") ? part : `.${part}`);
1158
- const extensions = declared.length > 0 ? declared : WINDOWS_EXEC_EXTENSIONS;
1159
- return [bin, ...extensions.map((extension) => `${bin}${extension}`)];
1160
- }
1161
- function firstExisting(names, exists) {
1162
- for (const name2 of names) if (exists(name2)) return name2;
1251
+ // src/oauth.js
1252
+ var OAUTH_SCOPES = "openid profile email offline_access api:access grok-cli:access";
1253
+ var OAUTH_BODY_LIMIT = 64 * 1024;
1254
+ var OAUTH_ERROR_LIMIT = 180;
1255
+ function collapse(value) {
1256
+ return String(value ?? "").replace(/\s+/gu, " ").trim().slice(0, OAUTH_ERROR_LIMIT);
1257
+ }
1258
+ function oauthError(body, status) {
1259
+ const code = collapse(body?.error) || "request_failed";
1260
+ const description = collapse(body?.error_description);
1261
+ if (description && description !== code) return `${code}: ${description} (HTTP ${status})`;
1262
+ return `${code} (HTTP ${status})`;
1263
+ }
1264
+ async function readOauthBody(response) {
1265
+ const status = typeof response.status === "number" ? response.status : 0;
1266
+ const declared = Number(response.headers?.get?.("content-length"));
1267
+ if (Number.isFinite(declared) && declared > OAUTH_BODY_LIMIT) {
1268
+ throw new Error(`OAuth response exceeded ${OAUTH_BODY_LIMIT} bytes (HTTP ${status})`);
1269
+ }
1270
+ let text;
1271
+ try {
1272
+ text = await readResponseText(response);
1273
+ } catch (error) {
1274
+ throw new Error(`OAuth response could not be read (HTTP ${status})`, { cause: error });
1275
+ }
1276
+ if (typeof text !== "string" || text.length > OAUTH_BODY_LIMIT) {
1277
+ throw new Error(`OAuth response exceeded ${OAUTH_BODY_LIMIT} bytes (HTTP ${status})`);
1278
+ }
1279
+ try {
1280
+ return JSON.parse(text);
1281
+ } catch {
1282
+ throw new Error(`OAuth endpoint returned HTTP ${status} with a non-JSON body`);
1283
+ }
1284
+ }
1285
+ function timeoutSignal(ms) {
1286
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
1287
+ return AbortSignal.timeout(ms);
1288
+ }
1163
1289
  return void 0;
1164
1290
  }
1165
- function pathEntry(dir, platform) {
1166
- return platform === "win32" && dir.length > 1 && dir.startsWith('"') && dir.endsWith('"') ? dir.slice(1, -1) : dir;
1291
+ async function postForm(url, params, fetchImpl) {
1292
+ const response = await fetchImpl(url, {
1293
+ method: "POST",
1294
+ headers: {
1295
+ accept: "application/json",
1296
+ "content-type": "application/x-www-form-urlencoded"
1297
+ },
1298
+ body: new URLSearchParams(params).toString(),
1299
+ signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
1300
+ });
1301
+ const body = await readOauthBody(response);
1302
+ return { status: typeof response.status === "number" ? response.status : 0, body };
1303
+ }
1304
+ function isXaiHttpsUrl(value) {
1305
+ try {
1306
+ const url = new URL(value);
1307
+ const host = url.hostname.toLowerCase();
1308
+ return url.protocol === "https:" && !url.username && !url.password && (host === "x.ai" || host.endsWith(".x.ai"));
1309
+ } catch {
1310
+ return false;
1311
+ }
1312
+ }
1313
+ function endpointOnIssuer(value, issuer, field) {
1314
+ let url;
1315
+ let issuerUrl;
1316
+ try {
1317
+ url = new URL(value);
1318
+ issuerUrl = new URL(issuer);
1319
+ } catch {
1320
+ throw new Error(`OIDC discovery returned an invalid ${field}`);
1321
+ }
1322
+ if (url.protocol !== "https:" || issuerUrl.protocol !== "https:") {
1323
+ throw new Error(`OIDC discovery returned a non-HTTPS ${field}`);
1324
+ }
1325
+ if (url.username || url.password) throw new Error(`OIDC discovery returned credentials in ${field}`);
1326
+ if (url.origin !== issuerUrl.origin) throw new Error(`OIDC discovery ${field} does not match the issuer`);
1327
+ return url.toString();
1328
+ }
1329
+ function fallbackOauthEndpoints(issuer = XAI_OAUTH_ISSUER) {
1330
+ const base = String(issuer).replace(/\/+$/u, "");
1331
+ return {
1332
+ deviceAuthorizationEndpoint: `${base}/oauth2/device/code`,
1333
+ tokenEndpoint: `${base}/oauth2/token`
1334
+ };
1335
+ }
1336
+ async function discoverOauthEndpoints(issuer = XAI_OAUTH_ISSUER, options = {}) {
1337
+ const fetchImpl = options.fetch ?? fetch;
1338
+ const base = String(issuer).replace(/\/+$/u, "");
1339
+ try {
1340
+ const response = await fetchImpl(`${base}/.well-known/openid-configuration`, {
1341
+ headers: { accept: "application/json" },
1342
+ signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
1343
+ });
1344
+ const body = await readOauthBody(response);
1345
+ const status = typeof response.status === "number" ? response.status : 0;
1346
+ if (status < 200 || status >= 300) throw new Error(`OIDC discovery failed (HTTP ${status})`);
1347
+ const discovered = typeof body?.issuer === "string" ? body.issuer.replace(/\/+$/u, "") : "";
1348
+ if (discovered !== base) throw new Error("OIDC discovery issuer does not match the requested issuer");
1349
+ return {
1350
+ deviceAuthorizationEndpoint: endpointOnIssuer(body.device_authorization_endpoint, base, "device_authorization_endpoint"),
1351
+ tokenEndpoint: endpointOnIssuer(body.token_endpoint, base, "token_endpoint")
1352
+ };
1353
+ } catch {
1354
+ return fallbackOauthEndpoints(base);
1355
+ }
1356
+ }
1357
+ function clockMs(now) {
1358
+ if (typeof now === "function") return now();
1359
+ if (typeof now === "number" && Number.isFinite(now)) return now;
1360
+ return Date.now();
1167
1361
  }
1168
- function resolveGrokBin(env = process.env, exists = existsSync3, platform = process.platform) {
1169
- const override = typeof env.DSH_GROK_BIN === "string" && env.DSH_GROK_BIN.trim() ? env.DSH_GROK_BIN.trim() : void 0;
1170
- if (override !== void 0) {
1171
- if (!hasPathSeparator(override)) return override;
1172
- return firstExisting(executableNames(override, platform, env), exists) ?? override;
1362
+ function expiresAtFrom(body, accessToken, nowMs) {
1363
+ if (typeof body?.expires_in === "number" && Number.isFinite(body.expires_in) && body.expires_in > 0) {
1364
+ return new Date(nowMs + body.expires_in * 1e3).toISOString();
1173
1365
  }
1174
- const local = join3(grokHome(env), "bin", "grok");
1175
- const installed = firstExisting(executableNames(local, platform, env), exists);
1176
- if (installed !== void 0) return installed;
1177
- return "grok";
1366
+ const exp = jwtExpiryMs(accessToken);
1367
+ return typeof exp === "number" ? new Date(exp).toISOString() : void 0;
1178
1368
  }
1179
- function grokCliAvailable(env = process.env, exists = existsSync3, platform = process.platform) {
1180
- const bin = resolveGrokBin(env, exists, platform);
1181
- if (hasPathSeparator(bin)) return exists(bin);
1182
- const delimiter = platform === "win32" ? ";" : ":";
1183
- const names = executableNames(bin, platform, env);
1184
- return String(env.PATH ?? "").split(delimiter).some((dir) => dir && names.some((name2) => exists(join3(pathEntry(dir, platform), name2))));
1369
+ function jwtExpiryMs(token) {
1370
+ const parts = typeof token === "string" ? token.split(".") : [];
1371
+ if (parts.length < 2) return void 0;
1372
+ try {
1373
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
1374
+ if (typeof payload?.exp === "number" && Number.isFinite(payload.exp)) return payload.exp * 1e3;
1375
+ } catch {
1376
+ return void 0;
1377
+ }
1378
+ return void 0;
1185
1379
  }
1380
+ async function requestDeviceAuthorization(options = {}) {
1381
+ const fetchImpl = options.fetch ?? fetch;
1382
+ const clientId = options.clientId ?? GROK_OIDC_CLIENT_ID;
1383
+ const scopes = options.scopes ?? OAUTH_SCOPES;
1384
+ const endpoint = options.endpoint;
1385
+ if (!isXaiHttpsUrl(endpoint)) throw new Error("Refusing to request a device code from a non-x.ai endpoint");
1386
+ const { status, body } = await postForm(endpoint, {
1387
+ client_id: clientId,
1388
+ scope: scopes
1389
+ }, fetchImpl);
1390
+ if (body?.error || status < 200 || status >= 300) {
1391
+ throw new Error(`Could not start device sign-in: ${oauthError(body, status)}`);
1392
+ }
1393
+ const deviceCode = typeof body?.device_code === "string" ? body.device_code : "";
1394
+ const userCode = typeof body?.user_code === "string" ? body.user_code : "";
1395
+ const verificationUri = typeof body?.verification_uri === "string" ? body.verification_uri : void 0;
1396
+ const verificationUriComplete = typeof body?.verification_uri_complete === "string" ? body.verification_uri_complete : void 0;
1397
+ if (!deviceCode || !userCode) throw new Error("Device sign-in returned no user code");
1398
+ return {
1399
+ deviceCode,
1400
+ userCode,
1401
+ verificationUri,
1402
+ verificationUriComplete,
1403
+ expiresIn: typeof body?.expires_in === "number" ? body.expires_in : void 0,
1404
+ interval: typeof body?.interval === "number" ? body.interval : void 0
1405
+ };
1406
+ }
1407
+ async function exchangeDeviceCode(options = {}) {
1408
+ const fetchImpl = options.fetch ?? fetch;
1409
+ const clientId = options.clientId ?? GROK_OIDC_CLIENT_ID;
1410
+ const endpoint = options.endpoint;
1411
+ if (!isXaiHttpsUrl(endpoint)) return { kind: "stop", error: "refusing a non-x.ai token endpoint" };
1412
+ const { status, body } = await postForm(endpoint, {
1413
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1414
+ client_id: clientId,
1415
+ device_code: options.deviceCode
1416
+ }, fetchImpl);
1417
+ return classifyTokenResponse(body, status, clockMs(options.now));
1418
+ }
1419
+ async function refreshOauthToken(options = {}) {
1420
+ const fetchImpl = options.fetch ?? fetch;
1421
+ const endpoint = options.endpoint;
1422
+ if (!isXaiHttpsUrl(endpoint)) throw new Error("Refusing to refresh a token at a non-x.ai endpoint");
1423
+ const { status, body } = await postForm(endpoint, {
1424
+ grant_type: "refresh_token",
1425
+ client_id: options.clientId ?? GROK_OIDC_CLIENT_ID,
1426
+ refresh_token: options.refreshToken
1427
+ }, fetchImpl);
1428
+ const result = classifyTokenResponse(body, status, clockMs(options.now));
1429
+ if (result.kind !== "ok") throw new Error(`Could not refresh the session: ${result.error ?? "unknown"}`);
1430
+ return result;
1431
+ }
1432
+ function classifyTokenResponse(body, status, nowMs) {
1433
+ const error = typeof body?.error === "string" ? body.error : "";
1434
+ if (error === "authorization_pending") return { kind: "pending" };
1435
+ if (error === "slow_down") return { kind: "slow_down" };
1436
+ if (error) return { kind: "stop", error: oauthError(body, status) };
1437
+ const accessToken = typeof body?.access_token === "string" ? body.access_token : "";
1438
+ if (!accessToken || status < 200 || status >= 300) {
1439
+ return { kind: "stop", error: oauthError(body, status) };
1440
+ }
1441
+ const refreshToken = typeof body?.refresh_token === "string" && body.refresh_token ? body.refresh_token : void 0;
1442
+ return {
1443
+ kind: "ok",
1444
+ accessToken,
1445
+ refreshToken,
1446
+ expiresIn: typeof body?.expires_in === "number" ? body.expires_in : void 0,
1447
+ expiresAt: expiresAtFrom(body, accessToken, nowMs),
1448
+ issuer: XAI_OAUTH_ISSUER,
1449
+ clientId: GROK_OIDC_CLIENT_ID
1450
+ };
1451
+ }
1452
+
1453
+ // src/session.js
1186
1454
  function openExternal(url, options = {}) {
1187
1455
  const spawnFn = options.spawn ?? spawn;
1188
1456
  const platform = options.platform ?? process.platform;
@@ -1206,127 +1474,59 @@ function openExternal(url, options = {}) {
1206
1474
  child.unref?.();
1207
1475
  });
1208
1476
  }
1209
- var LOGIN_URL_PATTERN = /https?:\/\/[^\s<>"'`\\]+/u;
1210
- var DEVICE_CODE_PATTERN = /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/u;
1211
- async function spawnGrokLogin(options = {}) {
1212
- const spawnFn = options.spawn ?? spawn;
1213
- const bin = resolveGrokBin(options.env, options.exists);
1214
- const args = options.device ? ["login", "--device-auth"] : ["login"];
1215
- const startTimeoutMs = typeof options.startTimeoutMs === "number" && options.startTimeoutMs > 0 ? options.startTimeoutMs : LOGIN_START_TIMEOUT_MS;
1216
- const openUrl = options.openUrl ?? ((url) => openExternal(url, { spawn: options.openSpawn }));
1217
- return new Promise((resolve, reject) => {
1218
- let child;
1219
- try {
1220
- child = spawnFn(bin, args, {
1221
- stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
1222
- env: options.env ?? process.env
1223
- });
1224
- } catch (error) {
1225
- reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
1226
- return;
1227
- }
1228
- let settled = false;
1229
- let loginUrl;
1230
- let userCode;
1231
- let buffer = "";
1232
- let timer;
1233
- const succeed = (value) => {
1234
- if (settled) return;
1235
- settled = true;
1236
- if (timer) clearTimeout(timer);
1237
- resolve(value);
1238
- };
1239
- const scan = (text) => {
1240
- if (text) buffer = `${buffer}${text}`.slice(-8192);
1241
- if (!userCode) {
1242
- const code = DEVICE_CODE_PATTERN.exec(buffer);
1243
- if (code) userCode = code[1];
1244
- }
1245
- if (loginUrl) return;
1246
- const match = LOGIN_URL_PATTERN.exec(buffer);
1247
- if (!match) return;
1248
- loginUrl = match[0];
1249
- try {
1250
- void openUrl(loginUrl);
1251
- } catch {
1252
- }
1253
- succeed({ ok: true, pending: true, loginUrl, userCode, child });
1254
- };
1255
- const piped = Boolean(child.stdout && child.stderr);
1256
- if (piped) {
1257
- child.stdout.on("data", (chunk) => scan(String(chunk)));
1258
- child.stderr.on("data", (chunk) => scan(String(chunk)));
1259
- timer = setTimeout(() => {
1260
- succeed({
1261
- ok: true,
1262
- pending: true,
1263
- loginUrl,
1264
- userCode,
1265
- child,
1266
- warning: `grok login printed no sign-in URL within ${startTimeoutMs}ms`
1267
- });
1268
- }, startTimeoutMs);
1269
- }
1270
- child.on("error", (error) => {
1271
- if (settled) return;
1272
- settled = true;
1273
- if (timer) clearTimeout(timer);
1274
- reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
1275
- });
1276
- child.on("exit", (code, signal) => {
1277
- if (settled) return;
1278
- settled = true;
1279
- if (timer) clearTimeout(timer);
1280
- if (code === 0) resolve({ ok: true, pending: false, loginUrl, userCode, child });
1281
- else reject(new Error(signal ? `grok login terminated by ${signal}` : `grok login exited with code ${code ?? "unknown"}`));
1282
- });
1477
+ function defaultSleep(ms) {
1478
+ return new Promise((resolve) => {
1479
+ const timer = setTimeout(resolve, ms);
1480
+ if (typeof timer.unref === "function") timer.unref();
1283
1481
  });
1284
1482
  }
1285
- async function spawnGrokRefresh(options = {}) {
1286
- const spawnFn = options.spawn ?? spawn;
1287
- const bin = resolveGrokBin(options.env, options.exists);
1288
- const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : CLI_REFRESH_TIMEOUT_MS;
1289
- return new Promise((resolve, reject) => {
1290
- let child;
1291
- try {
1292
- child = spawnFn(bin, ["models"], {
1293
- stdio: options.stdio ?? "ignore",
1294
- env: options.env ?? process.env
1295
- });
1296
- } catch (error) {
1297
- reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
1298
- return;
1299
- }
1300
- let settled = false;
1301
- const timer = setTimeout(() => {
1302
- if (settled) return;
1303
- settled = true;
1304
- try {
1305
- child.kill("SIGKILL");
1306
- } catch {
1307
- }
1308
- resolve({ ok: false, reason: "timeout" });
1309
- }, timeoutMs);
1310
- child.on("error", (error) => {
1311
- if (settled) return;
1312
- settled = true;
1313
- clearTimeout(timer);
1314
- reject(new Error(`Could not run grok CLI (${bin})`, { cause: error }));
1315
- });
1316
- child.on("exit", (code) => {
1317
- if (settled) return;
1318
- settled = true;
1319
- clearTimeout(timer);
1320
- resolve({ ok: true, code });
1321
- });
1322
- });
1483
+ function issuerOf(session) {
1484
+ if (typeof session?.oidcIssuer === "string" && session.oidcIssuer.startsWith("https://")) return session.oidcIssuer;
1485
+ const scope = typeof session?.scope === "string" ? session.scope : "";
1486
+ const issuer = scope.split("::")[0];
1487
+ if (issuer?.startsWith("https://")) return issuer;
1488
+ return XAI_OAUTH_ISSUER;
1489
+ }
1490
+ function clientIdOf(session) {
1491
+ if (typeof session?.oidcClientId === "string" && session.oidcClientId) return session.oidcClientId;
1492
+ const scope = typeof session?.scope === "string" ? session.scope : "";
1493
+ const clientId = scope.split("::")[1];
1494
+ return clientId || GROK_OIDC_CLIENT_ID;
1323
1495
  }
1324
1496
  async function defaultRenewSession(options = {}) {
1325
1497
  const env = options.env ?? process.env;
1326
- if (!grokCliAvailable(env)) return void 0;
1327
- await spawnGrokRefresh({ env });
1328
- const parsed = (options.readAuth ?? readGrokAuthSession)();
1329
- return parsed?.session;
1498
+ const path = options.authPath ?? authJsonPath(env);
1499
+ const read = options.readAuth ?? (() => readGrokAuthSession(path));
1500
+ let parsed;
1501
+ try {
1502
+ parsed = read();
1503
+ } catch {
1504
+ return void 0;
1505
+ }
1506
+ const current = parsed?.session;
1507
+ if (!current?.refreshToken) return void 0;
1508
+ const issuer = issuerOf(current);
1509
+ const clientId = clientIdOf(current);
1510
+ const fetchImpl = options.fetch ?? fetch;
1511
+ const endpoints = options.endpoints ?? await discoverOauthEndpoints(issuer, { fetch: fetchImpl });
1512
+ const nowMs = typeof options.now === "function" ? options.now() : Date.now();
1513
+ const tokens = await refreshOauthToken({
1514
+ endpoint: endpoints.tokenEndpoint,
1515
+ clientId,
1516
+ refreshToken: current.refreshToken,
1517
+ fetch: fetchImpl,
1518
+ now: () => nowMs
1519
+ });
1520
+ const written = writeOauthSession(path, {
1521
+ issuer,
1522
+ clientId,
1523
+ accessToken: tokens.accessToken,
1524
+ refreshToken: tokens.refreshToken || current.refreshToken,
1525
+ expiresAt: tokens.expiresAt ?? current.expiresAt,
1526
+ email: current.email,
1527
+ userId: current.userId
1528
+ });
1529
+ return written.session;
1330
1530
  }
1331
1531
  async function defaultCredentialRefOf() {
1332
1532
  try {
@@ -1363,9 +1563,21 @@ function createSessionService({
1363
1563
  storeTokenTimeoutMs = STORE_TOKEN_TIMEOUT_MS,
1364
1564
  credentialsIoTimeoutMs = CREDENTIALS_IO_TIMEOUT_MS,
1365
1565
  credentialRefOf = defaultCredentialRefOf,
1366
- renewSession = defaultRenewSession,
1566
+ renewSession,
1567
+ fetch: fetchImpl,
1568
+ authPath,
1569
+ env,
1570
+ oauthEndpoints,
1367
1571
  now = () => Date.now()
1368
1572
  } = {}) {
1573
+ const renew = renewSession ?? (() => defaultRenewSession({
1574
+ readAuth,
1575
+ fetch: fetchImpl,
1576
+ authPath,
1577
+ env,
1578
+ endpoints: oauthEndpoints,
1579
+ now
1580
+ }));
1369
1581
  let catalog = { models: [], source: "signed-out", error: void 0 };
1370
1582
  let lastPublic = publicSessionView(void 0);
1371
1583
  let lastUsage = unavailable("Not fetched yet");
@@ -1404,7 +1616,7 @@ function createSessionService({
1404
1616
  if (renewalInFlight) return renewalInFlight;
1405
1617
  renewalInFlight = (async () => {
1406
1618
  try {
1407
- const session = await renewSession();
1619
+ const session = await renew();
1408
1620
  const token = adoptSession(session);
1409
1621
  if (token) notifyCatalogChange();
1410
1622
  return token;
@@ -1544,7 +1756,7 @@ function createSessionService({
1544
1756
  );
1545
1757
  });
1546
1758
  });
1547
- const message = parsed.reason === "api-key-only" ? "Found an API-key entry only. Sign in with SuperGrok / X Premium via grok login." : "No Grok Build subscription session in auth.json. Run grok login first.";
1759
+ const message = parsed.reason === "api-key-only" ? "Found an API-key entry only. Sign in with a SuperGrok / X Premium account." : "No Grok Build subscription session in auth.json. Sign in from Settings first.";
1548
1760
  notifyCatalogChange();
1549
1761
  return { ok: false, error: message, account: lastPublic, catalog, usage: lastUsage };
1550
1762
  }
@@ -1566,15 +1778,16 @@ function createSessionService({
1566
1778
  kickBackgroundUsageRefresh();
1567
1779
  return { ok: true, account: lastPublic, catalog, usage: lastUsage };
1568
1780
  };
1781
+ const snapshot = () => ({ account: lastPublic, catalog, usage: lastUsage, authPath: authPath ?? authJsonPath(env) });
1569
1782
  const status = async () => {
1570
1783
  if (typeof memoryAccessToken === "string" && memoryAccessToken.length > 0 || lastPublic.signedIn === true) {
1571
- return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1784
+ return snapshot();
1572
1785
  }
1573
1786
  try {
1574
1787
  const parsed = readAuth();
1575
1788
  if (parsed.session) {
1576
1789
  adoptSession(parsed.session);
1577
- return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1790
+ return snapshot();
1578
1791
  }
1579
1792
  } catch (error) {
1580
1793
  logger?.warn?.(
@@ -1593,36 +1806,95 @@ function createSessionService({
1593
1806
  lastPublic = publicSessionView(void 0);
1594
1807
  catalog = { models: [], source: "signed-out", error: void 0 };
1595
1808
  lastUsage = unavailable("Not signed in");
1596
- return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1809
+ return snapshot();
1597
1810
  }
1598
1811
  memoryAccessToken = token;
1599
1812
  lastPublic = Object.freeze({ signedIn: true, maskedAccount: "\u2022\u2022\u2022\u2022", authMode: "oidc", source: "dsh-credentials" });
1600
- return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1813
+ return snapshot();
1601
1814
  };
1602
- const login = async (options) => {
1603
- const started = await spawnGrokLogin(options);
1604
- const extra = {
1605
- ...started.loginUrl === void 0 ? {} : { loginUrl: started.loginUrl },
1606
- ...started.userCode === void 0 ? {} : { userCode: started.userCode },
1607
- ...started.warning === void 0 ? {} : { warning: started.warning }
1608
- };
1609
- if (started.pending) {
1610
- started.child?.once?.("exit", (code) => {
1611
- if (code !== 0) return;
1612
- scheduleDeferred(() => {
1613
- void pull().catch((error) => {
1614
- logger?.warn?.(
1615
- "Grok subscription login sync failed: %s",
1616
- error instanceof Error ? error.message : "unknown"
1617
- );
1618
- });
1619
- });
1815
+ let loginEpoch = 0;
1816
+ const login = async (loginOptions) => {
1817
+ const epoch = ++loginEpoch;
1818
+ const fetchLogin = loginOptions?.fetch ?? fetchImpl ?? fetch;
1819
+ const path = loginOptions?.authPath ?? authPath ?? authJsonPath(env);
1820
+ const sleep = loginOptions?.sleep ?? defaultSleep;
1821
+ const openUrl = loginOptions?.openUrl ?? ((url) => openExternal(url, { spawn: loginOptions?.openSpawn }));
1822
+ const clock = () => typeof loginOptions?.now === "function" ? loginOptions.now() : now();
1823
+ let endpoints;
1824
+ let device;
1825
+ try {
1826
+ endpoints = loginOptions?.endpoints ?? oauthEndpoints ?? await discoverOauthEndpoints(XAI_OAUTH_ISSUER, { fetch: fetchLogin });
1827
+ device = await requestDeviceAuthorization({
1828
+ endpoint: endpoints.deviceAuthorizationEndpoint,
1829
+ fetch: fetchLogin
1620
1830
  });
1621
- return { ok: true, pending: true, ...extra, ...await status() };
1831
+ } catch (error) {
1832
+ const message = error instanceof Error ? error.message : "Could not start sign-in";
1833
+ return { ok: false, error: message, ...await status() };
1834
+ }
1835
+ const loginUrl = device.verificationUriComplete || device.verificationUri;
1836
+ if (!isXaiHttpsUrl(loginUrl) || !device.userCode || !device.deviceCode) {
1837
+ return { ok: false, error: "Sign-in did not return a verification link.", ...await status() };
1622
1838
  }
1623
- return { ...await pull(), pending: false, ...extra };
1839
+ try {
1840
+ void openUrl(loginUrl);
1841
+ } catch {
1842
+ }
1843
+ const deadline = clock() + (typeof device.expiresIn === "number" && device.expiresIn > 0 ? device.expiresIn : 900) * 1e3;
1844
+ let interval = Number.isFinite(device.interval) && device.interval >= 0 ? device.interval : 5;
1845
+ const poll = async () => {
1846
+ try {
1847
+ while (epoch === loginEpoch) {
1848
+ if (clock() >= deadline) {
1849
+ logger?.warn?.("Grok subscription sign-in timed out before authorization");
1850
+ return;
1851
+ }
1852
+ await sleep(interval * 1e3);
1853
+ if (epoch !== loginEpoch) return;
1854
+ const result = await exchangeDeviceCode({
1855
+ endpoint: endpoints.tokenEndpoint,
1856
+ deviceCode: device.deviceCode,
1857
+ fetch: fetchLogin,
1858
+ now: clock
1859
+ });
1860
+ if (epoch !== loginEpoch) return;
1861
+ if (result.kind === "pending") continue;
1862
+ if (result.kind === "slow_down") {
1863
+ interval += 5;
1864
+ continue;
1865
+ }
1866
+ if (result.kind !== "ok") {
1867
+ logger?.warn?.("Grok subscription sign-in stopped: %s", result.error ?? "unknown");
1868
+ return;
1869
+ }
1870
+ writeOauthSession(path, {
1871
+ issuer: result.issuer,
1872
+ clientId: result.clientId,
1873
+ accessToken: result.accessToken,
1874
+ refreshToken: result.refreshToken,
1875
+ expiresAt: result.expiresAt
1876
+ });
1877
+ if (epoch !== loginEpoch) return;
1878
+ await pull();
1879
+ return;
1880
+ }
1881
+ } catch (error) {
1882
+ logger?.warn?.(
1883
+ "Grok subscription sign-in failed: %s",
1884
+ error instanceof Error ? error.message : "unknown"
1885
+ );
1886
+ } finally {
1887
+ try {
1888
+ loginOptions?.onSettled?.();
1889
+ } catch {
1890
+ }
1891
+ }
1892
+ };
1893
+ void poll();
1894
+ return { ok: true, pending: true, loginUrl, userCode: device.userCode, ...await status() };
1624
1895
  };
1625
1896
  const logout = async () => {
1897
+ loginEpoch += 1;
1626
1898
  memoryAccessToken = void 0;
1627
1899
  memorySessionTouched = true;
1628
1900
  await clearToken().catch((error) => {
@@ -1645,7 +1917,7 @@ function createSessionService({
1645
1917
  login,
1646
1918
  logout,
1647
1919
  currentToken: readStoredToken,
1648
- /** Force a CLI-backed renewal; used when the provider answers 401. */
1920
+ /** Force a refresh-token renewal; used when the provider answers 401. */
1649
1921
  refreshToken: () => renewAccessToken(),
1650
1922
  models: () => catalog.models,
1651
1923
  catalog: () => catalog,
@@ -1656,10 +1928,10 @@ function createSessionService({
1656
1928
 
1657
1929
  // src/plugin-version.js
1658
1930
  import { spawn as spawn2 } from "node:child_process";
1659
- import { existsSync as existsSync4 } from "node:fs";
1931
+ import { existsSync as existsSync3 } from "node:fs";
1660
1932
  import { readdir, readFile, realpath } from "node:fs/promises";
1661
1933
  import { homedir as homedir3 } from "node:os";
1662
- import { join as join4 } from "node:path";
1934
+ import { join as join3 } from "node:path";
1663
1935
  import { fileURLToPath } from "node:url";
1664
1936
  var PACKAGE_NAME = PLUGIN_ID;
1665
1937
  var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -1703,7 +1975,7 @@ var readManifest = async (path) => {
1703
1975
  };
1704
1976
  async function confirmInstalled({ profileDir, expected, settleDelayMs, attempts = 3 }) {
1705
1977
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
1706
- const installed = (await readManifest(join4(profileDir, "node_modules", PACKAGE_NAME, "package.json")))?.version;
1978
+ const installed = (await readManifest(join3(profileDir, "node_modules", PACKAGE_NAME, "package.json")))?.version;
1707
1979
  if (installed === expected) return;
1708
1980
  if (attempt < attempts) await new Promise((resolve) => {
1709
1981
  setTimeout(resolve, settleDelayMs);
@@ -1720,20 +1992,20 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
1720
1992
  }
1721
1993
  let entries = [];
1722
1994
  try {
1723
- entries = await readdir(join4(dshHome, "profiles"), { withFileTypes: true });
1995
+ entries = await readdir(join3(dshHome, "profiles"), { withFileTypes: true });
1724
1996
  } catch {
1725
1997
  entries = [];
1726
1998
  }
1727
1999
  const declared = [];
1728
2000
  for (const entry of entries) {
1729
2001
  if (!entry.isDirectory()) continue;
1730
- const profileDir = join4(dshHome, "profiles", entry.name);
1731
- const spec = (await readManifest(join4(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
2002
+ const profileDir = join3(dshHome, "profiles", entry.name);
2003
+ const spec = (await readManifest(join3(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
1732
2004
  if (typeof spec !== "string") continue;
1733
2005
  declared.push({ profile: entry.name, spec });
1734
2006
  if (ownReal === void 0) continue;
1735
2007
  try {
1736
- const installed = await realpath(join4(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
2008
+ const installed = await realpath(join3(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
1737
2009
  if (installed === ownReal) return { kind: classifySpec(spec), profile: entry.name };
1738
2010
  } catch {
1739
2011
  }
@@ -1741,9 +2013,9 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
1741
2013
  if (declared.length === 1) return { kind: classifySpec(declared[0].spec), profile: declared[0].profile };
1742
2014
  return { kind: "unknown" };
1743
2015
  }
1744
- var isExistingPath = (value) => typeof value === "string" && value !== "" && existsSync4(value);
2016
+ var isExistingPath = (value) => typeof value === "string" && value !== "" && existsSync3(value);
1745
2017
  var PNPM_SCRIPT = /[\\/]pnpm\.(?:mjs|cjs|js)$/u;
1746
- var dshRuntimeRoot = (value) => isExistingPath(value) && existsSync4(join4(value, "node_modules", "@deepseek-ai", "dsh", "package.json")) ? value : void 0;
2018
+ var dshRuntimeRoot = (value) => isExistingPath(value) && existsSync3(join3(value, "node_modules", "@deepseek-ai", "dsh", "package.json")) ? value : void 0;
1747
2019
  function resolvePnpm({ argv } = {}) {
1748
2020
  const entries = Array.isArray(argv) ? argv : [];
1749
2021
  for (const entry of entries) {
@@ -1752,8 +2024,8 @@ function resolvePnpm({ argv } = {}) {
1752
2024
  for (const entry of entries) {
1753
2025
  const root = dshRuntimeRoot(entry);
1754
2026
  if (root === void 0) continue;
1755
- const script = join4(root, "..", "pnpm", "bin", "pnpm.mjs");
1756
- if (existsSync4(script)) return script;
2027
+ const script = join3(root, "..", "pnpm", "bin", "pnpm.mjs");
2028
+ if (existsSync3(script)) return script;
1757
2029
  }
1758
2030
  return void 0;
1759
2031
  }
@@ -1826,15 +2098,15 @@ function createGrokPluginManager({
1826
2098
  } = {}) {
1827
2099
  if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
1828
2100
  if (typeof runCommand !== "function") throw new Error("Grok plugin manager requires runCommand");
1829
- const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join4(homedir3(), ".dsh");
2101
+ const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join3(homedir3(), ".dsh");
1830
2102
  const launcher = resolveLauncher({ argv, execPath });
1831
2103
  let cached;
1832
2104
  let inFlight;
1833
2105
  let generation = 0;
1834
2106
  let updating = false;
1835
2107
  const fetchLatest = async (signal) => {
1836
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
1837
- const requestSignal = signal === void 0 ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
2108
+ const timeoutSignal2 = AbortSignal.timeout(timeoutMs);
2109
+ const requestSignal = signal === void 0 ? timeoutSignal2 : AbortSignal.any([signal, timeoutSignal2]);
1838
2110
  let response;
1839
2111
  try {
1840
2112
  response = await fetchImpl(registryUrl, {
@@ -1916,7 +2188,7 @@ function createGrokPluginManager({
1916
2188
  throw new Error("Grok plugin update could not find the owning profile");
1917
2189
  }
1918
2190
  const latest = await fetchLatest(signal);
1919
- const profileDir = join4(dshHome, "profiles", install.profile);
2191
+ const profileDir = join3(dshHome, "profiles", install.profile);
1920
2192
  let result;
1921
2193
  try {
1922
2194
  result = await runCommand({
@@ -1982,7 +2254,10 @@ function stripSecrets(value) {
1982
2254
  delete next.accessToken;
1983
2255
  delete next.token;
1984
2256
  delete next.refresh;
2257
+ delete next.refreshToken;
1985
2258
  delete next.refresh_token;
2259
+ delete next.deviceCode;
2260
+ delete next.device_code;
1986
2261
  delete next.key;
1987
2262
  if (next.catalog) {
1988
2263
  next.catalog = {