dsh-grok-subscription 1.0.11 → 1.0.19

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/README.en.md CHANGED
@@ -31,7 +31,7 @@ Models, reasoning effort, and weekly quota all stay inside DSH.
31
31
  dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
32
32
  ```
33
33
 
34
- 2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login**, then finish the official prompt in the terminal that started DSH. If you already ran `grok login` yourself, just click **Pull from Grok CLI** — nothing has to be pasted.
34
+ 2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login**. The plugin runs the official `grok login`, hands the link the CLI prints to your browser, and shows the one-time code plus a clickable link in the panel; the session syncs itself once you authorize. If you already ran `grok login` yourself, just click **Pull from Grok CLI** — nothing has to be pasted.
35
35
  3. **Start using it.** Pick a model such as Grok 4.7 in the model picker. The round badge next to the picker shows weekly remaining quota, and the model menu offers reasoning effort levels.
36
36
 
37
37
  Restart `dsh web` after installing or upgrading, otherwise the old `lib/` keeps running.
@@ -209,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
209
209
 
210
210
  - **`dsh` is not recognized**: the official npm route never creates a global `dsh` command — use the full `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` command above;
211
211
  - **The model list is empty**: nothing is exposed while signed out. Finish signing in, then click **Pull from Grok CLI**;
212
+ - **Sign-in opens no browser / chat fails with `fetch failed` / usage says "Billing request timed out"**: all three have one cause — `accounts.x.ai` and `cli-chat-proxy.grok.com` need a tunnel on some networks. Add `GROK_PROXY=http://127.0.0.1:10808` to `~/.dsh/.env`: it applies only to **this plugin's own Grok requests** (the host undici's `fetch`; the process-wide dispatcher is never replaced) and to the **`grok` CLI** it spawns. Every other plugin, DSH itself, and every other provider keep the host's original `fetch`. To proxy the whole Harness, set the standard `https_proxy` (in `~/.dsh/.env`, never in a project `.env`): `@deepseek-ai/dsh-http-proxy` takes over and this plugin adds no tunnel of its own. Restart the client afterwards; the sign-in link is handed to your browser and the panel also shows the one-time code and a clickable link.
212
213
  - **Nothing changed after upgrading**: the plugin keeps running the old `lib/` — reinstall the plugin, restart `dsh web`, and hard-refresh (Ctrl+Shift+R);
213
214
  - **It reports `auth.json` permissions**: apply the `chmod 600` above; the plugin refuses symlinks and group- or other-readable files;
214
215
  - **"No reply" after sending a message**: upgrade to `1.0.1` or later. `1.0.0` had a defect where tool-calling turns aborted with a non-serializable stream chunk, leaving no reply in the UI at all.
package/README.md CHANGED
@@ -31,7 +31,7 @@
31
31
  dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
32
32
  ```
33
33
 
34
- 2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或 **设备码登录**,在启动 DSH 的终端里完成官方提示。已经自己跑过 `grok login` 的话,直接点 **从 Grok CLI 拉取** 即可,不需要粘贴任何 token。
34
+ 2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或 **设备码登录**。插件会运行官方 `grok login`,把 CLI 打印的登录链接交给浏览器,并在面板里显示一次性验证码与可点的链接;授权完成后自动同步。已经自己跑过 `grok login` 的话,直接点 **从 Grok CLI 拉取** 即可,不需要粘贴任何 token。
35
35
  3. **开始使用**:在模型选择器中选择 Grok 4.7 等模型。输入框旁的圆形徽章显示每周剩余额度,模型菜单里有推理档位。
36
36
 
37
37
  安装或升级后需要重启 `dsh web`,否则会继续运行旧的 `lib/`。
@@ -209,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
209
209
 
210
210
  - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` 命令;
211
211
  - **模型列表是空的**:未登录时插件不暴露任何模型。先完成登录,再点 **从 Grok CLI 拉取**;
212
+ - **登录不弹浏览器 / 聊天报 `fetch failed` / 用量「Billing request timed out」**:这三件事都指向同一个原因——`accounts.x.ai` 与 `cli-chat-proxy.grok.com` 在部分网络里必须走隧道。在 `~/.dsh/.env` 写一行 `GROK_PROXY=http://127.0.0.1:10808` 即可:它只作用于**本插件自己的 Grok 请求**(走宿主 undici 的 fetch,不替换进程的全局调度器)和它启动的 **`grok` CLI**。其他插件、DSH 自身和其他 provider 继续用宿主原来的 `fetch`。想让整个 Harness 走代理时,用标准的 `https_proxy`(可写在 `~/.dsh/.env`,不要写进项目目录),宿主的 `@deepseek-ai/dsh-http-proxy` 会接管,本插件不再另加隧道。写好后重启客户端;登录链接会交给系统浏览器打开,面板里同时显示一次性验证码与可点链接。
212
213
  - **升级后界面没变化**:插件会继续运行旧的 `lib/`,请重新安装插件并重启 `dsh web`,然后硬刷新(Ctrl+Shift+R);
213
214
  - **提示 `auth.json` 权限不正确**:按上面的 `chmod 600` 处理;插件拒绝读取符号链接或组/其他用户可读的文件;
214
215
  - **出现「没有回复」**:请升级到 `1.0.1` 或更高版本。`1.0.0` 存在一个缺陷:工具调用轮次会因流片段无法无损序列化而整轮中止,界面上完全没有回复。
package/lib/client.js CHANGED
@@ -39,7 +39,7 @@ var import_react2 = require("react");
39
39
  var zh = {
40
40
  nav: "Grok \u8BA2\u9605",
41
41
  title: "Grok \u8BA2\u9605",
42
- subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.11",
42
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.19",
43
43
  signedIn: "\u5DF2\u767B\u5F55",
44
44
  signedOut: "\u672A\u767B\u5F55",
45
45
  account: "\u8D26\u6237",
@@ -54,8 +54,12 @@ var zh = {
54
54
  loginDevice: "\u8BBE\u5907\u7801\u767B\u5F55",
55
55
  pull: "\u4ECE Grok CLI \u62C9\u53D6",
56
56
  logout: "\u9000\u51FA\u767B\u5F55",
57
- loginHint: "\u4F1A\u5728\u542F\u52A8 DSH \u7684\u7EC8\u7AEF\u4E2D\u8FD0\u884C grok login\u3002\u8BF7\u5728\u7EC8\u7AEF\u6216\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u6388\u6743\uFF0C\u7136\u540E\u7B49\u5F85\u540C\u6B65\u3002",
58
- deviceHint: "\u8BBE\u5907\u7801\u63D0\u793A\u4F1A\u51FA\u73B0\u5728 DSH \u7EC8\u7AEF\u3002\u4E5F\u53EF\u624B\u52A8\u8FD0\u884C grok login --device-auth\uFF0C\u5B8C\u6210\u540E\u70B9\u201C\u4ECE Grok CLI \u62C9\u53D6\u201D\u3002",
57
+ loginHint: "\u70B9\u51FB\u540E\u5728\u672C\u673A\u8FD0\u884C grok login\uFF0C\u5E76\u628A CLI \u6253\u5370\u7684\u767B\u5F55\u94FE\u63A5\u4EA4\u7ED9\u6D4F\u89C8\u5668\uFF1B\u6388\u6743\u5B8C\u6210\u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u540C\u6B65\u3002",
58
+ deviceHint: "\u8BBE\u5907\u7801\u767B\u5F55\u4F1A\u8BA9 CLI \u6253\u5370\u4E00\u6B21\u6027\u9A8C\u8BC1\u7801\u548C\u94FE\u63A5\u3002\u82E5\u6D4F\u89C8\u5668\u6CA1\u6709\u81EA\u52A8\u6253\u5F00\uFF0C\u70B9\u63D0\u793A\u91CC\u7684\u201C\u6253\u5F00\u767B\u5F55\u9875\u9762\u201D\u5E76\u786E\u8BA4\u9A8C\u8BC1\u7801\u3002",
59
+ loginWaiting: "\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u2026",
60
+ loginOpenBrowser: "\u6253\u5F00\u767B\u5F55\u9875\u9762",
61
+ loginNoUrl: "\u6CA1\u62FF\u5230\u767B\u5F55\u94FE\u63A5\uFF1A\u8BF7\u786E\u8BA4 grok CLI \u80FD\u8BBF\u95EE x.ai\uFF08\u684C\u9762\u7248\u901A\u5E38\u9700\u8981\u4EE3\u7406\uFF09\uFF0C\u7136\u540E\u91CD\u8BD5\u3002",
62
+ loginTimeout: "\u7B49\u5F85\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u70B9\u51FB\u767B\u5F55\u3002",
59
63
  pullHint: "\u5B89\u5168\u8BFB\u53D6 ~/.grok/auth.json\uFF08\u62D2\u7EDD\u7B26\u53F7\u94FE\u63A5\u4E0E\u7EC4/\u5176\u4ED6\u4EBA\u53EF\u8BFB\uFF09\uFF0C\u53EA\u628A\u77ED\u671F access token \u5199\u5165 DSH\u3002",
60
64
  loginHelp: "\u767B\u5F55\u65B9\u5F0F\u8BF4\u660E",
61
65
  refreshCatalog: "\u5237\u65B0\u6A21\u578B\u76EE\u5F55",
@@ -115,7 +119,7 @@ var zh = {
115
119
  var en = {
116
120
  nav: "Grok Subscription",
117
121
  title: "Grok Subscription",
118
- subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.11",
122
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.19",
119
123
  signedIn: "Signed in",
120
124
  signedOut: "Signed out",
121
125
  account: "Account",
@@ -130,8 +134,12 @@ var en = {
130
134
  loginDevice: "Device-code login",
131
135
  pull: "Pull from Grok CLI",
132
136
  logout: "Log out",
133
- loginHint: "Runs grok login in the terminal that started DSH. Finish the browser or CLI prompt there, then wait for sync.",
134
- deviceHint: "The device-code prompt appears in the DSH terminal. You can also run grok login --device-auth yourself, then Pull from Grok CLI.",
137
+ loginHint: "Runs grok login on this machine and hands the link the CLI prints to your browser; the panel syncs itself once you authorize.",
138
+ deviceHint: "Device-code login makes the CLI print a one-time code and link. If no browser opened, use \u201COpen the sign-in page\u201D in the status line and confirm the code.",
139
+ loginWaiting: "Waiting for browser authorization\u2026",
140
+ loginOpenBrowser: "Open the sign-in page",
141
+ loginNoUrl: "No sign-in link was returned: make sure the grok CLI can reach x.ai (the desktop app usually needs a proxy), then retry.",
142
+ loginTimeout: "Timed out waiting for authorization; click sign-in again.",
135
143
  pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files) and stores only the short-lived access token in DSH.",
136
144
  loginHelp: "How signing in works",
137
145
  refreshCatalog: "Refresh model catalog",
@@ -640,12 +648,14 @@ var inject = [
640
648
  "slots",
641
649
  "locale",
642
650
  "connection",
643
- "settingsScope",
644
651
  "modelDirectories",
645
652
  "sessions",
646
653
  "remote"
647
654
  ];
648
655
  var RPC_CALL_TIMEOUT_MS = 12e3;
656
+ var LOGIN_CALL_TIMEOUT_MS = 3e4;
657
+ var LOGIN_POLL_INTERVAL_MS = 3e3;
658
+ var LOGIN_POLL_TIMEOUT_MS = 10 * 6e4;
649
659
  function pickCopy(locale) {
650
660
  const language = typeof locale === "string" ? locale : locale?.language ?? locale?.lang;
651
661
  const resolved = language ?? (typeof navigator !== "undefined" ? navigator.language : "zh-CN");
@@ -672,7 +682,7 @@ async function callRpc(rpc, endpoint, payload = {}, timeoutMs = RPC_CALL_TIMEOUT
672
682
  controller?.abort();
673
683
  } catch {
674
684
  }
675
- reject(new Error(`Request timed out after ${RPC_CALL_TIMEOUT_MS}ms`));
685
+ reject(new Error(`Request timed out after ${timeoutMs}ms`));
676
686
  }, timeoutMs);
677
687
  });
678
688
  return unwrap(await Promise.race([call, timeout]));
@@ -876,6 +886,7 @@ function GrokSubscriptionSection({ rpc, t }) {
876
886
  const [error, setError] = (0, import_react2.useState)("");
877
887
  const [notice, setNotice] = (0, import_react2.useState)("");
878
888
  const [status, setStatus] = (0, import_react2.useState)(void 0);
889
+ const [loginPending, setLoginPending] = (0, import_react2.useState)(void 0);
879
890
  const initialUsageKick = (0, import_react2.useRef)(false);
880
891
  const applyPartial = (value) => {
881
892
  if (value?.account || value?.catalog || value?.usage) {
@@ -914,8 +925,29 @@ function GrokSubscriptionSection({ rpc, t }) {
914
925
  }).catch(() => {
915
926
  });
916
927
  };
928
+ (0, import_react2.useEffect)(() => {
929
+ if (!loginPending) return void 0;
930
+ const startedAt = Date.now();
931
+ const timer = setInterval(() => {
932
+ if (Date.now() - startedAt > LOGIN_POLL_TIMEOUT_MS) {
933
+ setLoginPending(void 0);
934
+ setError(t("loginTimeout"));
935
+ return;
936
+ }
937
+ void callRpc(rpc, "status", {}).then((value) => {
938
+ if (value?.account || value?.catalog || value?.usage) applyPartial(value);
939
+ if (value?.account?.signedIn !== true) return;
940
+ setLoginPending(void 0);
941
+ setNotice(t("loginOk"));
942
+ kickFollowUpRefresh();
943
+ }).catch(() => {
944
+ });
945
+ }, LOGIN_POLL_INTERVAL_MS);
946
+ return () => clearInterval(timer);
947
+ }, [loginPending, rpc]);
917
948
  const run = async (endpoint) => {
918
949
  const isUsageRefresh = endpoint === "usage/refresh";
950
+ const isLogin = endpoint === "login/cli" || endpoint === "login/device";
919
951
  if (isUsageRefresh) {
920
952
  setUsageBusy(true);
921
953
  } else {
@@ -925,7 +957,7 @@ function GrokSubscriptionSection({ rpc, t }) {
925
957
  setNotice("");
926
958
  let value;
927
959
  try {
928
- value = await callRpc(rpc, endpoint, {});
960
+ value = await callRpc(rpc, endpoint, {}, isLogin ? LOGIN_CALL_TIMEOUT_MS : RPC_CALL_TIMEOUT_MS);
929
961
  if (value?.account || value?.catalog || value?.usage) {
930
962
  applyPartial(value);
931
963
  } else {
@@ -934,6 +966,9 @@ function GrokSubscriptionSection({ rpc, t }) {
934
966
  if (value?.ok === false && value.error) {
935
967
  setNotice("");
936
968
  setError(value.error);
969
+ } else if (value?.pending === true) {
970
+ setLoginPending({ url: value.loginUrl, code: value.userCode, warning: value.warning });
971
+ setNotice("");
937
972
  } else if (value?.ok !== false) {
938
973
  const successKey = {
939
974
  pull: "pullOk",
@@ -955,7 +990,7 @@ function GrokSubscriptionSection({ rpc, t }) {
955
990
  if (isUsageRefresh) setUsageBusy(false);
956
991
  else setBusy("");
957
992
  }
958
- const shouldFollowUp = (endpoint === "pull" || endpoint === "login/cli" || endpoint === "login/device") && value?.ok !== false && !value?.error;
993
+ const shouldFollowUp = (endpoint === "pull" || endpoint === "login/cli" || endpoint === "login/device") && value?.ok !== false && !value?.error && value?.pending !== true;
959
994
  if (shouldFollowUp) kickFollowUpRefresh();
960
995
  };
961
996
  const account = status?.account;
@@ -975,12 +1010,24 @@ function GrokSubscriptionSection({ rpc, t }) {
975
1010
  signedIn && account?.maskedAccount ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gsAccount", children: account.maskedAccount }) : null
976
1011
  ] }),
977
1012
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsActions", children: [
978
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy), onClick: () => void run("login/cli"), children: t("loginCli") }),
979
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("login/device"), children: t("loginDevice") }),
1013
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy) || Boolean(loginPending), onClick: () => void run("login/cli"), children: t("loginCli") }),
1014
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy) || Boolean(loginPending), onClick: () => void run("login/device"), children: t("loginDevice") }),
980
1015
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("pull"), children: t("pull") }),
981
1016
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--danger", type: "button", disabled: Boolean(busy) || !signedIn, onClick: () => void run("logout"), children: t("logout") })
982
1017
  ] }),
983
1018
  busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--busy", children: t("busy") }) : null,
1019
+ loginPending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--busy", children: [
1020
+ t("loginWaiting"),
1021
+ loginPending.code ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1022
+ " \xB7 ",
1023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: loginPending.code })
1024
+ ] }) : null,
1025
+ loginPending.url ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1026
+ " \xB7 ",
1027
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "gsLink", href: loginPending.url, target: "_blank", rel: "noreferrer", children: t("loginOpenBrowser") })
1028
+ ] }) : null
1029
+ ] }) : null,
1030
+ loginPending?.warning ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginNoUrl") }) : null,
984
1031
  notice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: notice }) : null,
985
1032
  error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--error", children: [
986
1033
  t("error"),
package/lib/index.js CHANGED
@@ -38,11 +38,13 @@ var IMAGE_INPUT_MODEL_IDS = Object.freeze([
38
38
  var IMPORT_TIMEOUT_MS = 2500;
39
39
  var CREDENTIALS_IO_TIMEOUT_MS = 1500;
40
40
  var RPC_HANDLER_TIMEOUT_MS = 8e3;
41
+ var LOGIN_START_TIMEOUT_MS = 1e4;
42
+ var LOGIN_HANDLER_TIMEOUT_MS = 2e4;
41
43
 
42
44
  // src/session.js
43
- import { existsSync as existsSync3 } from "node:fs";
45
+ import { existsSync as existsSync4 } from "node:fs";
44
46
  import { spawn } from "node:child_process";
45
- import { join as join3 } from "node:path";
47
+ import { join as join4 } from "node:path";
46
48
 
47
49
  // src/auth-file.js
48
50
  import { lstatSync, openSync, readFileSync, closeSync, constants as fsConstants } from "node:fs";
@@ -278,6 +280,131 @@ function buildProxyHeaders(accessToken, options = {}) {
278
280
  });
279
281
  }
280
282
 
283
+ // src/http-json.js
284
+ import { brotliDecompressSync, gunzipSync, inflateSync, zstdDecompressSync } from "node:zlib";
285
+ var MAGIC_GZIP = [31, 139];
286
+ var MAGIC_ZSTD = [40, 181, 47, 253];
287
+ var DEFAULT_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
288
+ function startsWith(buffer, bytes) {
289
+ if (buffer.length < bytes.length) return false;
290
+ return bytes.every((byte, index) => buffer[index] === byte);
291
+ }
292
+ function maxOutputOf(options) {
293
+ return typeof options.maxOutputBytes === "number" && options.maxOutputBytes > 0 ? options.maxOutputBytes : DEFAULT_MAX_OUTPUT_BYTES;
294
+ }
295
+ function decodeCompressedBody(buffer, encoding = "", options = {}) {
296
+ const declared = String(encoding ?? "").toLowerCase();
297
+ const kind = declared.includes("gzip") || startsWith(buffer, MAGIC_GZIP) ? "gzip" : declared.includes("zstd") || startsWith(buffer, MAGIC_ZSTD) ? "zstd" : declared.includes("deflate") ? "deflate" : declared.includes("br") ? "br" : void 0;
298
+ if (!kind) return buffer;
299
+ const limits = { maxOutputLength: maxOutputOf(options) };
300
+ try {
301
+ if (kind === "gzip") return gunzipSync(buffer, limits);
302
+ if (kind === "zstd") return zstdDecompressSync(buffer, limits);
303
+ if (kind === "deflate") return inflateSync(buffer, limits);
304
+ return brotliDecompressSync(buffer, limits);
305
+ } catch {
306
+ return buffer;
307
+ }
308
+ }
309
+ async function readResponseText(response, options = {}) {
310
+ if (typeof response.arrayBuffer !== "function") {
311
+ if (typeof response.text === "function") return response.text();
312
+ throw new TypeError("response exposes neither arrayBuffer() nor text()");
313
+ }
314
+ const buffer = Buffer.from(await response.arrayBuffer());
315
+ const encoding = typeof response.headers?.get === "function" ? response.headers.get("content-encoding") ?? "" : "";
316
+ return decodeCompressedBody(buffer, encoding, options).toString("utf8");
317
+ }
318
+ async function readResponseJson(response, options = {}) {
319
+ if (typeof response.arrayBuffer !== "function" && typeof response.json === "function") {
320
+ return response.json();
321
+ }
322
+ return JSON.parse(await readResponseText(response, options));
323
+ }
324
+
325
+ // src/proxy.js
326
+ import { existsSync as existsSync2 } from "node:fs";
327
+ import { dirname, join as join2 } from "node:path";
328
+ import { pathToFileURL } from "node:url";
329
+ var HTTP_PROXY_PATTERN = /^https?:\/\//iu;
330
+ var HOST_PROXY_PACKAGE = "@deepseek-ai/dsh-http-proxy";
331
+ var GROK_ORIGIN = "https://cli-chat-proxy.grok.com/";
332
+ var cached;
333
+ var dependencies = { undici: void 0, hostProxies: void 0, hostProxyEnv: void 0 };
334
+ function apiProxyUrl(env = process.env) {
335
+ const named = [env?.GROK_API_PROXY, env?.GROK_PROXY];
336
+ for (const candidate of named) {
337
+ if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
338
+ }
339
+ return void 0;
340
+ }
341
+ async function loadHostUndici() {
342
+ let dir = dirname(process.argv[1] ?? "");
343
+ for (let depth = 0; depth < 6 && dir && dir !== dirname(dir); depth++) {
344
+ const entry = join2(dir, "node_modules", "undici", "index.js");
345
+ if (existsSync2(entry)) {
346
+ try {
347
+ return await import(pathToFileURL(entry).href);
348
+ } catch {
349
+ break;
350
+ }
351
+ }
352
+ dir = dirname(dir);
353
+ }
354
+ try {
355
+ return await import("undici");
356
+ } catch {
357
+ return void 0;
358
+ }
359
+ }
360
+ async function hostProxyModule() {
361
+ try {
362
+ return await import(HOST_PROXY_PACKAGE);
363
+ } catch {
364
+ return void 0;
365
+ }
366
+ }
367
+ async function hostAlreadyProxiesGrok() {
368
+ if (typeof dependencies.hostProxies === "function") return dependencies.hostProxies();
369
+ const mod = await hostProxyModule();
370
+ try {
371
+ return mod?.proxyRouteFor?.(GROK_ORIGIN)?.proxied === true;
372
+ } catch {
373
+ return false;
374
+ }
375
+ }
376
+ var CHILD_PROXY_NAMES = ["http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"];
377
+ function proxyOverlay(value) {
378
+ if (!value || typeof value !== "object") return void 0;
379
+ return CHILD_PROXY_NAMES.some((name2) => typeof value[name2] === "string" && value[name2].trim()) ? value : void 0;
380
+ }
381
+ async function hostProxyEnvironment() {
382
+ if (typeof dependencies.hostProxyEnv === "function") return proxyOverlay(dependencies.hostProxyEnv());
383
+ const mod = await hostProxyModule();
384
+ try {
385
+ const overlay = typeof mod?.proxyEnvironmentForChild === "function" ? mod.proxyEnvironmentForChild() : void 0;
386
+ return proxyOverlay(overlay);
387
+ } catch {
388
+ return void 0;
389
+ }
390
+ }
391
+ async function grokFetch(env = process.env) {
392
+ const url = apiProxyUrl(env);
393
+ if (!url || !HTTP_PROXY_PATTERN.test(url)) return void 0;
394
+ if (await hostAlreadyProxiesGrok()) return void 0;
395
+ if (cached?.url === url) return cached.fetch;
396
+ const undici = dependencies.undici ?? await loadHostUndici();
397
+ if (typeof undici?.fetch !== "function" || typeof undici?.ProxyAgent !== "function") return void 0;
398
+ try {
399
+ const dispatcher = new undici.ProxyAgent(url);
400
+ const fetchImpl = (input, init) => undici.fetch(input, { ...init, dispatcher });
401
+ cached = { url, fetch: fetchImpl };
402
+ return fetchImpl;
403
+ } catch {
404
+ return void 0;
405
+ }
406
+ }
407
+
281
408
  // src/catalog.js
282
409
  var REASONING_LEVELS = Object.freeze(["low", "medium", "high", "xhigh"]);
283
410
  function asId(value) {
@@ -400,7 +527,7 @@ function toLlmModels(models) {
400
527
  });
401
528
  }
402
529
  async function fetchLiveCatalog(accessToken, options = {}) {
403
- const fetchImpl = options.fetch ?? globalThis.fetch;
530
+ const fetchImpl = options.fetch ?? await grokFetch(options.env) ?? globalThis.fetch;
404
531
  const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? CATALOG_TIMEOUT_MS);
405
532
  const response = await fetchImpl(MODELS_V2_URL, {
406
533
  method: "GET",
@@ -414,7 +541,7 @@ async function fetchLiveCatalog(accessToken, options = {}) {
414
541
  if (!response.ok) {
415
542
  throw new Error(`Grok models-v2 returned HTTP ${response.status}`);
416
543
  }
417
- const body = await response.json();
544
+ const body = await readResponseJson(response);
418
545
  return extractLiveModels(body);
419
546
  }
420
547
  async function loadCatalog(accessToken, options = {}) {
@@ -646,7 +773,8 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
646
773
  let response;
647
774
  try {
648
775
  const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? USAGE_TIMEOUT_MS);
649
- response = await fetchImpl(BILLING_CREDITS_URL, {
776
+ const tunnelFetch = options.fetch ? void 0 : await grokFetch(options.env);
777
+ response = await (tunnelFetch ?? fetchImpl)(BILLING_CREDITS_URL, {
650
778
  method: "GET",
651
779
  headers: {
652
780
  Accept: "application/json",
@@ -672,7 +800,7 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
672
800
  const looksJson = !contentTypeLower || contentTypeLower.includes("json") || contentTypeLower === "text/plain";
673
801
  let text;
674
802
  try {
675
- text = await response.text();
803
+ text = await readResponseText(response);
676
804
  } catch {
677
805
  return unavailable("Could not read billing body");
678
806
  }
@@ -722,11 +850,11 @@ async function fetchBillingUsage(accessToken, options = {}) {
722
850
  }
723
851
 
724
852
  // src/adapter.js
725
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
853
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
726
854
  import { createRequire } from "node:module";
727
855
  import { homedir as homedir2 } from "node:os";
728
- import { dirname, join as join2 } from "node:path";
729
- import { pathToFileURL } from "node:url";
856
+ import { dirname as dirname2, join as join3 } from "node:path";
857
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
730
858
  function withImportTimeout(promise, ms, message) {
731
859
  let timer;
732
860
  return Promise.race([
@@ -746,28 +874,28 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
746
874
  const override = env.DSH_GROK_PEER_ROOT;
747
875
  if (typeof override === "string" && override.trim()) push(override.trim());
748
876
  if (typeof argv1 === "string" && argv1) {
749
- let dir = dirname(argv1);
750
- for (let depth = 0; depth < 6 && dir && dir !== dirname(dir); depth++) {
751
- push(join2(dir, "node_modules"));
752
- dir = dirname(dir);
877
+ let dir = dirname2(argv1);
878
+ for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
879
+ push(join3(dir, "node_modules"));
880
+ dir = dirname2(dir);
753
881
  }
754
882
  }
755
883
  const prefix = env.NPM_CONFIG_PREFIX;
756
- if (typeof prefix === "string" && prefix.trim()) push(join2(prefix.trim(), "lib", "node_modules"));
884
+ if (typeof prefix === "string" && prefix.trim()) push(join3(prefix.trim(), "lib", "node_modules"));
757
885
  if (typeof process.execPath === "string" && process.execPath) {
758
- push(join2(dirname(process.execPath), "..", "lib", "node_modules"));
886
+ push(join3(dirname2(process.execPath), "..", "lib", "node_modules"));
759
887
  }
760
- push(join2("/opt/homebrew", "lib", "node_modules"));
761
- push(join2("/usr/local", "lib", "node_modules"));
888
+ push(join3("/opt/homebrew", "lib", "node_modules"));
889
+ push(join3("/usr/local", "lib", "node_modules"));
762
890
  for (const segment of ["Library/pnpm", ".local/share/pnpm", ".pnpm-global"]) {
763
- push(join2(homedir2(), segment, "node_modules"));
891
+ push(join3(homedir2(), segment, "node_modules"));
764
892
  }
765
893
  if (typeof env.DSH_PORTABLE_HOME === "string" && env.DSH_PORTABLE_HOME.trim()) {
766
- push(join2(env.DSH_PORTABLE_HOME.trim(), "node_modules"));
894
+ push(join3(env.DSH_PORTABLE_HOME.trim(), "node_modules"));
767
895
  }
768
- push(join2(homedir2(), ".local", "lib", "node_modules"));
769
- const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() : join2(homedir2(), ".dsh");
770
- push(join2(dshHome, "profiles", "node_modules"));
896
+ push(join3(homedir2(), ".local", "lib", "node_modules"));
897
+ const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() : join3(homedir2(), ".dsh");
898
+ push(join3(dshHome, "profiles", "node_modules"));
771
899
  return roots;
772
900
  }
773
901
  function splitSpecifier(specifier) {
@@ -811,14 +939,14 @@ function resolveExportsTarget(exportsField, subpath) {
811
939
  function resolveHostSpecifierManually(root, specifier) {
812
940
  const { name: name2, subpath } = splitSpecifier(specifier);
813
941
  const candidates = [
814
- join2(root, name2),
815
- join2(root, "node_modules", name2),
816
- join2(root, "@deepseek-ai", "dsh", "node_modules", name2),
817
- join2(root, "node_modules", "@deepseek-ai", "dsh", "node_modules", name2)
942
+ join3(root, name2),
943
+ join3(root, "node_modules", name2),
944
+ join3(root, "@deepseek-ai", "dsh", "node_modules", name2),
945
+ join3(root, "node_modules", "@deepseek-ai", "dsh", "node_modules", name2)
818
946
  ];
819
947
  for (const pkgDir of candidates) {
820
- const manifest = join2(pkgDir, "package.json");
821
- if (!existsSync2(manifest)) continue;
948
+ const manifest = join3(pkgDir, "package.json");
949
+ if (!existsSync3(manifest)) continue;
822
950
  let pkg;
823
951
  try {
824
952
  pkg = JSON.parse(readFileSync3(manifest, "utf8"));
@@ -827,15 +955,15 @@ function resolveHostSpecifierManually(root, specifier) {
827
955
  }
828
956
  const target = pkg.exports !== void 0 ? resolveExportsTarget(pkg.exports, subpath) : subpath === "." ? pkg.module ?? pkg.main ?? "index.js" : void 0;
829
957
  if (typeof target !== "string" || !target) continue;
830
- const file = join2(pkgDir, target);
831
- if (existsSync2(file)) return file;
958
+ const file = join3(pkgDir, target);
959
+ if (existsSync3(file)) return file;
832
960
  }
833
961
  return void 0;
834
962
  }
835
963
  function resolveHostSpecifier(specifier, roots = hostModuleRoots()) {
836
964
  for (const root of roots) {
837
965
  try {
838
- const resolved = createRequire(join2(root, "noop.js")).resolve(specifier);
966
+ const resolved = createRequire(join3(root, "noop.js")).resolve(specifier);
839
967
  if (typeof resolved === "string" && resolved) return resolved;
840
968
  } catch {
841
969
  }
@@ -851,7 +979,7 @@ async function optionalImport(specifier, options = {}) {
851
979
  const candidates = [specifier];
852
980
  if (!injected && !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("file:")) {
853
981
  const resolved = resolveHostSpecifier(specifier, options.hostRoots ?? hostModuleRoots());
854
- if (resolved) candidates.push(pathToFileURL(resolved).href);
982
+ if (resolved) candidates.push(pathToFileURL2(resolved).href);
855
983
  }
856
984
  for (const candidate of candidates) {
857
985
  try {
@@ -1066,6 +1194,7 @@ function jsonSafeChunk(value) {
1066
1194
  return out;
1067
1195
  }
1068
1196
  async function* streamResponsesUnsafe(options, token) {
1197
+ const fetchImpl = await grokFetch() ?? globalThis.fetch;
1069
1198
  const headers = {
1070
1199
  Accept: "text/event-stream",
1071
1200
  "Content-Type": "application/json",
@@ -1091,7 +1220,7 @@ async function* streamResponsesUnsafe(options, token) {
1091
1220
  const tools = cacheStableTools(options.tools);
1092
1221
  if (tools.length) body.tools = tools;
1093
1222
  if (options.reasoningEffort) body.reasoning = { effort: options.reasoningEffort };
1094
- const response = await fetch(RESPONSES_URL, {
1223
+ const response = await fetchImpl(RESPONSES_URL, {
1095
1224
  method: "POST",
1096
1225
  headers,
1097
1226
  body: JSON.stringify(body),
@@ -1429,9 +1558,10 @@ function buildAuthConfig() {
1429
1558
  }
1430
1559
  };
1431
1560
  }
1432
- function withEncryptedReasoningInclude(api) {
1561
+ function withEncryptedReasoningInclude(api, fetchImpl) {
1433
1562
  const inject2 = (options) => ({
1434
1563
  ...options,
1564
+ ...typeof fetchImpl === "function" ? { fetch: fetchImpl } : {},
1435
1565
  samplingParams: {
1436
1566
  ...options?.samplingParams,
1437
1567
  include: ["reasoning.encrypted_content"]
@@ -1501,7 +1631,7 @@ async function createGrokBuildAdapter(session, options = {}) {
1501
1631
  if (!responsesApi) {
1502
1632
  return { adapter: asHostAdapter(duck), kind: "custom-mvp", note: "openai-responses API module is unavailable; using the custom adapter." };
1503
1633
  }
1504
- responsesApi = withEncryptedReasoningInclude(responsesApi);
1634
+ responsesApi = withEncryptedReasoningInclude(responsesApi, await grokFetch());
1505
1635
  const store = createStore(session);
1506
1636
  const authModels = piAi.createModels({
1507
1637
  credentials: store,
@@ -1592,46 +1722,166 @@ async function createGrokBuildAdapter(session, options = {}) {
1592
1722
  }
1593
1723
 
1594
1724
  // src/session.js
1595
- function resolveGrokBin(env = process.env, exists = existsSync3) {
1725
+ function resolveGrokBin(env = process.env, exists = existsSync4) {
1596
1726
  if (typeof env.DSH_GROK_BIN === "string" && env.DSH_GROK_BIN.trim()) return env.DSH_GROK_BIN.trim();
1597
- const local = join3(grokHome(env), "bin", "grok");
1727
+ const local = join4(grokHome(env), "bin", "grok");
1598
1728
  if (exists(local)) return local;
1599
1729
  return "grok";
1600
1730
  }
1601
- function grokCliAvailable(env = process.env, exists = existsSync3) {
1731
+ function grokCliAvailable(env = process.env, exists = existsSync4) {
1602
1732
  const bin = resolveGrokBin(env, exists);
1603
1733
  if (bin.includes("/") || bin.includes("\\")) return exists(bin);
1604
1734
  const delimiter = process.platform === "win32" ? ";" : ":";
1605
- return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join3(dir, bin)));
1735
+ return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join4(dir, bin)));
1736
+ }
1737
+ var STANDARD_PROXY_NAMES = [
1738
+ "http_proxy",
1739
+ "https_proxy",
1740
+ "all_proxy",
1741
+ "no_proxy",
1742
+ "HTTP_PROXY",
1743
+ "HTTPS_PROXY",
1744
+ "ALL_PROXY",
1745
+ "NO_PROXY"
1746
+ ];
1747
+ function cliProxyEnv(env = process.env) {
1748
+ const base = env ?? process.env;
1749
+ const declared = STANDARD_PROXY_NAMES.some((name2) => typeof base[name2] === "string" && base[name2].trim() !== "");
1750
+ if (declared) return base;
1751
+ const named = [base.GROK_CLI_PROXY, base.GROK_PROXY].find((value) => typeof value === "string" && value.trim() !== "");
1752
+ const proxy = typeof named === "string" ? named.trim() : "";
1753
+ if (!proxy) return base;
1754
+ const bypass = typeof base.GROK_CLI_NO_PROXY === "string" && base.GROK_CLI_NO_PROXY.trim() ? base.GROK_CLI_NO_PROXY.trim() : "localhost,127.0.0.1,::1";
1755
+ const httpish = /^https?:\/\//iu.test(proxy);
1756
+ return {
1757
+ ...base,
1758
+ ...httpish ? { http_proxy: proxy, https_proxy: proxy } : {},
1759
+ all_proxy: proxy,
1760
+ no_proxy: bypass
1761
+ };
1762
+ }
1763
+ async function resolveCliEnv(env = process.env) {
1764
+ const base = env ?? process.env;
1765
+ const overlay = await hostProxyEnvironment();
1766
+ if (!overlay) return cliProxyEnv(base);
1767
+ const next = { ...base };
1768
+ for (const [key, value] of Object.entries(overlay)) {
1769
+ if (value === void 0) delete next[key];
1770
+ else next[key] = value;
1771
+ }
1772
+ return next;
1773
+ }
1774
+ function openExternal(url, options = {}) {
1775
+ const spawnFn = options.spawn ?? spawn;
1776
+ const platform = options.platform ?? process.platform;
1777
+ const [command, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
1778
+ return new Promise((resolve) => {
1779
+ let child;
1780
+ try {
1781
+ child = spawnFn(command, args, { stdio: "ignore", detached: false });
1782
+ } catch {
1783
+ resolve(false);
1784
+ return;
1785
+ }
1786
+ let settled = false;
1787
+ const finish = (value) => {
1788
+ if (settled) return;
1789
+ settled = true;
1790
+ resolve(value);
1791
+ };
1792
+ child.on?.("error", () => finish(false));
1793
+ child.on?.("exit", (code) => finish(code === 0));
1794
+ child.unref?.();
1795
+ });
1606
1796
  }
1607
- function spawnGrokLogin(options = {}) {
1797
+ var LOGIN_URL_PATTERN = /https?:\/\/[^\s<>"'`\\]+/u;
1798
+ var DEVICE_CODE_PATTERN = /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/u;
1799
+ async function spawnGrokLogin(options = {}) {
1608
1800
  const spawnFn = options.spawn ?? spawn;
1609
1801
  const bin = resolveGrokBin(options.env, options.exists);
1610
1802
  const args = options.device ? ["login", "--device-auth"] : ["login"];
1803
+ const startTimeoutMs = typeof options.startTimeoutMs === "number" && options.startTimeoutMs > 0 ? options.startTimeoutMs : LOGIN_START_TIMEOUT_MS;
1804
+ const openUrl = options.openUrl ?? ((url) => openExternal(url, { spawn: options.openSpawn }));
1805
+ const childEnv = await resolveCliEnv(options.env);
1611
1806
  return new Promise((resolve, reject) => {
1612
- const child = spawnFn(bin, args, {
1613
- stdio: options.stdio ?? "inherit",
1614
- env: options.env ?? process.env
1615
- });
1807
+ let child;
1808
+ try {
1809
+ child = spawnFn(bin, args, {
1810
+ stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
1811
+ env: childEnv
1812
+ });
1813
+ } catch (error) {
1814
+ reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
1815
+ return;
1816
+ }
1817
+ let settled = false;
1818
+ let loginUrl;
1819
+ let userCode;
1820
+ let buffer = "";
1821
+ let timer;
1822
+ const succeed = (value) => {
1823
+ if (settled) return;
1824
+ settled = true;
1825
+ if (timer) clearTimeout(timer);
1826
+ resolve(value);
1827
+ };
1828
+ const scan = (text) => {
1829
+ if (text) buffer = `${buffer}${text}`.slice(-8192);
1830
+ if (!userCode) {
1831
+ const code = DEVICE_CODE_PATTERN.exec(buffer);
1832
+ if (code) userCode = code[1];
1833
+ }
1834
+ if (loginUrl) return;
1835
+ const match = LOGIN_URL_PATTERN.exec(buffer);
1836
+ if (!match) return;
1837
+ loginUrl = match[0];
1838
+ try {
1839
+ void openUrl(loginUrl);
1840
+ } catch {
1841
+ }
1842
+ succeed({ ok: true, pending: true, loginUrl, userCode, child });
1843
+ };
1844
+ const piped = Boolean(child.stdout && child.stderr);
1845
+ if (piped) {
1846
+ child.stdout.on("data", (chunk) => scan(String(chunk)));
1847
+ child.stderr.on("data", (chunk) => scan(String(chunk)));
1848
+ timer = setTimeout(() => {
1849
+ succeed({
1850
+ ok: true,
1851
+ pending: true,
1852
+ loginUrl,
1853
+ userCode,
1854
+ child,
1855
+ warning: `grok login printed no sign-in URL within ${startTimeoutMs}ms`
1856
+ });
1857
+ }, startTimeoutMs);
1858
+ }
1616
1859
  child.on("error", (error) => {
1860
+ if (settled) return;
1861
+ settled = true;
1862
+ if (timer) clearTimeout(timer);
1617
1863
  reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
1618
1864
  });
1619
1865
  child.on("exit", (code, signal) => {
1620
- if (code === 0) resolve({ ok: true, code });
1866
+ if (settled) return;
1867
+ settled = true;
1868
+ if (timer) clearTimeout(timer);
1869
+ if (code === 0) resolve({ ok: true, pending: false, loginUrl, userCode, child });
1621
1870
  else reject(new Error(signal ? `grok login terminated by ${signal}` : `grok login exited with code ${code ?? "unknown"}`));
1622
1871
  });
1623
1872
  });
1624
1873
  }
1625
- function spawnGrokRefresh(options = {}) {
1874
+ async function spawnGrokRefresh(options = {}) {
1626
1875
  const spawnFn = options.spawn ?? spawn;
1627
1876
  const bin = resolveGrokBin(options.env, options.exists);
1628
1877
  const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : CLI_REFRESH_TIMEOUT_MS;
1878
+ const childEnv = await resolveCliEnv(options.env);
1629
1879
  return new Promise((resolve, reject) => {
1630
1880
  let child;
1631
1881
  try {
1632
1882
  child = spawnFn(bin, ["models"], {
1633
1883
  stdio: options.stdio ?? "ignore",
1634
- env: options.env ?? process.env
1884
+ env: childEnv
1635
1885
  });
1636
1886
  } catch (error) {
1637
1887
  reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
@@ -1940,8 +2190,27 @@ function createSessionService({
1940
2190
  return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1941
2191
  };
1942
2192
  const login = async (options) => {
1943
- await spawnGrokLogin(options);
1944
- return pull();
2193
+ const started = await spawnGrokLogin(options);
2194
+ const extra = {
2195
+ ...started.loginUrl === void 0 ? {} : { loginUrl: started.loginUrl },
2196
+ ...started.userCode === void 0 ? {} : { userCode: started.userCode },
2197
+ ...started.warning === void 0 ? {} : { warning: started.warning }
2198
+ };
2199
+ if (started.pending) {
2200
+ started.child?.once?.("exit", (code) => {
2201
+ if (code !== 0) return;
2202
+ scheduleDeferred(() => {
2203
+ void pull().catch((error) => {
2204
+ logger?.warn?.(
2205
+ "Grok subscription login sync failed: %s",
2206
+ error instanceof Error ? error.message : "unknown"
2207
+ );
2208
+ });
2209
+ });
2210
+ });
2211
+ return { ok: true, pending: true, ...extra, ...await status() };
2212
+ }
2213
+ return { ...await pull(), pending: false, ...extra };
1945
2214
  };
1946
2215
  const logout = async () => {
1947
2216
  memoryAccessToken = void 0;
@@ -1979,7 +2248,7 @@ function createSessionService({
1979
2248
  import { spawn as spawn2 } from "node:child_process";
1980
2249
  import { readdir, readFile, realpath } from "node:fs/promises";
1981
2250
  import { homedir as homedir3 } from "node:os";
1982
- import { join as join4 } from "node:path";
2251
+ import { join as join5 } from "node:path";
1983
2252
  import { fileURLToPath } from "node:url";
1984
2253
  var PACKAGE_NAME = PLUGIN_ID;
1985
2254
  var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -2028,20 +2297,20 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
2028
2297
  }
2029
2298
  let entries = [];
2030
2299
  try {
2031
- entries = await readdir(join4(dshHome, "profiles"), { withFileTypes: true });
2300
+ entries = await readdir(join5(dshHome, "profiles"), { withFileTypes: true });
2032
2301
  } catch {
2033
2302
  entries = [];
2034
2303
  }
2035
2304
  const declared = [];
2036
2305
  for (const entry of entries) {
2037
2306
  if (!entry.isDirectory()) continue;
2038
- const profileDir = join4(dshHome, "profiles", entry.name);
2039
- const spec = (await readManifest(join4(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
2307
+ const profileDir = join5(dshHome, "profiles", entry.name);
2308
+ const spec = (await readManifest(join5(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
2040
2309
  if (typeof spec !== "string") continue;
2041
2310
  declared.push({ profile: entry.name, spec });
2042
2311
  if (ownReal === void 0) continue;
2043
2312
  try {
2044
- const installed = await realpath(join4(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
2313
+ const installed = await realpath(join5(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
2045
2314
  if (installed === ownReal) return { kind: classifySpec(spec), profile: entry.name };
2046
2315
  } catch {
2047
2316
  }
@@ -2108,8 +2377,8 @@ function createGrokPluginManager({
2108
2377
  } = {}) {
2109
2378
  if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
2110
2379
  if (typeof runCommand !== "function") throw new Error("Grok plugin manager requires runCommand");
2111
- const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join4(homedir3(), ".dsh");
2112
- let cached;
2380
+ const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join5(homedir3(), ".dsh");
2381
+ let cached2;
2113
2382
  let inFlight;
2114
2383
  let generation = 0;
2115
2384
  let updating = false;
@@ -2130,7 +2399,7 @@ function createGrokPluginManager({
2130
2399
  if (!response.ok) throw new Error("Grok plugin version check failed");
2131
2400
  let version;
2132
2401
  try {
2133
- version = (await response.json())?.version;
2402
+ version = (await readResponseJson(response))?.version;
2134
2403
  } catch {
2135
2404
  version = void 0;
2136
2405
  }
@@ -2160,11 +2429,11 @@ function createGrokPluginManager({
2160
2429
  return Object.freeze({
2161
2430
  async read({ force = false, signal } = {}) {
2162
2431
  signal?.throwIfAborted();
2163
- if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone(cached);
2432
+ if (!force && cached2 !== void 0 && now() - cached2.fetchedAt < ttlMs) return clone(cached2);
2164
2433
  const createPending = (requestSignal) => {
2165
2434
  const observedGeneration = generation;
2166
2435
  return load(requestSignal).then((value) => {
2167
- if (generation === observedGeneration) cached = value;
2436
+ if (generation === observedGeneration) cached2 = value;
2168
2437
  return value;
2169
2438
  });
2170
2439
  };
@@ -2207,7 +2476,7 @@ function createGrokPluginManager({
2207
2476
  }
2208
2477
  if (result.code !== 0) throw new Error("Grok plugin update failed");
2209
2478
  generation += 1;
2210
- cached = void 0;
2479
+ cached2 = void 0;
2211
2480
  return { version: latest, profile: install.profile };
2212
2481
  } finally {
2213
2482
  updating = false;
@@ -2215,7 +2484,7 @@ function createGrokPluginManager({
2215
2484
  },
2216
2485
  invalidate() {
2217
2486
  generation += 1;
2218
- cached = void 0;
2487
+ cached2 = void 0;
2219
2488
  }
2220
2489
  });
2221
2490
  }
@@ -2357,14 +2626,16 @@ async function dispatch(session, endpoint, diagnostics, payload, signal, pluginM
2357
2626
  }
2358
2627
  function createRpcHandler(session, options = {}) {
2359
2628
  const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : RPC_HANDLER_TIMEOUT_MS;
2629
+ const loginTimeoutMs = typeof options.loginTimeoutMs === "number" && options.loginTimeoutMs > 0 ? options.loginTimeoutMs : LOGIN_HANDLER_TIMEOUT_MS;
2360
2630
  return async function handle(endpoint, payload, signal) {
2361
2631
  try {
2362
2632
  const operation = dispatch(session, endpoint, options.diagnostics, payload, signal, options.pluginManager);
2363
2633
  if (endpoint === "plugin/update") return await operation;
2634
+ const ceiling = endpoint === "login/cli" || endpoint === "login/device" ? loginTimeoutMs : timeoutMs;
2364
2635
  return await withTimeout(
2365
2636
  operation,
2366
- timeoutMs,
2367
- `Grok subscription RPC "${endpoint}" timed out after ${timeoutMs}ms`,
2637
+ ceiling,
2638
+ `Grok subscription RPC "${endpoint}" timed out after ${ceiling}ms`,
2368
2639
  { unref: false }
2369
2640
  );
2370
2641
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-grok-subscription",
3
- "version": "1.0.11",
3
+ "version": "1.0.19",
4
4
  "description": "Use a SuperGrok or X Premium Grok Build subscription in DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",