dsh-grok-subscription 2.0.3 → 2.0.5
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 +13 -0
- package/README.en.md +16 -14
- package/README.md +15 -13
- package/SECURITY.md +5 -4
- package/docs/guide.en.md +15 -21
- package/docs/guide.zh-CN.md +15 -21
- package/lib/client.js +16 -22
- package/lib/index.js +493 -204
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -14,8 +14,10 @@ var TOKEN_AUTH_VALUE = "xai-grok-cli";
|
|
|
14
14
|
var CLIENT_IDENTIFIER_HEADER = "x-grok-client-identifier";
|
|
15
15
|
var CLIENT_VERSION_HEADER = "x-grok-client-version";
|
|
16
16
|
var CLIENT_IDENTIFIER = "grok-shell";
|
|
17
|
-
var CLIENT_VERSION_FALLBACK = "
|
|
17
|
+
var CLIENT_VERSION_FALLBACK = "99.0.0";
|
|
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({
|
|
@@ -244,19 +343,33 @@ function parseClientVersion(document) {
|
|
|
244
343
|
}
|
|
245
344
|
return void 0;
|
|
246
345
|
}
|
|
346
|
+
function releaseAtLeast(version, minimum) {
|
|
347
|
+
const parse = (value) => {
|
|
348
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(typeof value === "string" ? value.trim() : "");
|
|
349
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
|
|
350
|
+
};
|
|
351
|
+
const left = parse(version);
|
|
352
|
+
const right = parse(minimum);
|
|
353
|
+
if (!left || !right) return false;
|
|
354
|
+
for (let index = 0; index < 3; index += 1) {
|
|
355
|
+
if (left[index] !== right[index]) return left[index] > right[index];
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
247
359
|
function readClientVersion(env = process.env, options = {}) {
|
|
248
360
|
if (typeof env.DSH_GROK_CLIENT_VERSION === "string" && env.DSH_GROK_CLIENT_VERSION.trim()) {
|
|
249
361
|
return env.DSH_GROK_CLIENT_VERSION.trim();
|
|
250
362
|
}
|
|
363
|
+
const fallback = options.fallback ?? CLIENT_VERSION_FALLBACK;
|
|
251
364
|
const path = options.path ?? versionJsonPath(env);
|
|
252
365
|
const exists = options.exists ?? existsSync;
|
|
253
366
|
const read = options.read ?? ((target) => readFileSync2(target, "utf8"));
|
|
254
|
-
if (!exists(path)) return
|
|
367
|
+
if (!exists(path)) return fallback;
|
|
255
368
|
try {
|
|
256
369
|
const parsed = parseClientVersion(JSON.parse(read(path)));
|
|
257
|
-
return parsed
|
|
370
|
+
return parsed && releaseAtLeast(parsed, fallback) ? parsed : fallback;
|
|
258
371
|
} catch {
|
|
259
|
-
return
|
|
372
|
+
return fallback;
|
|
260
373
|
}
|
|
261
374
|
}
|
|
262
375
|
function fingerprintHeaders(options = {}) {
|
|
@@ -829,7 +942,7 @@ async function fetchBillingUsage(accessToken, options = {}) {
|
|
|
829
942
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
830
943
|
import { createRequire } from "node:module";
|
|
831
944
|
import { homedir as homedir2 } from "node:os";
|
|
832
|
-
import { dirname, join as join2 } from "node:path";
|
|
945
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
833
946
|
import { pathToFileURL } from "node:url";
|
|
834
947
|
function withImportTimeout(promise, ms, message) {
|
|
835
948
|
let timer;
|
|
@@ -850,16 +963,16 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
|
|
|
850
963
|
const override = env.DSH_GROK_PEER_ROOT;
|
|
851
964
|
if (typeof override === "string" && override.trim()) push(override.trim());
|
|
852
965
|
if (typeof argv1 === "string" && argv1) {
|
|
853
|
-
let dir =
|
|
854
|
-
for (let depth = 0; depth < 6 && dir && dir !==
|
|
966
|
+
let dir = dirname2(argv1);
|
|
967
|
+
for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
|
|
855
968
|
push(join2(dir, "node_modules"));
|
|
856
|
-
dir =
|
|
969
|
+
dir = dirname2(dir);
|
|
857
970
|
}
|
|
858
971
|
}
|
|
859
972
|
const prefix = env.NPM_CONFIG_PREFIX;
|
|
860
973
|
if (typeof prefix === "string" && prefix.trim()) push(join2(prefix.trim(), "lib", "node_modules"));
|
|
861
974
|
if (typeof process.execPath === "string" && process.execPath) {
|
|
862
|
-
push(join2(
|
|
975
|
+
push(join2(dirname2(process.execPath), "..", "lib", "node_modules"));
|
|
863
976
|
}
|
|
864
977
|
push(join2("/opt/homebrew", "lib", "node_modules"));
|
|
865
978
|
push(join2("/usr/local", "lib", "node_modules"));
|
|
@@ -1149,40 +1262,209 @@ async function createGrokBuildAdapter(session, options = {}) {
|
|
|
1149
1262
|
};
|
|
1150
1263
|
}
|
|
1151
1264
|
|
|
1152
|
-
// src/
|
|
1153
|
-
var
|
|
1154
|
-
var
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1265
|
+
// src/oauth.js
|
|
1266
|
+
var OAUTH_SCOPES = "openid profile email offline_access api:access grok-cli:access";
|
|
1267
|
+
var OAUTH_BODY_LIMIT = 64 * 1024;
|
|
1268
|
+
var OAUTH_ERROR_LIMIT = 180;
|
|
1269
|
+
function collapse(value) {
|
|
1270
|
+
return String(value ?? "").replace(/\s+/gu, " ").trim().slice(0, OAUTH_ERROR_LIMIT);
|
|
1271
|
+
}
|
|
1272
|
+
function oauthError(body, status) {
|
|
1273
|
+
const code = collapse(body?.error) || "request_failed";
|
|
1274
|
+
const description = collapse(body?.error_description);
|
|
1275
|
+
if (description && description !== code) return `${code}: ${description} (HTTP ${status})`;
|
|
1276
|
+
return `${code} (HTTP ${status})`;
|
|
1277
|
+
}
|
|
1278
|
+
async function readOauthBody(response) {
|
|
1279
|
+
const status = typeof response.status === "number" ? response.status : 0;
|
|
1280
|
+
const declared = Number(response.headers?.get?.("content-length"));
|
|
1281
|
+
if (Number.isFinite(declared) && declared > OAUTH_BODY_LIMIT) {
|
|
1282
|
+
throw new Error(`OAuth response exceeded ${OAUTH_BODY_LIMIT} bytes (HTTP ${status})`);
|
|
1283
|
+
}
|
|
1284
|
+
let text;
|
|
1285
|
+
try {
|
|
1286
|
+
text = await readResponseText(response);
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
throw new Error(`OAuth response could not be read (HTTP ${status})`, { cause: error });
|
|
1289
|
+
}
|
|
1290
|
+
if (typeof text !== "string" || text.length > OAUTH_BODY_LIMIT) {
|
|
1291
|
+
throw new Error(`OAuth response exceeded ${OAUTH_BODY_LIMIT} bytes (HTTP ${status})`);
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
return JSON.parse(text);
|
|
1295
|
+
} catch {
|
|
1296
|
+
throw new Error(`OAuth endpoint returned HTTP ${status} with a non-JSON body`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function timeoutSignal(ms) {
|
|
1300
|
+
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
|
|
1301
|
+
return AbortSignal.timeout(ms);
|
|
1302
|
+
}
|
|
1163
1303
|
return void 0;
|
|
1164
1304
|
}
|
|
1165
|
-
function
|
|
1166
|
-
|
|
1305
|
+
async function postForm(url, params, fetchImpl) {
|
|
1306
|
+
const response = await fetchImpl(url, {
|
|
1307
|
+
method: "POST",
|
|
1308
|
+
headers: {
|
|
1309
|
+
accept: "application/json",
|
|
1310
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
1311
|
+
},
|
|
1312
|
+
body: new URLSearchParams(params).toString(),
|
|
1313
|
+
signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
|
|
1314
|
+
});
|
|
1315
|
+
const body = await readOauthBody(response);
|
|
1316
|
+
return { status: typeof response.status === "number" ? response.status : 0, body };
|
|
1317
|
+
}
|
|
1318
|
+
function isXaiHttpsUrl(value) {
|
|
1319
|
+
try {
|
|
1320
|
+
const url = new URL(value);
|
|
1321
|
+
const host = url.hostname.toLowerCase();
|
|
1322
|
+
return url.protocol === "https:" && !url.username && !url.password && (host === "x.ai" || host.endsWith(".x.ai"));
|
|
1323
|
+
} catch {
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function endpointOnIssuer(value, issuer, field) {
|
|
1328
|
+
let url;
|
|
1329
|
+
let issuerUrl;
|
|
1330
|
+
try {
|
|
1331
|
+
url = new URL(value);
|
|
1332
|
+
issuerUrl = new URL(issuer);
|
|
1333
|
+
} catch {
|
|
1334
|
+
throw new Error(`OIDC discovery returned an invalid ${field}`);
|
|
1335
|
+
}
|
|
1336
|
+
if (url.protocol !== "https:" || issuerUrl.protocol !== "https:") {
|
|
1337
|
+
throw new Error(`OIDC discovery returned a non-HTTPS ${field}`);
|
|
1338
|
+
}
|
|
1339
|
+
if (url.username || url.password) throw new Error(`OIDC discovery returned credentials in ${field}`);
|
|
1340
|
+
if (url.origin !== issuerUrl.origin) throw new Error(`OIDC discovery ${field} does not match the issuer`);
|
|
1341
|
+
return url.toString();
|
|
1342
|
+
}
|
|
1343
|
+
function fallbackOauthEndpoints(issuer = XAI_OAUTH_ISSUER) {
|
|
1344
|
+
const base = String(issuer).replace(/\/+$/u, "");
|
|
1345
|
+
return {
|
|
1346
|
+
deviceAuthorizationEndpoint: `${base}/oauth2/device/code`,
|
|
1347
|
+
tokenEndpoint: `${base}/oauth2/token`
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
async function discoverOauthEndpoints(issuer = XAI_OAUTH_ISSUER, options = {}) {
|
|
1351
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
1352
|
+
const base = String(issuer).replace(/\/+$/u, "");
|
|
1353
|
+
try {
|
|
1354
|
+
const response = await fetchImpl(`${base}/.well-known/openid-configuration`, {
|
|
1355
|
+
headers: { accept: "application/json" },
|
|
1356
|
+
signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
|
|
1357
|
+
});
|
|
1358
|
+
const body = await readOauthBody(response);
|
|
1359
|
+
const status = typeof response.status === "number" ? response.status : 0;
|
|
1360
|
+
if (status < 200 || status >= 300) throw new Error(`OIDC discovery failed (HTTP ${status})`);
|
|
1361
|
+
const discovered = typeof body?.issuer === "string" ? body.issuer.replace(/\/+$/u, "") : "";
|
|
1362
|
+
if (discovered !== base) throw new Error("OIDC discovery issuer does not match the requested issuer");
|
|
1363
|
+
return {
|
|
1364
|
+
deviceAuthorizationEndpoint: endpointOnIssuer(body.device_authorization_endpoint, base, "device_authorization_endpoint"),
|
|
1365
|
+
tokenEndpoint: endpointOnIssuer(body.token_endpoint, base, "token_endpoint")
|
|
1366
|
+
};
|
|
1367
|
+
} catch {
|
|
1368
|
+
return fallbackOauthEndpoints(base);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
function clockMs(now) {
|
|
1372
|
+
if (typeof now === "function") return now();
|
|
1373
|
+
if (typeof now === "number" && Number.isFinite(now)) return now;
|
|
1374
|
+
return Date.now();
|
|
1167
1375
|
}
|
|
1168
|
-
function
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (!hasPathSeparator(override)) return override;
|
|
1172
|
-
return firstExisting(executableNames(override, platform, env), exists) ?? override;
|
|
1376
|
+
function expiresAtFrom(body, accessToken, nowMs) {
|
|
1377
|
+
if (typeof body?.expires_in === "number" && Number.isFinite(body.expires_in) && body.expires_in > 0) {
|
|
1378
|
+
return new Date(nowMs + body.expires_in * 1e3).toISOString();
|
|
1173
1379
|
}
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1380
|
+
const exp = jwtExpiryMs(accessToken);
|
|
1381
|
+
return typeof exp === "number" ? new Date(exp).toISOString() : void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function jwtExpiryMs(token) {
|
|
1384
|
+
const parts = typeof token === "string" ? token.split(".") : [];
|
|
1385
|
+
if (parts.length < 2) return void 0;
|
|
1386
|
+
try {
|
|
1387
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
1388
|
+
if (typeof payload?.exp === "number" && Number.isFinite(payload.exp)) return payload.exp * 1e3;
|
|
1389
|
+
} catch {
|
|
1390
|
+
return void 0;
|
|
1391
|
+
}
|
|
1392
|
+
return void 0;
|
|
1393
|
+
}
|
|
1394
|
+
async function requestDeviceAuthorization(options = {}) {
|
|
1395
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
1396
|
+
const clientId = options.clientId ?? GROK_OIDC_CLIENT_ID;
|
|
1397
|
+
const scopes = options.scopes ?? OAUTH_SCOPES;
|
|
1398
|
+
const endpoint = options.endpoint;
|
|
1399
|
+
if (!isXaiHttpsUrl(endpoint)) throw new Error("Refusing to request a device code from a non-x.ai endpoint");
|
|
1400
|
+
const { status, body } = await postForm(endpoint, {
|
|
1401
|
+
client_id: clientId,
|
|
1402
|
+
scope: scopes
|
|
1403
|
+
}, fetchImpl);
|
|
1404
|
+
if (body?.error || status < 200 || status >= 300) {
|
|
1405
|
+
throw new Error(`Could not start device sign-in: ${oauthError(body, status)}`);
|
|
1406
|
+
}
|
|
1407
|
+
const deviceCode = typeof body?.device_code === "string" ? body.device_code : "";
|
|
1408
|
+
const userCode = typeof body?.user_code === "string" ? body.user_code : "";
|
|
1409
|
+
const verificationUri = typeof body?.verification_uri === "string" ? body.verification_uri : void 0;
|
|
1410
|
+
const verificationUriComplete = typeof body?.verification_uri_complete === "string" ? body.verification_uri_complete : void 0;
|
|
1411
|
+
if (!deviceCode || !userCode) throw new Error("Device sign-in returned no user code");
|
|
1412
|
+
return {
|
|
1413
|
+
deviceCode,
|
|
1414
|
+
userCode,
|
|
1415
|
+
verificationUri,
|
|
1416
|
+
verificationUriComplete,
|
|
1417
|
+
expiresIn: typeof body?.expires_in === "number" ? body.expires_in : void 0,
|
|
1418
|
+
interval: typeof body?.interval === "number" ? body.interval : void 0
|
|
1419
|
+
};
|
|
1178
1420
|
}
|
|
1179
|
-
function
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1421
|
+
async function exchangeDeviceCode(options = {}) {
|
|
1422
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
1423
|
+
const clientId = options.clientId ?? GROK_OIDC_CLIENT_ID;
|
|
1424
|
+
const endpoint = options.endpoint;
|
|
1425
|
+
if (!isXaiHttpsUrl(endpoint)) return { kind: "stop", error: "refusing a non-x.ai token endpoint" };
|
|
1426
|
+
const { status, body } = await postForm(endpoint, {
|
|
1427
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1428
|
+
client_id: clientId,
|
|
1429
|
+
device_code: options.deviceCode
|
|
1430
|
+
}, fetchImpl);
|
|
1431
|
+
return classifyTokenResponse(body, status, clockMs(options.now));
|
|
1432
|
+
}
|
|
1433
|
+
async function refreshOauthToken(options = {}) {
|
|
1434
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
1435
|
+
const endpoint = options.endpoint;
|
|
1436
|
+
if (!isXaiHttpsUrl(endpoint)) throw new Error("Refusing to refresh a token at a non-x.ai endpoint");
|
|
1437
|
+
const { status, body } = await postForm(endpoint, {
|
|
1438
|
+
grant_type: "refresh_token",
|
|
1439
|
+
client_id: options.clientId ?? GROK_OIDC_CLIENT_ID,
|
|
1440
|
+
refresh_token: options.refreshToken
|
|
1441
|
+
}, fetchImpl);
|
|
1442
|
+
const result = classifyTokenResponse(body, status, clockMs(options.now));
|
|
1443
|
+
if (result.kind !== "ok") throw new Error(`Could not refresh the session: ${result.error ?? "unknown"}`);
|
|
1444
|
+
return result;
|
|
1445
|
+
}
|
|
1446
|
+
function classifyTokenResponse(body, status, nowMs) {
|
|
1447
|
+
const error = typeof body?.error === "string" ? body.error : "";
|
|
1448
|
+
if (error === "authorization_pending") return { kind: "pending" };
|
|
1449
|
+
if (error === "slow_down") return { kind: "slow_down" };
|
|
1450
|
+
if (error) return { kind: "stop", error: oauthError(body, status) };
|
|
1451
|
+
const accessToken = typeof body?.access_token === "string" ? body.access_token : "";
|
|
1452
|
+
if (!accessToken || status < 200 || status >= 300) {
|
|
1453
|
+
return { kind: "stop", error: oauthError(body, status) };
|
|
1454
|
+
}
|
|
1455
|
+
const refreshToken = typeof body?.refresh_token === "string" && body.refresh_token ? body.refresh_token : void 0;
|
|
1456
|
+
return {
|
|
1457
|
+
kind: "ok",
|
|
1458
|
+
accessToken,
|
|
1459
|
+
refreshToken,
|
|
1460
|
+
expiresIn: typeof body?.expires_in === "number" ? body.expires_in : void 0,
|
|
1461
|
+
expiresAt: expiresAtFrom(body, accessToken, nowMs),
|
|
1462
|
+
issuer: XAI_OAUTH_ISSUER,
|
|
1463
|
+
clientId: GROK_OIDC_CLIENT_ID
|
|
1464
|
+
};
|
|
1185
1465
|
}
|
|
1466
|
+
|
|
1467
|
+
// src/session.js
|
|
1186
1468
|
function openExternal(url, options = {}) {
|
|
1187
1469
|
const spawnFn = options.spawn ?? spawn;
|
|
1188
1470
|
const platform = options.platform ?? process.platform;
|
|
@@ -1206,127 +1488,59 @@ function openExternal(url, options = {}) {
|
|
|
1206
1488
|
child.unref?.();
|
|
1207
1489
|
});
|
|
1208
1490
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
-
});
|
|
1491
|
+
function defaultSleep(ms) {
|
|
1492
|
+
return new Promise((resolve) => {
|
|
1493
|
+
const timer = setTimeout(resolve, ms);
|
|
1494
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1283
1495
|
});
|
|
1284
1496
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
const
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
-
});
|
|
1497
|
+
function issuerOf(session) {
|
|
1498
|
+
if (typeof session?.oidcIssuer === "string" && session.oidcIssuer.startsWith("https://")) return session.oidcIssuer;
|
|
1499
|
+
const scope = typeof session?.scope === "string" ? session.scope : "";
|
|
1500
|
+
const issuer = scope.split("::")[0];
|
|
1501
|
+
if (issuer?.startsWith("https://")) return issuer;
|
|
1502
|
+
return XAI_OAUTH_ISSUER;
|
|
1503
|
+
}
|
|
1504
|
+
function clientIdOf(session) {
|
|
1505
|
+
if (typeof session?.oidcClientId === "string" && session.oidcClientId) return session.oidcClientId;
|
|
1506
|
+
const scope = typeof session?.scope === "string" ? session.scope : "";
|
|
1507
|
+
const clientId = scope.split("::")[1];
|
|
1508
|
+
return clientId || GROK_OIDC_CLIENT_ID;
|
|
1323
1509
|
}
|
|
1324
1510
|
async function defaultRenewSession(options = {}) {
|
|
1325
1511
|
const env = options.env ?? process.env;
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1512
|
+
const path = options.authPath ?? authJsonPath(env);
|
|
1513
|
+
const read = options.readAuth ?? (() => readGrokAuthSession(path));
|
|
1514
|
+
let parsed;
|
|
1515
|
+
try {
|
|
1516
|
+
parsed = read();
|
|
1517
|
+
} catch {
|
|
1518
|
+
return void 0;
|
|
1519
|
+
}
|
|
1520
|
+
const current = parsed?.session;
|
|
1521
|
+
if (!current?.refreshToken) return void 0;
|
|
1522
|
+
const issuer = issuerOf(current);
|
|
1523
|
+
const clientId = clientIdOf(current);
|
|
1524
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
1525
|
+
const endpoints = options.endpoints ?? await discoverOauthEndpoints(issuer, { fetch: fetchImpl });
|
|
1526
|
+
const nowMs = typeof options.now === "function" ? options.now() : Date.now();
|
|
1527
|
+
const tokens = await refreshOauthToken({
|
|
1528
|
+
endpoint: endpoints.tokenEndpoint,
|
|
1529
|
+
clientId,
|
|
1530
|
+
refreshToken: current.refreshToken,
|
|
1531
|
+
fetch: fetchImpl,
|
|
1532
|
+
now: () => nowMs
|
|
1533
|
+
});
|
|
1534
|
+
const written = writeOauthSession(path, {
|
|
1535
|
+
issuer,
|
|
1536
|
+
clientId,
|
|
1537
|
+
accessToken: tokens.accessToken,
|
|
1538
|
+
refreshToken: tokens.refreshToken || current.refreshToken,
|
|
1539
|
+
expiresAt: tokens.expiresAt ?? current.expiresAt,
|
|
1540
|
+
email: current.email,
|
|
1541
|
+
userId: current.userId
|
|
1542
|
+
});
|
|
1543
|
+
return written.session;
|
|
1330
1544
|
}
|
|
1331
1545
|
async function defaultCredentialRefOf() {
|
|
1332
1546
|
try {
|
|
@@ -1363,9 +1577,21 @@ function createSessionService({
|
|
|
1363
1577
|
storeTokenTimeoutMs = STORE_TOKEN_TIMEOUT_MS,
|
|
1364
1578
|
credentialsIoTimeoutMs = CREDENTIALS_IO_TIMEOUT_MS,
|
|
1365
1579
|
credentialRefOf = defaultCredentialRefOf,
|
|
1366
|
-
renewSession
|
|
1580
|
+
renewSession,
|
|
1581
|
+
fetch: fetchImpl,
|
|
1582
|
+
authPath,
|
|
1583
|
+
env,
|
|
1584
|
+
oauthEndpoints,
|
|
1367
1585
|
now = () => Date.now()
|
|
1368
1586
|
} = {}) {
|
|
1587
|
+
const renew = renewSession ?? (() => defaultRenewSession({
|
|
1588
|
+
readAuth,
|
|
1589
|
+
fetch: fetchImpl,
|
|
1590
|
+
authPath,
|
|
1591
|
+
env,
|
|
1592
|
+
endpoints: oauthEndpoints,
|
|
1593
|
+
now
|
|
1594
|
+
}));
|
|
1369
1595
|
let catalog = { models: [], source: "signed-out", error: void 0 };
|
|
1370
1596
|
let lastPublic = publicSessionView(void 0);
|
|
1371
1597
|
let lastUsage = unavailable("Not fetched yet");
|
|
@@ -1404,7 +1630,7 @@ function createSessionService({
|
|
|
1404
1630
|
if (renewalInFlight) return renewalInFlight;
|
|
1405
1631
|
renewalInFlight = (async () => {
|
|
1406
1632
|
try {
|
|
1407
|
-
const session = await
|
|
1633
|
+
const session = await renew();
|
|
1408
1634
|
const token = adoptSession(session);
|
|
1409
1635
|
if (token) notifyCatalogChange();
|
|
1410
1636
|
return token;
|
|
@@ -1544,7 +1770,7 @@ function createSessionService({
|
|
|
1544
1770
|
);
|
|
1545
1771
|
});
|
|
1546
1772
|
});
|
|
1547
|
-
const message = parsed.reason === "api-key-only" ? "Found an API-key entry only. Sign in with SuperGrok / X Premium
|
|
1773
|
+
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
1774
|
notifyCatalogChange();
|
|
1549
1775
|
return { ok: false, error: message, account: lastPublic, catalog, usage: lastUsage };
|
|
1550
1776
|
}
|
|
@@ -1566,15 +1792,16 @@ function createSessionService({
|
|
|
1566
1792
|
kickBackgroundUsageRefresh();
|
|
1567
1793
|
return { ok: true, account: lastPublic, catalog, usage: lastUsage };
|
|
1568
1794
|
};
|
|
1795
|
+
const snapshot = () => ({ account: lastPublic, catalog, usage: lastUsage, authPath: authPath ?? authJsonPath(env) });
|
|
1569
1796
|
const status = async () => {
|
|
1570
1797
|
if (typeof memoryAccessToken === "string" && memoryAccessToken.length > 0 || lastPublic.signedIn === true) {
|
|
1571
|
-
return
|
|
1798
|
+
return snapshot();
|
|
1572
1799
|
}
|
|
1573
1800
|
try {
|
|
1574
1801
|
const parsed = readAuth();
|
|
1575
1802
|
if (parsed.session) {
|
|
1576
1803
|
adoptSession(parsed.session);
|
|
1577
|
-
return
|
|
1804
|
+
return snapshot();
|
|
1578
1805
|
}
|
|
1579
1806
|
} catch (error) {
|
|
1580
1807
|
logger?.warn?.(
|
|
@@ -1593,36 +1820,95 @@ function createSessionService({
|
|
|
1593
1820
|
lastPublic = publicSessionView(void 0);
|
|
1594
1821
|
catalog = { models: [], source: "signed-out", error: void 0 };
|
|
1595
1822
|
lastUsage = unavailable("Not signed in");
|
|
1596
|
-
return
|
|
1823
|
+
return snapshot();
|
|
1597
1824
|
}
|
|
1598
1825
|
memoryAccessToken = token;
|
|
1599
1826
|
lastPublic = Object.freeze({ signedIn: true, maskedAccount: "\u2022\u2022\u2022\u2022", authMode: "oidc", source: "dsh-credentials" });
|
|
1600
|
-
return
|
|
1827
|
+
return snapshot();
|
|
1601
1828
|
};
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
};
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
);
|
|
1618
|
-
});
|
|
1619
|
-
});
|
|
1829
|
+
let loginEpoch = 0;
|
|
1830
|
+
const login = async (loginOptions) => {
|
|
1831
|
+
const epoch = ++loginEpoch;
|
|
1832
|
+
const fetchLogin = loginOptions?.fetch ?? fetchImpl ?? fetch;
|
|
1833
|
+
const path = loginOptions?.authPath ?? authPath ?? authJsonPath(env);
|
|
1834
|
+
const sleep = loginOptions?.sleep ?? defaultSleep;
|
|
1835
|
+
const openUrl = loginOptions?.openUrl ?? ((url) => openExternal(url, { spawn: loginOptions?.openSpawn }));
|
|
1836
|
+
const clock = () => typeof loginOptions?.now === "function" ? loginOptions.now() : now();
|
|
1837
|
+
let endpoints;
|
|
1838
|
+
let device;
|
|
1839
|
+
try {
|
|
1840
|
+
endpoints = loginOptions?.endpoints ?? oauthEndpoints ?? await discoverOauthEndpoints(XAI_OAUTH_ISSUER, { fetch: fetchLogin });
|
|
1841
|
+
device = await requestDeviceAuthorization({
|
|
1842
|
+
endpoint: endpoints.deviceAuthorizationEndpoint,
|
|
1843
|
+
fetch: fetchLogin
|
|
1620
1844
|
});
|
|
1621
|
-
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
const message = error instanceof Error ? error.message : "Could not start sign-in";
|
|
1847
|
+
return { ok: false, error: message, ...await status() };
|
|
1622
1848
|
}
|
|
1623
|
-
|
|
1849
|
+
const loginUrl = device.verificationUriComplete || device.verificationUri;
|
|
1850
|
+
if (!isXaiHttpsUrl(loginUrl) || !device.userCode || !device.deviceCode) {
|
|
1851
|
+
return { ok: false, error: "Sign-in did not return a verification link.", ...await status() };
|
|
1852
|
+
}
|
|
1853
|
+
try {
|
|
1854
|
+
void openUrl(loginUrl);
|
|
1855
|
+
} catch {
|
|
1856
|
+
}
|
|
1857
|
+
const deadline = clock() + (typeof device.expiresIn === "number" && device.expiresIn > 0 ? device.expiresIn : 900) * 1e3;
|
|
1858
|
+
let interval = Number.isFinite(device.interval) && device.interval >= 0 ? device.interval : 5;
|
|
1859
|
+
const poll = async () => {
|
|
1860
|
+
try {
|
|
1861
|
+
while (epoch === loginEpoch) {
|
|
1862
|
+
if (clock() >= deadline) {
|
|
1863
|
+
logger?.warn?.("Grok subscription sign-in timed out before authorization");
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
await sleep(interval * 1e3);
|
|
1867
|
+
if (epoch !== loginEpoch) return;
|
|
1868
|
+
const result = await exchangeDeviceCode({
|
|
1869
|
+
endpoint: endpoints.tokenEndpoint,
|
|
1870
|
+
deviceCode: device.deviceCode,
|
|
1871
|
+
fetch: fetchLogin,
|
|
1872
|
+
now: clock
|
|
1873
|
+
});
|
|
1874
|
+
if (epoch !== loginEpoch) return;
|
|
1875
|
+
if (result.kind === "pending") continue;
|
|
1876
|
+
if (result.kind === "slow_down") {
|
|
1877
|
+
interval += 5;
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
if (result.kind !== "ok") {
|
|
1881
|
+
logger?.warn?.("Grok subscription sign-in stopped: %s", result.error ?? "unknown");
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
writeOauthSession(path, {
|
|
1885
|
+
issuer: result.issuer,
|
|
1886
|
+
clientId: result.clientId,
|
|
1887
|
+
accessToken: result.accessToken,
|
|
1888
|
+
refreshToken: result.refreshToken,
|
|
1889
|
+
expiresAt: result.expiresAt
|
|
1890
|
+
});
|
|
1891
|
+
if (epoch !== loginEpoch) return;
|
|
1892
|
+
await pull();
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
logger?.warn?.(
|
|
1897
|
+
"Grok subscription sign-in failed: %s",
|
|
1898
|
+
error instanceof Error ? error.message : "unknown"
|
|
1899
|
+
);
|
|
1900
|
+
} finally {
|
|
1901
|
+
try {
|
|
1902
|
+
loginOptions?.onSettled?.();
|
|
1903
|
+
} catch {
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
void poll();
|
|
1908
|
+
return { ok: true, pending: true, loginUrl, userCode: device.userCode, ...await status() };
|
|
1624
1909
|
};
|
|
1625
1910
|
const logout = async () => {
|
|
1911
|
+
loginEpoch += 1;
|
|
1626
1912
|
memoryAccessToken = void 0;
|
|
1627
1913
|
memorySessionTouched = true;
|
|
1628
1914
|
await clearToken().catch((error) => {
|
|
@@ -1645,7 +1931,7 @@ function createSessionService({
|
|
|
1645
1931
|
login,
|
|
1646
1932
|
logout,
|
|
1647
1933
|
currentToken: readStoredToken,
|
|
1648
|
-
/** Force a
|
|
1934
|
+
/** Force a refresh-token renewal; used when the provider answers 401. */
|
|
1649
1935
|
refreshToken: () => renewAccessToken(),
|
|
1650
1936
|
models: () => catalog.models,
|
|
1651
1937
|
catalog: () => catalog,
|
|
@@ -1656,10 +1942,10 @@ function createSessionService({
|
|
|
1656
1942
|
|
|
1657
1943
|
// src/plugin-version.js
|
|
1658
1944
|
import { spawn as spawn2 } from "node:child_process";
|
|
1659
|
-
import { existsSync as
|
|
1945
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1660
1946
|
import { readdir, readFile, realpath } from "node:fs/promises";
|
|
1661
1947
|
import { homedir as homedir3 } from "node:os";
|
|
1662
|
-
import { join as
|
|
1948
|
+
import { join as join3 } from "node:path";
|
|
1663
1949
|
import { fileURLToPath } from "node:url";
|
|
1664
1950
|
var PACKAGE_NAME = PLUGIN_ID;
|
|
1665
1951
|
var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -1703,7 +1989,7 @@ var readManifest = async (path) => {
|
|
|
1703
1989
|
};
|
|
1704
1990
|
async function confirmInstalled({ profileDir, expected, settleDelayMs, attempts = 3 }) {
|
|
1705
1991
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1706
|
-
const installed = (await readManifest(
|
|
1992
|
+
const installed = (await readManifest(join3(profileDir, "node_modules", PACKAGE_NAME, "package.json")))?.version;
|
|
1707
1993
|
if (installed === expected) return;
|
|
1708
1994
|
if (attempt < attempts) await new Promise((resolve) => {
|
|
1709
1995
|
setTimeout(resolve, settleDelayMs);
|
|
@@ -1720,20 +2006,20 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
|
1720
2006
|
}
|
|
1721
2007
|
let entries = [];
|
|
1722
2008
|
try {
|
|
1723
|
-
entries = await readdir(
|
|
2009
|
+
entries = await readdir(join3(dshHome, "profiles"), { withFileTypes: true });
|
|
1724
2010
|
} catch {
|
|
1725
2011
|
entries = [];
|
|
1726
2012
|
}
|
|
1727
2013
|
const declared = [];
|
|
1728
2014
|
for (const entry of entries) {
|
|
1729
2015
|
if (!entry.isDirectory()) continue;
|
|
1730
|
-
const profileDir =
|
|
1731
|
-
const spec = (await readManifest(
|
|
2016
|
+
const profileDir = join3(dshHome, "profiles", entry.name);
|
|
2017
|
+
const spec = (await readManifest(join3(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
|
|
1732
2018
|
if (typeof spec !== "string") continue;
|
|
1733
2019
|
declared.push({ profile: entry.name, spec });
|
|
1734
2020
|
if (ownReal === void 0) continue;
|
|
1735
2021
|
try {
|
|
1736
|
-
const installed = await realpath(
|
|
2022
|
+
const installed = await realpath(join3(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
|
|
1737
2023
|
if (installed === ownReal) return { kind: classifySpec(spec), profile: entry.name };
|
|
1738
2024
|
} catch {
|
|
1739
2025
|
}
|
|
@@ -1741,9 +2027,9 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
|
1741
2027
|
if (declared.length === 1) return { kind: classifySpec(declared[0].spec), profile: declared[0].profile };
|
|
1742
2028
|
return { kind: "unknown" };
|
|
1743
2029
|
}
|
|
1744
|
-
var isExistingPath = (value) => typeof value === "string" && value !== "" &&
|
|
2030
|
+
var isExistingPath = (value) => typeof value === "string" && value !== "" && existsSync3(value);
|
|
1745
2031
|
var PNPM_SCRIPT = /[\\/]pnpm\.(?:mjs|cjs|js)$/u;
|
|
1746
|
-
var dshRuntimeRoot = (value) => isExistingPath(value) &&
|
|
2032
|
+
var dshRuntimeRoot = (value) => isExistingPath(value) && existsSync3(join3(value, "node_modules", "@deepseek-ai", "dsh", "package.json")) ? value : void 0;
|
|
1747
2033
|
function resolvePnpm({ argv } = {}) {
|
|
1748
2034
|
const entries = Array.isArray(argv) ? argv : [];
|
|
1749
2035
|
for (const entry of entries) {
|
|
@@ -1752,8 +2038,8 @@ function resolvePnpm({ argv } = {}) {
|
|
|
1752
2038
|
for (const entry of entries) {
|
|
1753
2039
|
const root = dshRuntimeRoot(entry);
|
|
1754
2040
|
if (root === void 0) continue;
|
|
1755
|
-
const script =
|
|
1756
|
-
if (
|
|
2041
|
+
const script = join3(root, "..", "pnpm", "bin", "pnpm.mjs");
|
|
2042
|
+
if (existsSync3(script)) return script;
|
|
1757
2043
|
}
|
|
1758
2044
|
return void 0;
|
|
1759
2045
|
}
|
|
@@ -1826,15 +2112,15 @@ function createGrokPluginManager({
|
|
|
1826
2112
|
} = {}) {
|
|
1827
2113
|
if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
|
|
1828
2114
|
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 :
|
|
2115
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join3(homedir3(), ".dsh");
|
|
1830
2116
|
const launcher = resolveLauncher({ argv, execPath });
|
|
1831
2117
|
let cached;
|
|
1832
2118
|
let inFlight;
|
|
1833
2119
|
let generation = 0;
|
|
1834
2120
|
let updating = false;
|
|
1835
2121
|
const fetchLatest = async (signal) => {
|
|
1836
|
-
const
|
|
1837
|
-
const requestSignal = signal === void 0 ?
|
|
2122
|
+
const timeoutSignal2 = AbortSignal.timeout(timeoutMs);
|
|
2123
|
+
const requestSignal = signal === void 0 ? timeoutSignal2 : AbortSignal.any([signal, timeoutSignal2]);
|
|
1838
2124
|
let response;
|
|
1839
2125
|
try {
|
|
1840
2126
|
response = await fetchImpl(registryUrl, {
|
|
@@ -1916,7 +2202,7 @@ function createGrokPluginManager({
|
|
|
1916
2202
|
throw new Error("Grok plugin update could not find the owning profile");
|
|
1917
2203
|
}
|
|
1918
2204
|
const latest = await fetchLatest(signal);
|
|
1919
|
-
const profileDir =
|
|
2205
|
+
const profileDir = join3(dshHome, "profiles", install.profile);
|
|
1920
2206
|
let result;
|
|
1921
2207
|
try {
|
|
1922
2208
|
result = await runCommand({
|
|
@@ -1982,7 +2268,10 @@ function stripSecrets(value) {
|
|
|
1982
2268
|
delete next.accessToken;
|
|
1983
2269
|
delete next.token;
|
|
1984
2270
|
delete next.refresh;
|
|
2271
|
+
delete next.refreshToken;
|
|
1985
2272
|
delete next.refresh_token;
|
|
2273
|
+
delete next.deviceCode;
|
|
2274
|
+
delete next.device_code;
|
|
1986
2275
|
delete next.key;
|
|
1987
2276
|
if (next.catalog) {
|
|
1988
2277
|
next.catalog = {
|