dsh-grok-subscription 2.0.2 → 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/CHANGELOG.md +10 -0
- package/README.en.md +14 -14
- package/README.md +13 -13
- package/SECURITY.md +5 -4
- package/docs/guide.en.md +13 -21
- package/docs/guide.zh-CN.md +13 -21
- package/lib/client.js +16 -22
- package/lib/index.js +495 -205
- package/package.json +1 -1
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 {
|
|
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
|
|
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(`${
|
|
178
|
-
if (issuer ===
|
|
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({
|
|
@@ -323,7 +422,21 @@ async function readResponseJson(response, options = {}) {
|
|
|
323
422
|
|
|
324
423
|
// src/catalog.js
|
|
325
424
|
var REASONING_LEVELS = Object.freeze(["low", "medium", "high", "xhigh"]);
|
|
326
|
-
var
|
|
425
|
+
var DOCUMENTED_CONTEXT_WINDOWS = Object.freeze({
|
|
426
|
+
"grok-4.7": 5e5,
|
|
427
|
+
"grok-4.7-build-fast": 5e5,
|
|
428
|
+
"grok-4.6": 5e5,
|
|
429
|
+
"grok-4.5": 5e5,
|
|
430
|
+
"grok-4.3": 1e6,
|
|
431
|
+
"grok-4.20-0309-reasoning": 1e6,
|
|
432
|
+
"grok-4.20-0309-non-reasoning": 1e6,
|
|
433
|
+
"grok-4.20-multi-agent-0309": 1e6,
|
|
434
|
+
"grok-build-0.1": 256e3
|
|
435
|
+
});
|
|
436
|
+
var FALLBACK_CONTEXT_WINDOW = 5e5;
|
|
437
|
+
function documentedContextWindow(id) {
|
|
438
|
+
return Object.prototype.hasOwnProperty.call(DOCUMENTED_CONTEXT_WINDOWS, id) ? DOCUMENTED_CONTEXT_WINDOWS[id] : void 0;
|
|
439
|
+
}
|
|
327
440
|
function asId(value) {
|
|
328
441
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
329
442
|
}
|
|
@@ -358,11 +471,12 @@ function extractLiveModels(body) {
|
|
|
358
471
|
if (!id || seen.has(id)) continue;
|
|
359
472
|
seen.add(id);
|
|
360
473
|
const object = row && typeof row === "object" ? row : { id };
|
|
474
|
+
const contextWindow = documentedContextWindow(id) ?? contextWindowOf(object) ?? FALLBACK_CONTEXT_WINDOW;
|
|
361
475
|
models.push(Object.freeze({
|
|
362
476
|
id,
|
|
363
477
|
name: displayNameOf(id, object),
|
|
364
|
-
contextWindow
|
|
365
|
-
maxTokens:
|
|
478
|
+
contextWindow,
|
|
479
|
+
maxTokens: contextWindow,
|
|
366
480
|
reasoning: object.reasoning !== false,
|
|
367
481
|
reasoningEfforts: reasoningEffortsOf(object)
|
|
368
482
|
}));
|
|
@@ -373,8 +487,8 @@ function fallbackModels() {
|
|
|
373
487
|
return FALLBACK_MODEL_IDS.map((id) => Object.freeze({
|
|
374
488
|
id,
|
|
375
489
|
name: id === "grok-4.7" ? "Grok 4.7" : id === "grok-4.6" ? "Grok 4.6" : "Grok 4.5",
|
|
376
|
-
contextWindow: FALLBACK_CONTEXT_WINDOW,
|
|
377
|
-
maxTokens: FALLBACK_CONTEXT_WINDOW,
|
|
490
|
+
contextWindow: documentedContextWindow(id) ?? FALLBACK_CONTEXT_WINDOW,
|
|
491
|
+
maxTokens: documentedContextWindow(id) ?? FALLBACK_CONTEXT_WINDOW,
|
|
378
492
|
reasoning: true,
|
|
379
493
|
reasoningEfforts: id === "grok-4.5" ? ["low", "medium", "high"] : ["low", "medium", "high", "xhigh"],
|
|
380
494
|
defaultEffort: "high",
|
|
@@ -814,7 +928,7 @@ async function fetchBillingUsage(accessToken, options = {}) {
|
|
|
814
928
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
815
929
|
import { createRequire } from "node:module";
|
|
816
930
|
import { homedir as homedir2 } from "node:os";
|
|
817
|
-
import { dirname, join as join2 } from "node:path";
|
|
931
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
818
932
|
import { pathToFileURL } from "node:url";
|
|
819
933
|
function withImportTimeout(promise, ms, message) {
|
|
820
934
|
let timer;
|
|
@@ -835,16 +949,16 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
|
|
|
835
949
|
const override = env.DSH_GROK_PEER_ROOT;
|
|
836
950
|
if (typeof override === "string" && override.trim()) push(override.trim());
|
|
837
951
|
if (typeof argv1 === "string" && argv1) {
|
|
838
|
-
let dir =
|
|
839
|
-
for (let depth = 0; depth < 6 && dir && dir !==
|
|
952
|
+
let dir = dirname2(argv1);
|
|
953
|
+
for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
|
|
840
954
|
push(join2(dir, "node_modules"));
|
|
841
|
-
dir =
|
|
955
|
+
dir = dirname2(dir);
|
|
842
956
|
}
|
|
843
957
|
}
|
|
844
958
|
const prefix = env.NPM_CONFIG_PREFIX;
|
|
845
959
|
if (typeof prefix === "string" && prefix.trim()) push(join2(prefix.trim(), "lib", "node_modules"));
|
|
846
960
|
if (typeof process.execPath === "string" && process.execPath) {
|
|
847
|
-
push(join2(
|
|
961
|
+
push(join2(dirname2(process.execPath), "..", "lib", "node_modules"));
|
|
848
962
|
}
|
|
849
963
|
push(join2("/opt/homebrew", "lib", "node_modules"));
|
|
850
964
|
push(join2("/usr/local", "lib", "node_modules"));
|
|
@@ -1134,40 +1248,209 @@ async function createGrokBuildAdapter(session, options = {}) {
|
|
|
1134
1248
|
};
|
|
1135
1249
|
}
|
|
1136
1250
|
|
|
1137
|
-
// src/
|
|
1138
|
-
var
|
|
1139
|
-
var
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
+
}
|
|
1148
1289
|
return void 0;
|
|
1149
1290
|
}
|
|
1150
|
-
function
|
|
1151
|
-
|
|
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();
|
|
1152
1361
|
}
|
|
1153
|
-
function
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
if (!hasPathSeparator(override)) return override;
|
|
1157
|
-
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();
|
|
1158
1365
|
}
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1366
|
+
const exp = jwtExpiryMs(accessToken);
|
|
1367
|
+
return typeof exp === "number" ? new Date(exp).toISOString() : void 0;
|
|
1368
|
+
}
|
|
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;
|
|
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
|
+
};
|
|
1163
1406
|
}
|
|
1164
|
-
function
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
+
};
|
|
1170
1451
|
}
|
|
1452
|
+
|
|
1453
|
+
// src/session.js
|
|
1171
1454
|
function openExternal(url, options = {}) {
|
|
1172
1455
|
const spawnFn = options.spawn ?? spawn;
|
|
1173
1456
|
const platform = options.platform ?? process.platform;
|
|
@@ -1191,127 +1474,59 @@ function openExternal(url, options = {}) {
|
|
|
1191
1474
|
child.unref?.();
|
|
1192
1475
|
});
|
|
1193
1476
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
const bin = resolveGrokBin(options.env, options.exists);
|
|
1199
|
-
const args = options.device ? ["login", "--device-auth"] : ["login"];
|
|
1200
|
-
const startTimeoutMs = typeof options.startTimeoutMs === "number" && options.startTimeoutMs > 0 ? options.startTimeoutMs : LOGIN_START_TIMEOUT_MS;
|
|
1201
|
-
const openUrl = options.openUrl ?? ((url) => openExternal(url, { spawn: options.openSpawn }));
|
|
1202
|
-
return new Promise((resolve, reject) => {
|
|
1203
|
-
let child;
|
|
1204
|
-
try {
|
|
1205
|
-
child = spawnFn(bin, args, {
|
|
1206
|
-
stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
|
|
1207
|
-
env: options.env ?? process.env
|
|
1208
|
-
});
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
let settled = false;
|
|
1214
|
-
let loginUrl;
|
|
1215
|
-
let userCode;
|
|
1216
|
-
let buffer = "";
|
|
1217
|
-
let timer;
|
|
1218
|
-
const succeed = (value) => {
|
|
1219
|
-
if (settled) return;
|
|
1220
|
-
settled = true;
|
|
1221
|
-
if (timer) clearTimeout(timer);
|
|
1222
|
-
resolve(value);
|
|
1223
|
-
};
|
|
1224
|
-
const scan = (text) => {
|
|
1225
|
-
if (text) buffer = `${buffer}${text}`.slice(-8192);
|
|
1226
|
-
if (!userCode) {
|
|
1227
|
-
const code = DEVICE_CODE_PATTERN.exec(buffer);
|
|
1228
|
-
if (code) userCode = code[1];
|
|
1229
|
-
}
|
|
1230
|
-
if (loginUrl) return;
|
|
1231
|
-
const match = LOGIN_URL_PATTERN.exec(buffer);
|
|
1232
|
-
if (!match) return;
|
|
1233
|
-
loginUrl = match[0];
|
|
1234
|
-
try {
|
|
1235
|
-
void openUrl(loginUrl);
|
|
1236
|
-
} catch {
|
|
1237
|
-
}
|
|
1238
|
-
succeed({ ok: true, pending: true, loginUrl, userCode, child });
|
|
1239
|
-
};
|
|
1240
|
-
const piped = Boolean(child.stdout && child.stderr);
|
|
1241
|
-
if (piped) {
|
|
1242
|
-
child.stdout.on("data", (chunk) => scan(String(chunk)));
|
|
1243
|
-
child.stderr.on("data", (chunk) => scan(String(chunk)));
|
|
1244
|
-
timer = setTimeout(() => {
|
|
1245
|
-
succeed({
|
|
1246
|
-
ok: true,
|
|
1247
|
-
pending: true,
|
|
1248
|
-
loginUrl,
|
|
1249
|
-
userCode,
|
|
1250
|
-
child,
|
|
1251
|
-
warning: `grok login printed no sign-in URL within ${startTimeoutMs}ms`
|
|
1252
|
-
});
|
|
1253
|
-
}, startTimeoutMs);
|
|
1254
|
-
}
|
|
1255
|
-
child.on("error", (error) => {
|
|
1256
|
-
if (settled) return;
|
|
1257
|
-
settled = true;
|
|
1258
|
-
if (timer) clearTimeout(timer);
|
|
1259
|
-
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
1260
|
-
});
|
|
1261
|
-
child.on("exit", (code, signal) => {
|
|
1262
|
-
if (settled) return;
|
|
1263
|
-
settled = true;
|
|
1264
|
-
if (timer) clearTimeout(timer);
|
|
1265
|
-
if (code === 0) resolve({ ok: true, pending: false, loginUrl, userCode, child });
|
|
1266
|
-
else reject(new Error(signal ? `grok login terminated by ${signal}` : `grok login exited with code ${code ?? "unknown"}`));
|
|
1267
|
-
});
|
|
1477
|
+
function defaultSleep(ms) {
|
|
1478
|
+
return new Promise((resolve) => {
|
|
1479
|
+
const timer = setTimeout(resolve, ms);
|
|
1480
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1268
1481
|
});
|
|
1269
1482
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
1283
|
-
return;
|
|
1284
|
-
}
|
|
1285
|
-
let settled = false;
|
|
1286
|
-
const timer = setTimeout(() => {
|
|
1287
|
-
if (settled) return;
|
|
1288
|
-
settled = true;
|
|
1289
|
-
try {
|
|
1290
|
-
child.kill("SIGKILL");
|
|
1291
|
-
} catch {
|
|
1292
|
-
}
|
|
1293
|
-
resolve({ ok: false, reason: "timeout" });
|
|
1294
|
-
}, timeoutMs);
|
|
1295
|
-
child.on("error", (error) => {
|
|
1296
|
-
if (settled) return;
|
|
1297
|
-
settled = true;
|
|
1298
|
-
clearTimeout(timer);
|
|
1299
|
-
reject(new Error(`Could not run grok CLI (${bin})`, { cause: error }));
|
|
1300
|
-
});
|
|
1301
|
-
child.on("exit", (code) => {
|
|
1302
|
-
if (settled) return;
|
|
1303
|
-
settled = true;
|
|
1304
|
-
clearTimeout(timer);
|
|
1305
|
-
resolve({ ok: true, code });
|
|
1306
|
-
});
|
|
1307
|
-
});
|
|
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;
|
|
1308
1495
|
}
|
|
1309
1496
|
async function defaultRenewSession(options = {}) {
|
|
1310
1497
|
const env = options.env ?? process.env;
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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;
|
|
1315
1530
|
}
|
|
1316
1531
|
async function defaultCredentialRefOf() {
|
|
1317
1532
|
try {
|
|
@@ -1348,9 +1563,21 @@ function createSessionService({
|
|
|
1348
1563
|
storeTokenTimeoutMs = STORE_TOKEN_TIMEOUT_MS,
|
|
1349
1564
|
credentialsIoTimeoutMs = CREDENTIALS_IO_TIMEOUT_MS,
|
|
1350
1565
|
credentialRefOf = defaultCredentialRefOf,
|
|
1351
|
-
renewSession
|
|
1566
|
+
renewSession,
|
|
1567
|
+
fetch: fetchImpl,
|
|
1568
|
+
authPath,
|
|
1569
|
+
env,
|
|
1570
|
+
oauthEndpoints,
|
|
1352
1571
|
now = () => Date.now()
|
|
1353
1572
|
} = {}) {
|
|
1573
|
+
const renew = renewSession ?? (() => defaultRenewSession({
|
|
1574
|
+
readAuth,
|
|
1575
|
+
fetch: fetchImpl,
|
|
1576
|
+
authPath,
|
|
1577
|
+
env,
|
|
1578
|
+
endpoints: oauthEndpoints,
|
|
1579
|
+
now
|
|
1580
|
+
}));
|
|
1354
1581
|
let catalog = { models: [], source: "signed-out", error: void 0 };
|
|
1355
1582
|
let lastPublic = publicSessionView(void 0);
|
|
1356
1583
|
let lastUsage = unavailable("Not fetched yet");
|
|
@@ -1389,7 +1616,7 @@ function createSessionService({
|
|
|
1389
1616
|
if (renewalInFlight) return renewalInFlight;
|
|
1390
1617
|
renewalInFlight = (async () => {
|
|
1391
1618
|
try {
|
|
1392
|
-
const session = await
|
|
1619
|
+
const session = await renew();
|
|
1393
1620
|
const token = adoptSession(session);
|
|
1394
1621
|
if (token) notifyCatalogChange();
|
|
1395
1622
|
return token;
|
|
@@ -1529,7 +1756,7 @@ function createSessionService({
|
|
|
1529
1756
|
);
|
|
1530
1757
|
});
|
|
1531
1758
|
});
|
|
1532
|
-
const message = parsed.reason === "api-key-only" ? "Found an API-key entry only. Sign in with SuperGrok / X Premium
|
|
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.";
|
|
1533
1760
|
notifyCatalogChange();
|
|
1534
1761
|
return { ok: false, error: message, account: lastPublic, catalog, usage: lastUsage };
|
|
1535
1762
|
}
|
|
@@ -1551,15 +1778,16 @@ function createSessionService({
|
|
|
1551
1778
|
kickBackgroundUsageRefresh();
|
|
1552
1779
|
return { ok: true, account: lastPublic, catalog, usage: lastUsage };
|
|
1553
1780
|
};
|
|
1781
|
+
const snapshot = () => ({ account: lastPublic, catalog, usage: lastUsage, authPath: authPath ?? authJsonPath(env) });
|
|
1554
1782
|
const status = async () => {
|
|
1555
1783
|
if (typeof memoryAccessToken === "string" && memoryAccessToken.length > 0 || lastPublic.signedIn === true) {
|
|
1556
|
-
return
|
|
1784
|
+
return snapshot();
|
|
1557
1785
|
}
|
|
1558
1786
|
try {
|
|
1559
1787
|
const parsed = readAuth();
|
|
1560
1788
|
if (parsed.session) {
|
|
1561
1789
|
adoptSession(parsed.session);
|
|
1562
|
-
return
|
|
1790
|
+
return snapshot();
|
|
1563
1791
|
}
|
|
1564
1792
|
} catch (error) {
|
|
1565
1793
|
logger?.warn?.(
|
|
@@ -1578,36 +1806,95 @@ function createSessionService({
|
|
|
1578
1806
|
lastPublic = publicSessionView(void 0);
|
|
1579
1807
|
catalog = { models: [], source: "signed-out", error: void 0 };
|
|
1580
1808
|
lastUsage = unavailable("Not signed in");
|
|
1581
|
-
return
|
|
1809
|
+
return snapshot();
|
|
1582
1810
|
}
|
|
1583
1811
|
memoryAccessToken = token;
|
|
1584
1812
|
lastPublic = Object.freeze({ signedIn: true, maskedAccount: "\u2022\u2022\u2022\u2022", authMode: "oidc", source: "dsh-credentials" });
|
|
1585
|
-
return
|
|
1813
|
+
return snapshot();
|
|
1586
1814
|
};
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
};
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
);
|
|
1603
|
-
});
|
|
1604
|
-
});
|
|
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
|
|
1605
1830
|
});
|
|
1606
|
-
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
const message = error instanceof Error ? error.message : "Could not start sign-in";
|
|
1833
|
+
return { ok: false, error: message, ...await status() };
|
|
1607
1834
|
}
|
|
1608
|
-
|
|
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() };
|
|
1838
|
+
}
|
|
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() };
|
|
1609
1895
|
};
|
|
1610
1896
|
const logout = async () => {
|
|
1897
|
+
loginEpoch += 1;
|
|
1611
1898
|
memoryAccessToken = void 0;
|
|
1612
1899
|
memorySessionTouched = true;
|
|
1613
1900
|
await clearToken().catch((error) => {
|
|
@@ -1630,7 +1917,7 @@ function createSessionService({
|
|
|
1630
1917
|
login,
|
|
1631
1918
|
logout,
|
|
1632
1919
|
currentToken: readStoredToken,
|
|
1633
|
-
/** Force a
|
|
1920
|
+
/** Force a refresh-token renewal; used when the provider answers 401. */
|
|
1634
1921
|
refreshToken: () => renewAccessToken(),
|
|
1635
1922
|
models: () => catalog.models,
|
|
1636
1923
|
catalog: () => catalog,
|
|
@@ -1641,10 +1928,10 @@ function createSessionService({
|
|
|
1641
1928
|
|
|
1642
1929
|
// src/plugin-version.js
|
|
1643
1930
|
import { spawn as spawn2 } from "node:child_process";
|
|
1644
|
-
import { existsSync as
|
|
1931
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1645
1932
|
import { readdir, readFile, realpath } from "node:fs/promises";
|
|
1646
1933
|
import { homedir as homedir3 } from "node:os";
|
|
1647
|
-
import { join as
|
|
1934
|
+
import { join as join3 } from "node:path";
|
|
1648
1935
|
import { fileURLToPath } from "node:url";
|
|
1649
1936
|
var PACKAGE_NAME = PLUGIN_ID;
|
|
1650
1937
|
var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -1688,7 +1975,7 @@ var readManifest = async (path) => {
|
|
|
1688
1975
|
};
|
|
1689
1976
|
async function confirmInstalled({ profileDir, expected, settleDelayMs, attempts = 3 }) {
|
|
1690
1977
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1691
|
-
const installed = (await readManifest(
|
|
1978
|
+
const installed = (await readManifest(join3(profileDir, "node_modules", PACKAGE_NAME, "package.json")))?.version;
|
|
1692
1979
|
if (installed === expected) return;
|
|
1693
1980
|
if (attempt < attempts) await new Promise((resolve) => {
|
|
1694
1981
|
setTimeout(resolve, settleDelayMs);
|
|
@@ -1705,20 +1992,20 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
|
1705
1992
|
}
|
|
1706
1993
|
let entries = [];
|
|
1707
1994
|
try {
|
|
1708
|
-
entries = await readdir(
|
|
1995
|
+
entries = await readdir(join3(dshHome, "profiles"), { withFileTypes: true });
|
|
1709
1996
|
} catch {
|
|
1710
1997
|
entries = [];
|
|
1711
1998
|
}
|
|
1712
1999
|
const declared = [];
|
|
1713
2000
|
for (const entry of entries) {
|
|
1714
2001
|
if (!entry.isDirectory()) continue;
|
|
1715
|
-
const profileDir =
|
|
1716
|
-
const spec = (await readManifest(
|
|
2002
|
+
const profileDir = join3(dshHome, "profiles", entry.name);
|
|
2003
|
+
const spec = (await readManifest(join3(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
|
|
1717
2004
|
if (typeof spec !== "string") continue;
|
|
1718
2005
|
declared.push({ profile: entry.name, spec });
|
|
1719
2006
|
if (ownReal === void 0) continue;
|
|
1720
2007
|
try {
|
|
1721
|
-
const installed = await realpath(
|
|
2008
|
+
const installed = await realpath(join3(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
|
|
1722
2009
|
if (installed === ownReal) return { kind: classifySpec(spec), profile: entry.name };
|
|
1723
2010
|
} catch {
|
|
1724
2011
|
}
|
|
@@ -1726,9 +2013,9 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
|
1726
2013
|
if (declared.length === 1) return { kind: classifySpec(declared[0].spec), profile: declared[0].profile };
|
|
1727
2014
|
return { kind: "unknown" };
|
|
1728
2015
|
}
|
|
1729
|
-
var isExistingPath = (value) => typeof value === "string" && value !== "" &&
|
|
2016
|
+
var isExistingPath = (value) => typeof value === "string" && value !== "" && existsSync3(value);
|
|
1730
2017
|
var PNPM_SCRIPT = /[\\/]pnpm\.(?:mjs|cjs|js)$/u;
|
|
1731
|
-
var dshRuntimeRoot = (value) => isExistingPath(value) &&
|
|
2018
|
+
var dshRuntimeRoot = (value) => isExistingPath(value) && existsSync3(join3(value, "node_modules", "@deepseek-ai", "dsh", "package.json")) ? value : void 0;
|
|
1732
2019
|
function resolvePnpm({ argv } = {}) {
|
|
1733
2020
|
const entries = Array.isArray(argv) ? argv : [];
|
|
1734
2021
|
for (const entry of entries) {
|
|
@@ -1737,8 +2024,8 @@ function resolvePnpm({ argv } = {}) {
|
|
|
1737
2024
|
for (const entry of entries) {
|
|
1738
2025
|
const root = dshRuntimeRoot(entry);
|
|
1739
2026
|
if (root === void 0) continue;
|
|
1740
|
-
const script =
|
|
1741
|
-
if (
|
|
2027
|
+
const script = join3(root, "..", "pnpm", "bin", "pnpm.mjs");
|
|
2028
|
+
if (existsSync3(script)) return script;
|
|
1742
2029
|
}
|
|
1743
2030
|
return void 0;
|
|
1744
2031
|
}
|
|
@@ -1811,15 +2098,15 @@ function createGrokPluginManager({
|
|
|
1811
2098
|
} = {}) {
|
|
1812
2099
|
if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
|
|
1813
2100
|
if (typeof runCommand !== "function") throw new Error("Grok plugin manager requires runCommand");
|
|
1814
|
-
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME :
|
|
2101
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join3(homedir3(), ".dsh");
|
|
1815
2102
|
const launcher = resolveLauncher({ argv, execPath });
|
|
1816
2103
|
let cached;
|
|
1817
2104
|
let inFlight;
|
|
1818
2105
|
let generation = 0;
|
|
1819
2106
|
let updating = false;
|
|
1820
2107
|
const fetchLatest = async (signal) => {
|
|
1821
|
-
const
|
|
1822
|
-
const requestSignal = signal === void 0 ?
|
|
2108
|
+
const timeoutSignal2 = AbortSignal.timeout(timeoutMs);
|
|
2109
|
+
const requestSignal = signal === void 0 ? timeoutSignal2 : AbortSignal.any([signal, timeoutSignal2]);
|
|
1823
2110
|
let response;
|
|
1824
2111
|
try {
|
|
1825
2112
|
response = await fetchImpl(registryUrl, {
|
|
@@ -1901,7 +2188,7 @@ function createGrokPluginManager({
|
|
|
1901
2188
|
throw new Error("Grok plugin update could not find the owning profile");
|
|
1902
2189
|
}
|
|
1903
2190
|
const latest = await fetchLatest(signal);
|
|
1904
|
-
const profileDir =
|
|
2191
|
+
const profileDir = join3(dshHome, "profiles", install.profile);
|
|
1905
2192
|
let result;
|
|
1906
2193
|
try {
|
|
1907
2194
|
result = await runCommand({
|
|
@@ -1967,7 +2254,10 @@ function stripSecrets(value) {
|
|
|
1967
2254
|
delete next.accessToken;
|
|
1968
2255
|
delete next.token;
|
|
1969
2256
|
delete next.refresh;
|
|
2257
|
+
delete next.refreshToken;
|
|
1970
2258
|
delete next.refresh_token;
|
|
2259
|
+
delete next.deviceCode;
|
|
2260
|
+
delete next.device_code;
|
|
1971
2261
|
delete next.key;
|
|
1972
2262
|
if (next.catalog) {
|
|
1973
2263
|
next.catalog = {
|