dsh-grok-subscription 1.0.11 → 1.0.18

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 to **this plugin's Grok requests** (catalog, usage, chat) and to the **`grok` CLI** it spawns (sign-in, renewal), while DSH and every other provider stay direct — an `http_proxy` line there would route them all through the tunnel and break them whenever it is down. 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. Standard proxy variables already present in the environment are inherited by the CLI too;
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 请求**(模型目录、用量、聊天)和它启动的 **`grok` CLI**(登录、续期),DSH 自身与其他 provider 仍走直连——把 `http_proxy` 写在这里会让所有 provider 一起走隧道,隧道一停就全连不上。写好后重启客户端;登录链接会交给系统浏览器打开,面板里同时显示一次性验证码与可点链接。环境里已有的标准代理变量同样会被 CLI 继承;
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.18",
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.18",
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,142 @@ 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 GROK_API_HOSTS = Object.freeze(["cli-chat-proxy.grok.com"]);
330
+ var HTTP_PROXY_PATTERN = /^https?:\/\//iu;
331
+ var HOST_PROXY_PACKAGE = "@deepseek-ai/dsh-http-proxy";
332
+ var installed;
333
+ var dependencies = { undici: void 0, hostProxies: 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
+ function hostOf(origin) {
342
+ try {
343
+ return new URL(origin).hostname;
344
+ } catch {
345
+ return "";
346
+ }
347
+ }
348
+ async function loadHostUndici() {
349
+ let dir = dirname(process.argv[1] ?? "");
350
+ for (let depth = 0; depth < 6 && dir && dir !== dirname(dir); depth++) {
351
+ const entry = join2(dir, "node_modules", "undici", "index.js");
352
+ if (existsSync2(entry)) {
353
+ try {
354
+ return await import(pathToFileURL(entry).href);
355
+ } catch {
356
+ break;
357
+ }
358
+ }
359
+ dir = dirname(dir);
360
+ }
361
+ try {
362
+ return await import("undici");
363
+ } catch {
364
+ return void 0;
365
+ }
366
+ }
367
+ async function hostProxiesGrok() {
368
+ if (typeof dependencies.hostProxies === "function") return dependencies.hostProxies();
369
+ try {
370
+ const mod = await import(HOST_PROXY_PACKAGE);
371
+ const route = mod?.proxyRouteFor?.(`https://${GROK_API_HOSTS[0]}/`);
372
+ return route?.proxied === true;
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+ async function ensureApiRouting(env = process.env) {
378
+ const url = apiProxyUrl(env);
379
+ if (!url || !HTTP_PROXY_PATTERN.test(url)) return "direct";
380
+ if (installed?.url === url) return "plugin";
381
+ if (await hostProxiesGrok()) return "host";
382
+ const undici = dependencies.undici ?? await loadHostUndici();
383
+ const { Agent, Pool, ProxyAgent, getGlobalDispatcher, setGlobalDispatcher } = undici ?? {};
384
+ if (typeof Agent !== "function" || typeof setGlobalDispatcher !== "function") return "direct";
385
+ try {
386
+ const tunnel = new ProxyAgent(url);
387
+ const routing = new Agent({
388
+ factory(origin, options) {
389
+ return GROK_API_HOSTS.includes(hostOf(origin)) ? tunnel : new Pool(origin, options);
390
+ }
391
+ });
392
+ const previous = typeof getGlobalDispatcher === "function" ? getGlobalDispatcher() : void 0;
393
+ setGlobalDispatcher(routing);
394
+ installed = {
395
+ url,
396
+ dispatcher: routing,
397
+ restore: () => {
398
+ if (previous !== void 0) setGlobalDispatcher(previous);
399
+ installed = void 0;
400
+ }
401
+ };
402
+ return "plugin";
403
+ } catch {
404
+ return "direct";
405
+ }
406
+ }
407
+ function resetApiRouting() {
408
+ const current = installed;
409
+ installed = void 0;
410
+ if (!current) return false;
411
+ try {
412
+ current.restore();
413
+ return true;
414
+ } catch {
415
+ return false;
416
+ }
417
+ }
418
+
281
419
  // src/catalog.js
282
420
  var REASONING_LEVELS = Object.freeze(["low", "medium", "high", "xhigh"]);
283
421
  function asId(value) {
@@ -400,6 +538,7 @@ function toLlmModels(models) {
400
538
  });
401
539
  }
402
540
  async function fetchLiveCatalog(accessToken, options = {}) {
541
+ await ensureApiRouting(options.env);
403
542
  const fetchImpl = options.fetch ?? globalThis.fetch;
404
543
  const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? CATALOG_TIMEOUT_MS);
405
544
  const response = await fetchImpl(MODELS_V2_URL, {
@@ -414,7 +553,7 @@ async function fetchLiveCatalog(accessToken, options = {}) {
414
553
  if (!response.ok) {
415
554
  throw new Error(`Grok models-v2 returned HTTP ${response.status}`);
416
555
  }
417
- const body = await response.json();
556
+ const body = await readResponseJson(response);
418
557
  return extractLiveModels(body);
419
558
  }
420
559
  async function loadCatalog(accessToken, options = {}) {
@@ -645,6 +784,7 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
645
784
  }
646
785
  let response;
647
786
  try {
787
+ await ensureApiRouting(options.env);
648
788
  const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? USAGE_TIMEOUT_MS);
649
789
  response = await fetchImpl(BILLING_CREDITS_URL, {
650
790
  method: "GET",
@@ -672,7 +812,7 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
672
812
  const looksJson = !contentTypeLower || contentTypeLower.includes("json") || contentTypeLower === "text/plain";
673
813
  let text;
674
814
  try {
675
- text = await response.text();
815
+ text = await readResponseText(response);
676
816
  } catch {
677
817
  return unavailable("Could not read billing body");
678
818
  }
@@ -722,11 +862,11 @@ async function fetchBillingUsage(accessToken, options = {}) {
722
862
  }
723
863
 
724
864
  // src/adapter.js
725
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
865
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
726
866
  import { createRequire } from "node:module";
727
867
  import { homedir as homedir2 } from "node:os";
728
- import { dirname, join as join2 } from "node:path";
729
- import { pathToFileURL } from "node:url";
868
+ import { dirname as dirname2, join as join3 } from "node:path";
869
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
730
870
  function withImportTimeout(promise, ms, message) {
731
871
  let timer;
732
872
  return Promise.race([
@@ -746,28 +886,28 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
746
886
  const override = env.DSH_GROK_PEER_ROOT;
747
887
  if (typeof override === "string" && override.trim()) push(override.trim());
748
888
  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);
889
+ let dir = dirname2(argv1);
890
+ for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
891
+ push(join3(dir, "node_modules"));
892
+ dir = dirname2(dir);
753
893
  }
754
894
  }
755
895
  const prefix = env.NPM_CONFIG_PREFIX;
756
- if (typeof prefix === "string" && prefix.trim()) push(join2(prefix.trim(), "lib", "node_modules"));
896
+ if (typeof prefix === "string" && prefix.trim()) push(join3(prefix.trim(), "lib", "node_modules"));
757
897
  if (typeof process.execPath === "string" && process.execPath) {
758
- push(join2(dirname(process.execPath), "..", "lib", "node_modules"));
898
+ push(join3(dirname2(process.execPath), "..", "lib", "node_modules"));
759
899
  }
760
- push(join2("/opt/homebrew", "lib", "node_modules"));
761
- push(join2("/usr/local", "lib", "node_modules"));
900
+ push(join3("/opt/homebrew", "lib", "node_modules"));
901
+ push(join3("/usr/local", "lib", "node_modules"));
762
902
  for (const segment of ["Library/pnpm", ".local/share/pnpm", ".pnpm-global"]) {
763
- push(join2(homedir2(), segment, "node_modules"));
903
+ push(join3(homedir2(), segment, "node_modules"));
764
904
  }
765
905
  if (typeof env.DSH_PORTABLE_HOME === "string" && env.DSH_PORTABLE_HOME.trim()) {
766
- push(join2(env.DSH_PORTABLE_HOME.trim(), "node_modules"));
906
+ push(join3(env.DSH_PORTABLE_HOME.trim(), "node_modules"));
767
907
  }
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"));
908
+ push(join3(homedir2(), ".local", "lib", "node_modules"));
909
+ const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() : join3(homedir2(), ".dsh");
910
+ push(join3(dshHome, "profiles", "node_modules"));
771
911
  return roots;
772
912
  }
773
913
  function splitSpecifier(specifier) {
@@ -811,14 +951,14 @@ function resolveExportsTarget(exportsField, subpath) {
811
951
  function resolveHostSpecifierManually(root, specifier) {
812
952
  const { name: name2, subpath } = splitSpecifier(specifier);
813
953
  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)
954
+ join3(root, name2),
955
+ join3(root, "node_modules", name2),
956
+ join3(root, "@deepseek-ai", "dsh", "node_modules", name2),
957
+ join3(root, "node_modules", "@deepseek-ai", "dsh", "node_modules", name2)
818
958
  ];
819
959
  for (const pkgDir of candidates) {
820
- const manifest = join2(pkgDir, "package.json");
821
- if (!existsSync2(manifest)) continue;
960
+ const manifest = join3(pkgDir, "package.json");
961
+ if (!existsSync3(manifest)) continue;
822
962
  let pkg;
823
963
  try {
824
964
  pkg = JSON.parse(readFileSync3(manifest, "utf8"));
@@ -827,15 +967,15 @@ function resolveHostSpecifierManually(root, specifier) {
827
967
  }
828
968
  const target = pkg.exports !== void 0 ? resolveExportsTarget(pkg.exports, subpath) : subpath === "." ? pkg.module ?? pkg.main ?? "index.js" : void 0;
829
969
  if (typeof target !== "string" || !target) continue;
830
- const file = join2(pkgDir, target);
831
- if (existsSync2(file)) return file;
970
+ const file = join3(pkgDir, target);
971
+ if (existsSync3(file)) return file;
832
972
  }
833
973
  return void 0;
834
974
  }
835
975
  function resolveHostSpecifier(specifier, roots = hostModuleRoots()) {
836
976
  for (const root of roots) {
837
977
  try {
838
- const resolved = createRequire(join2(root, "noop.js")).resolve(specifier);
978
+ const resolved = createRequire(join3(root, "noop.js")).resolve(specifier);
839
979
  if (typeof resolved === "string" && resolved) return resolved;
840
980
  } catch {
841
981
  }
@@ -851,7 +991,7 @@ async function optionalImport(specifier, options = {}) {
851
991
  const candidates = [specifier];
852
992
  if (!injected && !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("file:")) {
853
993
  const resolved = resolveHostSpecifier(specifier, options.hostRoots ?? hostModuleRoots());
854
- if (resolved) candidates.push(pathToFileURL(resolved).href);
994
+ if (resolved) candidates.push(pathToFileURL2(resolved).href);
855
995
  }
856
996
  for (const candidate of candidates) {
857
997
  try {
@@ -1066,6 +1206,7 @@ function jsonSafeChunk(value) {
1066
1206
  return out;
1067
1207
  }
1068
1208
  async function* streamResponsesUnsafe(options, token) {
1209
+ await ensureApiRouting();
1069
1210
  const headers = {
1070
1211
  Accept: "text/event-stream",
1071
1212
  "Content-Type": "application/json",
@@ -1501,6 +1642,7 @@ async function createGrokBuildAdapter(session, options = {}) {
1501
1642
  if (!responsesApi) {
1502
1643
  return { adapter: asHostAdapter(duck), kind: "custom-mvp", note: "openai-responses API module is unavailable; using the custom adapter." };
1503
1644
  }
1645
+ await ensureApiRouting();
1504
1646
  responsesApi = withEncryptedReasoningInclude(responsesApi);
1505
1647
  const store = createStore(session);
1506
1648
  const authModels = piAi.createModels({
@@ -1592,32 +1734,139 @@ async function createGrokBuildAdapter(session, options = {}) {
1592
1734
  }
1593
1735
 
1594
1736
  // src/session.js
1595
- function resolveGrokBin(env = process.env, exists = existsSync3) {
1737
+ function resolveGrokBin(env = process.env, exists = existsSync4) {
1596
1738
  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");
1739
+ const local = join4(grokHome(env), "bin", "grok");
1598
1740
  if (exists(local)) return local;
1599
1741
  return "grok";
1600
1742
  }
1601
- function grokCliAvailable(env = process.env, exists = existsSync3) {
1743
+ function grokCliAvailable(env = process.env, exists = existsSync4) {
1602
1744
  const bin = resolveGrokBin(env, exists);
1603
1745
  if (bin.includes("/") || bin.includes("\\")) return exists(bin);
1604
1746
  const delimiter = process.platform === "win32" ? ";" : ":";
1605
- return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join3(dir, bin)));
1747
+ return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join4(dir, bin)));
1748
+ }
1749
+ var STANDARD_PROXY_NAMES = [
1750
+ "http_proxy",
1751
+ "https_proxy",
1752
+ "all_proxy",
1753
+ "no_proxy",
1754
+ "HTTP_PROXY",
1755
+ "HTTPS_PROXY",
1756
+ "ALL_PROXY",
1757
+ "NO_PROXY"
1758
+ ];
1759
+ function cliProxyEnv(env = process.env) {
1760
+ const base = env ?? process.env;
1761
+ const declared = STANDARD_PROXY_NAMES.some((name2) => typeof base[name2] === "string" && base[name2].trim() !== "");
1762
+ if (declared) return base;
1763
+ const named = [base.GROK_CLI_PROXY, base.GROK_PROXY].find((value) => typeof value === "string" && value.trim() !== "");
1764
+ const proxy = typeof named === "string" ? named.trim() : "";
1765
+ if (!proxy) return base;
1766
+ 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";
1767
+ const httpish = /^https?:\/\//iu.test(proxy);
1768
+ return {
1769
+ ...base,
1770
+ ...httpish ? { http_proxy: proxy, https_proxy: proxy } : {},
1771
+ all_proxy: proxy,
1772
+ no_proxy: bypass
1773
+ };
1606
1774
  }
1775
+ function openExternal(url, options = {}) {
1776
+ const spawnFn = options.spawn ?? spawn;
1777
+ const platform = options.platform ?? process.platform;
1778
+ const [command, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
1779
+ return new Promise((resolve) => {
1780
+ let child;
1781
+ try {
1782
+ child = spawnFn(command, args, { stdio: "ignore", detached: false });
1783
+ } catch {
1784
+ resolve(false);
1785
+ return;
1786
+ }
1787
+ let settled = false;
1788
+ const finish = (value) => {
1789
+ if (settled) return;
1790
+ settled = true;
1791
+ resolve(value);
1792
+ };
1793
+ child.on?.("error", () => finish(false));
1794
+ child.on?.("exit", (code) => finish(code === 0));
1795
+ child.unref?.();
1796
+ });
1797
+ }
1798
+ var LOGIN_URL_PATTERN = /https?:\/\/[^\s<>"'`\\]+/u;
1799
+ var DEVICE_CODE_PATTERN = /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/u;
1607
1800
  function spawnGrokLogin(options = {}) {
1608
1801
  const spawnFn = options.spawn ?? spawn;
1609
1802
  const bin = resolveGrokBin(options.env, options.exists);
1610
1803
  const args = options.device ? ["login", "--device-auth"] : ["login"];
1804
+ const startTimeoutMs = typeof options.startTimeoutMs === "number" && options.startTimeoutMs > 0 ? options.startTimeoutMs : LOGIN_START_TIMEOUT_MS;
1805
+ const openUrl = options.openUrl ?? ((url) => openExternal(url, { spawn: options.openSpawn }));
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: cliProxyEnv(options.env)
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
  });
@@ -1631,7 +1880,7 @@ function spawnGrokRefresh(options = {}) {
1631
1880
  try {
1632
1881
  child = spawnFn(bin, ["models"], {
1633
1882
  stdio: options.stdio ?? "ignore",
1634
- env: options.env ?? process.env
1883
+ env: cliProxyEnv(options.env)
1635
1884
  });
1636
1885
  } catch (error) {
1637
1886
  reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
@@ -1940,8 +2189,27 @@ function createSessionService({
1940
2189
  return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1941
2190
  };
1942
2191
  const login = async (options) => {
1943
- await spawnGrokLogin(options);
1944
- return pull();
2192
+ const started = await spawnGrokLogin(options);
2193
+ const extra = {
2194
+ ...started.loginUrl === void 0 ? {} : { loginUrl: started.loginUrl },
2195
+ ...started.userCode === void 0 ? {} : { userCode: started.userCode },
2196
+ ...started.warning === void 0 ? {} : { warning: started.warning }
2197
+ };
2198
+ if (started.pending) {
2199
+ started.child?.once?.("exit", (code) => {
2200
+ if (code !== 0) return;
2201
+ scheduleDeferred(() => {
2202
+ void pull().catch((error) => {
2203
+ logger?.warn?.(
2204
+ "Grok subscription login sync failed: %s",
2205
+ error instanceof Error ? error.message : "unknown"
2206
+ );
2207
+ });
2208
+ });
2209
+ });
2210
+ return { ok: true, pending: true, ...extra, ...await status() };
2211
+ }
2212
+ return { ...await pull(), pending: false, ...extra };
1945
2213
  };
1946
2214
  const logout = async () => {
1947
2215
  memoryAccessToken = void 0;
@@ -1979,7 +2247,7 @@ function createSessionService({
1979
2247
  import { spawn as spawn2 } from "node:child_process";
1980
2248
  import { readdir, readFile, realpath } from "node:fs/promises";
1981
2249
  import { homedir as homedir3 } from "node:os";
1982
- import { join as join4 } from "node:path";
2250
+ import { join as join5 } from "node:path";
1983
2251
  import { fileURLToPath } from "node:url";
1984
2252
  var PACKAGE_NAME = PLUGIN_ID;
1985
2253
  var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -2028,21 +2296,21 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
2028
2296
  }
2029
2297
  let entries = [];
2030
2298
  try {
2031
- entries = await readdir(join4(dshHome, "profiles"), { withFileTypes: true });
2299
+ entries = await readdir(join5(dshHome, "profiles"), { withFileTypes: true });
2032
2300
  } catch {
2033
2301
  entries = [];
2034
2302
  }
2035
2303
  const declared = [];
2036
2304
  for (const entry of entries) {
2037
2305
  if (!entry.isDirectory()) continue;
2038
- const profileDir = join4(dshHome, "profiles", entry.name);
2039
- const spec = (await readManifest(join4(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
2306
+ const profileDir = join5(dshHome, "profiles", entry.name);
2307
+ const spec = (await readManifest(join5(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
2040
2308
  if (typeof spec !== "string") continue;
2041
2309
  declared.push({ profile: entry.name, spec });
2042
2310
  if (ownReal === void 0) continue;
2043
2311
  try {
2044
- const installed = await realpath(join4(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
2045
- if (installed === ownReal) return { kind: classifySpec(spec), profile: entry.name };
2312
+ const installed2 = await realpath(join5(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
2313
+ if (installed2 === ownReal) return { kind: classifySpec(spec), profile: entry.name };
2046
2314
  } catch {
2047
2315
  }
2048
2316
  }
@@ -2108,7 +2376,7 @@ function createGrokPluginManager({
2108
2376
  } = {}) {
2109
2377
  if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
2110
2378
  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");
2379
+ const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join5(homedir3(), ".dsh");
2112
2380
  let cached;
2113
2381
  let inFlight;
2114
2382
  let generation = 0;
@@ -2130,7 +2398,7 @@ function createGrokPluginManager({
2130
2398
  if (!response.ok) throw new Error("Grok plugin version check failed");
2131
2399
  let version;
2132
2400
  try {
2133
- version = (await response.json())?.version;
2401
+ version = (await readResponseJson(response))?.version;
2134
2402
  } catch {
2135
2403
  version = void 0;
2136
2404
  }
@@ -2357,14 +2625,16 @@ async function dispatch(session, endpoint, diagnostics, payload, signal, pluginM
2357
2625
  }
2358
2626
  function createRpcHandler(session, options = {}) {
2359
2627
  const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : RPC_HANDLER_TIMEOUT_MS;
2628
+ const loginTimeoutMs = typeof options.loginTimeoutMs === "number" && options.loginTimeoutMs > 0 ? options.loginTimeoutMs : LOGIN_HANDLER_TIMEOUT_MS;
2360
2629
  return async function handle(endpoint, payload, signal) {
2361
2630
  try {
2362
2631
  const operation = dispatch(session, endpoint, options.diagnostics, payload, signal, options.pluginManager);
2363
2632
  if (endpoint === "plugin/update") return await operation;
2633
+ const ceiling = endpoint === "login/cli" || endpoint === "login/device" ? loginTimeoutMs : timeoutMs;
2364
2634
  return await withTimeout(
2365
2635
  operation,
2366
- timeoutMs,
2367
- `Grok subscription RPC "${endpoint}" timed out after ${timeoutMs}ms`,
2636
+ ceiling,
2637
+ `Grok subscription RPC "${endpoint}" timed out after ${ceiling}ms`,
2368
2638
  { unref: false }
2369
2639
  );
2370
2640
  } catch (error) {
@@ -2490,6 +2760,18 @@ function apply(ctx, options = {}) {
2490
2760
  "grok-subscription: startup lifetime"
2491
2761
  );
2492
2762
  }
2763
+ void ensureApiRouting().then((mode) => {
2764
+ if (mode === "direct") return;
2765
+ ctx.logger?.debug?.("Grok subscription API route: %s", mode);
2766
+ }).catch((error) => {
2767
+ ctx.logger?.debug?.(
2768
+ "Grok subscription API route skipped: %s",
2769
+ error instanceof Error ? error.message : "unknown"
2770
+ );
2771
+ });
2772
+ if (typeof ctx.effect === "function") {
2773
+ ctx.effect(() => () => resetApiRouting(), "grok-subscription: API proxy route");
2774
+ }
2493
2775
  const notifyCatalogChange = () => {
2494
2776
  scheduleDeferred2(() => {
2495
2777
  if (!active) return;
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.18",
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",