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/CHANGELOG.md +6 -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 +475 -200
- 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({
|
|
@@ -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 =
|
|
854
|
-
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++) {
|
|
855
954
|
push(join2(dir, "node_modules"));
|
|
856
|
-
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(
|
|
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/
|
|
1153
|
-
var
|
|
1154
|
-
var
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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
|
|
1166
|
-
|
|
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
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
|
1175
|
-
|
|
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
|
|
1180
|
-
const
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
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
|
-
});
|
|
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
|
-
|
|
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
|
-
});
|
|
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
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1813
|
+
return snapshot();
|
|
1601
1814
|
};
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
};
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
1731
|
-
const spec = (await readManifest(
|
|
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(
|
|
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 !== "" &&
|
|
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) &&
|
|
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 =
|
|
1756
|
-
if (
|
|
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 :
|
|
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
|
|
1837
|
-
const requestSignal = signal === void 0 ?
|
|
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 =
|
|
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 = {
|