dsh-grok-subscription 1.0.9 → 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
@@ -25,13 +25,13 @@ Models, reasoning effort, and weekly quota all stay inside DSH.
25
25
 
26
26
  ## Three-step start
27
27
 
28
- 1. **Install the plugin.** Run the command below; to select a candidate version, enter the full `package@version`, for example `dsh-grok-subscription@1.0.9`.
28
+ 1. **Install the plugin.** Run the command below; to select a candidate version, enter the full `package@version`, for example `dsh-grok-subscription@1.0.10`.
29
29
 
30
30
  ```sh
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.
@@ -91,7 +91,7 @@ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
91
91
  You can also install the version published on npm:
92
92
 
93
93
  ```sh
94
- dsh plugin --profile web add dsh-grok-subscription@1.0.9
94
+ dsh plugin --profile web add dsh-grok-subscription@1.0.10
95
95
  ```
96
96
 
97
97
  DSH handles target selection, the profile lock, dependency resolution, and bundle activation.
@@ -146,7 +146,8 @@ Restart DSH manually afterwards, then:
146
146
  - Settings can show the used and remaining percentages the backend reports, and says so plainly instead of guessing when the read fails;
147
147
  - Supports CLI login, device-code login, pull from the Grok CLI, and logout;
148
148
  - Renews the access token automatically: near the end of its 6-hour life, or on a 401, it runs the official `grok` CLI and retries once, so a session never lapses mid-use; the chat model list refreshes whenever sign-in state changes;
149
- - When subscription routing is unavailable it reports an error instead of silently switching to another paid route.
149
+ - When subscription routing is unavailable it reports an error instead of silently switching to another paid route;
150
+ - Prefix caching follows xAI's automatic prompt-cache semantics: one DSH session keeps a stable `prompt_cache_key`, tool definitions stay in a stable order, and the fallback adapter preserves and replays `reasoning.encrypted_content` unchanged. Server-side eviction can still miss, and the plugin never invents a hit rate.
150
151
 
151
152
  ### Composer quota
152
153
 
@@ -208,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
208
209
 
209
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;
210
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;
211
213
  - **Nothing changed after upgrading**: the plugin keeps running the old `lib/` — reinstall the plugin, restart `dsh web`, and hard-refresh (Ctrl+Shift+R);
212
214
  - **It reports `auth.json` permissions**: apply the `chmod 600` above; the plugin refuses symlinks and group- or other-readable files;
213
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
@@ -25,13 +25,13 @@
25
25
 
26
26
  ## 三步开始
27
27
 
28
- 1. **安装插件**:在终端运行下面的命令;指定候选版本时填写完整的 `包名@版本`,例如 `dsh-grok-subscription@1.0.9`。
28
+ 1. **安装插件**:在终端运行下面的命令;指定候选版本时填写完整的 `包名@版本`,例如 `dsh-grok-subscription@1.0.10`。
29
29
 
30
30
  ```sh
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/`。
@@ -91,7 +91,7 @@ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
91
91
  也可以按 npm 上的已发布版本安装:
92
92
 
93
93
  ```sh
94
- dsh plugin --profile web add dsh-grok-subscription@1.0.9
94
+ dsh plugin --profile web add dsh-grok-subscription@1.0.10
95
95
  ```
96
96
 
97
97
  目标选择、profile 锁、依赖解析和 bundle 激活均由 DSH 负责。
@@ -146,7 +146,8 @@ dsh --profile web --dump-config
146
146
  - 设置页可查看服务端返回的用量与剩余百分比,失败时明确提示而不是显示猜测值;
147
147
  - 支持 CLI 登录、设备码登录、从 Grok CLI 拉取和登出;登录状态变化后 Chat 模型列表自动刷新;
148
148
  - access token 到期自动续期:临近 6 小时有效期或收到 401 时,调用官方 `grok` CLI 刷新会话并重试一次,不会中途要求手动重新登录;
149
- - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
149
+ - 订阅路由不可用时明确报错,不会静默切换到其他付费路由;
150
+ - 前缀缓存按 xAI 的自动缓存语义优化:同一 DSH 会话使用稳定的 `prompt_cache_key`,工具定义保持稳定顺序,回退适配器会保存并原样回放 `reasoning.encrypted_content`。缓存仍可能因服务端逐出而失效,插件不虚构命中率。
150
151
 
151
152
  ### 输入框额度
152
153
 
@@ -208,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
208
209
 
209
210
  - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` 命令;
210
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 继承;
211
213
  - **升级后界面没变化**:插件会继续运行旧的 `lib/`,请重新安装插件并重启 `dsh web`,然后硬刷新(Ctrl+Shift+R);
212
214
  - **提示 `auth.json` 权限不正确**:按上面的 `chmod 600` 处理;插件拒绝读取符号链接或组/其他用户可读的文件;
213
215
  - **出现「没有回复」**:请升级到 `1.0.1` 或更高版本。`1.0.0` 存在一个缺陷:工具调用轮次会因流片段无法无损序列化而整轮中止,界面上完全没有回复。
package/lib/client.js CHANGED
@@ -26,6 +26,7 @@ __export(client_exports, {
26
26
  RPC_CALL_TIMEOUT_MS: () => RPC_CALL_TIMEOUT_MS,
27
27
  SectionBoundary: () => SectionBoundary,
28
28
  UsagePanel: () => UsagePanel,
29
+ VersionCard: () => VersionCard,
29
30
  apply: () => apply,
30
31
  callRpc: () => callRpc,
31
32
  inject: () => inject,
@@ -38,7 +39,7 @@ var import_react2 = require("react");
38
39
  var zh = {
39
40
  nav: "Grok \u8BA2\u9605",
40
41
  title: "Grok \u8BA2\u9605",
41
- subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.9",
42
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.18",
42
43
  signedIn: "\u5DF2\u767B\u5F55",
43
44
  signedOut: "\u672A\u767B\u5F55",
44
45
  account: "\u8D26\u6237",
@@ -53,8 +54,12 @@ var zh = {
53
54
  loginDevice: "\u8BBE\u5907\u7801\u767B\u5F55",
54
55
  pull: "\u4ECE Grok CLI \u62C9\u53D6",
55
56
  logout: "\u9000\u51FA\u767B\u5F55",
56
- 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",
57
- 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",
58
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",
59
64
  loginHelp: "\u767B\u5F55\u65B9\u5F0F\u8BF4\u660E",
60
65
  refreshCatalog: "\u5237\u65B0\u6A21\u578B\u76EE\u5F55",
@@ -90,12 +95,31 @@ var zh = {
90
95
  composerQuotaDetails: "\u6BCF\u5468\u989D\u5EA6",
91
96
  composerQuotaRemaining: "\u6BCF\u5468\u989D\u5EA6 \u5269\u4F59 {remaining}%",
92
97
  composerQuotaResets: "\u91CD\u7F6E\u4E8E {reset}",
93
- composerQuotaResetUnknown: "\u91CD\u7F6E\u65F6\u95F4\u672A\u77E5"
98
+ composerQuotaResetUnknown: "\u91CD\u7F6E\u65F6\u95F4\u672A\u77E5",
99
+ versionTitle: "\u63D2\u4EF6\u7248\u672C",
100
+ versionCurrent: "\u5F53\u524D\u7248\u672C",
101
+ versionLatest: "\u6700\u65B0\u7248\u672C",
102
+ versionCheck: "\u68C0\u67E5\u66F4\u65B0",
103
+ versionChecking: "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026",
104
+ versionFailed: "\u65E0\u6CD5\u68C0\u67E5\u6700\u65B0\u7248\u672C\u3002",
105
+ versionUpToDate: "\u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002",
106
+ versionAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{version}\u3002",
107
+ versionLinked: "\u5F53\u524D\u4E3A\u672C\u5730\u5F00\u53D1\u5B89\u88C5\uFF0C\u8BF7\u5728\u63D2\u4EF6\u4ED3\u5E93\u62C9\u53D6\u6700\u65B0\u4EE3\u7801\u5E76\u91CD\u65B0\u6784\u5EFA\u3002",
108
+ versionManual: "\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C dsh plugin --profile web add dsh-grok-subscription@latest \u5B8C\u6210\u66F4\u65B0\u3002",
109
+ versionUpdate: "\u66F4\u65B0\u63D2\u4EF6",
110
+ versionUpdating: "\u6B63\u5728\u66F4\u65B0\u2026",
111
+ versionUpdateFailed: "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u6216\u5728\u7EC8\u7AEF\u624B\u52A8\u66F4\u65B0\u3002",
112
+ versionUpdated: "\u5DF2\u66F4\u65B0\u5230 v{version}\u3002",
113
+ versionUpdatedHint: "\u65B0\u7248\u672C\u9700\u8981\u91CD\u542F DSH \u670D\u52A1\u624D\u80FD\u5B8C\u5168\u751F\u6548\uFF1B\u4EC5\u5237\u65B0\u754C\u9762\u4E0D\u4F1A\u66F4\u65B0\u5BBF\u4E3B\u8FDB\u7A0B\u4E2D\u5DF2\u52A0\u8F7D\u7684\u63D2\u4EF6\u3002",
114
+ versionRestart: "\u91CD\u542F DSH \u670D\u52A1",
115
+ versionRestartHint: "\u63D2\u4EF6\u65E0\u6CD5\u5B89\u5168\u5730\u91CD\u542F\u5BBF\u4E3B\u8FDB\u7A0B\uFF1A\u8BF7\u5728\u8FD0\u884C DSH \u7684\u7EC8\u7AEF\u6309 Ctrl+C \u7ED3\u675F\u8FDB\u7A0B\uFF0C\u518D\u91CD\u65B0\u8FD0\u884C dsh web\u3002",
116
+ versionRefresh: "\u5237\u65B0\u754C\u9762",
117
+ versionLater: "\u7A0D\u540E"
94
118
  };
95
119
  var en = {
96
120
  nav: "Grok Subscription",
97
121
  title: "Grok Subscription",
98
- subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.9",
122
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.18",
99
123
  signedIn: "Signed in",
100
124
  signedOut: "Signed out",
101
125
  account: "Account",
@@ -110,8 +134,12 @@ var en = {
110
134
  loginDevice: "Device-code login",
111
135
  pull: "Pull from Grok CLI",
112
136
  logout: "Log out",
113
- loginHint: "Runs grok login in the terminal that started DSH. Finish the browser or CLI prompt there, then wait for sync.",
114
- 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.",
115
143
  pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files) and stores only the short-lived access token in DSH.",
116
144
  loginHelp: "How signing in works",
117
145
  refreshCatalog: "Refresh model catalog",
@@ -147,7 +175,26 @@ var en = {
147
175
  composerQuotaDetails: "Weekly quota",
148
176
  composerQuotaRemaining: "Weekly quota \xB7 {remaining}% left",
149
177
  composerQuotaResets: "Resets {reset}",
150
- composerQuotaResetUnknown: "Reset time unknown"
178
+ composerQuotaResetUnknown: "Reset time unknown",
179
+ versionTitle: "Plugin version",
180
+ versionCurrent: "Current version",
181
+ versionLatest: "Latest version",
182
+ versionCheck: "Check for updates",
183
+ versionChecking: "Checking for updates\u2026",
184
+ versionFailed: "Could not check the latest version.",
185
+ versionUpToDate: "You are on the latest version.",
186
+ versionAvailable: "A new version v{version} is available.",
187
+ versionLinked: "Installed from a local checkout; pull and rebuild the plugin repository to update.",
188
+ versionManual: "Run dsh plugin --profile web add dsh-grok-subscription@latest in a terminal to update.",
189
+ versionUpdate: "Update plugin",
190
+ versionUpdating: "Updating\u2026",
191
+ versionUpdateFailed: "The update failed. Try again or update manually in a terminal.",
192
+ versionUpdated: "Updated to v{version}.",
193
+ versionUpdatedHint: "The new version takes full effect after a DSH restart; refreshing the page alone does not reload the plugin inside the host process.",
194
+ versionRestart: "Restart DSH",
195
+ versionRestartHint: "The plugin cannot safely restart its host process: press Ctrl+C in the terminal running DSH, then run dsh web again.",
196
+ versionRefresh: "Refresh page",
197
+ versionLater: "Later"
151
198
  };
152
199
 
153
200
  // src/rpc-contract.js
@@ -160,7 +207,9 @@ var RPC_ENDPOINTS = Object.freeze([
160
207
  "logout",
161
208
  "catalog/refresh",
162
209
  "usage",
163
- "usage/refresh"
210
+ "usage/refresh",
211
+ "plugin/version",
212
+ "plugin/update"
164
213
  ]);
165
214
  function createRpcClient(transport) {
166
215
  return Object.freeze({
@@ -599,12 +648,14 @@ var inject = [
599
648
  "slots",
600
649
  "locale",
601
650
  "connection",
602
- "settingsScope",
603
651
  "modelDirectories",
604
652
  "sessions",
605
653
  "remote"
606
654
  ];
607
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;
608
659
  function pickCopy(locale) {
609
660
  const language = typeof locale === "string" ? locale : locale?.language ?? locale?.lang;
610
661
  const resolved = language ?? (typeof navigator !== "undefined" ? navigator.language : "zh-CN");
@@ -620,7 +671,7 @@ function formatPercent(value) {
620
671
  const rounded = Math.round(value * 10) / 10;
621
672
  return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
622
673
  }
623
- async function callRpc(rpc, endpoint, payload = {}) {
674
+ async function callRpc(rpc, endpoint, payload = {}, timeoutMs = RPC_CALL_TIMEOUT_MS) {
624
675
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
625
676
  let timer;
626
677
  try {
@@ -631,8 +682,8 @@ async function callRpc(rpc, endpoint, payload = {}) {
631
682
  controller?.abort();
632
683
  } catch {
633
684
  }
634
- reject(new Error(`Request timed out after ${RPC_CALL_TIMEOUT_MS}ms`));
635
- }, RPC_CALL_TIMEOUT_MS);
685
+ reject(new Error(`Request timed out after ${timeoutMs}ms`));
686
+ }, timeoutMs);
636
687
  });
637
688
  return unwrap(await Promise.race([call, timeout]));
638
689
  } finally {
@@ -687,6 +738,90 @@ function DiagnosticsRows({ diagnostics, t }) {
687
738
  ] })
688
739
  ] });
689
740
  }
741
+ function fillTemplate2(template, values) {
742
+ return Object.entries(values).reduce(
743
+ (text, [key, value]) => text.replaceAll(`{${key}}`, value),
744
+ template
745
+ );
746
+ }
747
+ function VersionCard({ call, t }) {
748
+ const [info, setInfo] = (0, import_react2.useState)();
749
+ const [loading, setLoading] = (0, import_react2.useState)(true);
750
+ const [error, setError] = (0, import_react2.useState)(false);
751
+ const [updating, setUpdating] = (0, import_react2.useState)(false);
752
+ const [updateFailed, setUpdateFailed] = (0, import_react2.useState)(false);
753
+ const [updated, setUpdated] = (0, import_react2.useState)();
754
+ const [restartHint, setRestartHint] = (0, import_react2.useState)(false);
755
+ const generation = (0, import_react2.useRef)(0);
756
+ const load = (force) => {
757
+ const current = ++generation.current;
758
+ setLoading(true);
759
+ setError(false);
760
+ void call("plugin/version", { force }).then((value) => {
761
+ if (generation.current === current) setInfo(value);
762
+ }).catch(() => {
763
+ if (generation.current === current) setError(true);
764
+ }).finally(() => {
765
+ if (generation.current === current) setLoading(false);
766
+ });
767
+ };
768
+ (0, import_react2.useEffect)(() => {
769
+ load(false);
770
+ return () => {
771
+ generation.current += 1;
772
+ };
773
+ }, []);
774
+ const update = () => {
775
+ setUpdating(true);
776
+ setUpdateFailed(false);
777
+ void call("plugin/update").then((value) => {
778
+ setUpdated(value);
779
+ setRestartHint(false);
780
+ }).catch(() => setUpdateFailed(true)).finally(() => setUpdating(false));
781
+ };
782
+ const installKind = info?.install?.kind;
783
+ const showStatus = info !== void 0 && updated === void 0;
784
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsCard", children: [
785
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsCardHead", children: [
786
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: t("versionTitle") }),
787
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gsSpacer" }),
788
+ updated === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: loading || updating, onClick: () => load(true), children: loading ? t("versionChecking") : t("versionCheck") }) : null
789
+ ] }),
790
+ loading && info === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--busy", role: "status", children: t("versionChecking") }) : null,
791
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--error", role: "alert", children: t("versionFailed") }) : null,
792
+ info !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRows", children: [
793
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRow", children: [
794
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("versionCurrent") }),
795
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gsRowValue", children: [
796
+ "v",
797
+ info.current
798
+ ] })
799
+ ] }),
800
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRow", children: [
801
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("versionLatest") }),
802
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gsRowValue", children: [
803
+ "v",
804
+ info.latest
805
+ ] })
806
+ ] })
807
+ ] }) : null,
808
+ showStatus ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", role: "status", children: info.updateAvailable ? fillTemplate2(t("versionAvailable"), { version: info.latest }) : t("versionUpToDate") }) : null,
809
+ showStatus && info.updateAvailable && installKind === "npm" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gsActions", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: updating, onClick: update, children: updating ? t("versionUpdating") : t("versionUpdate") }) }) : null,
810
+ showStatus && installKind === "link" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionLinked") }) : null,
811
+ showStatus && info.updateAvailable && installKind === "unknown" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionManual") }) : null,
812
+ updateFailed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--error", role: "alert", children: t("versionUpdateFailed") }) : null,
813
+ updated !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "status", children: [
814
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: fillTemplate2(t("versionUpdated"), { version: updated.version }) }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionUpdatedHint") }),
816
+ restartHint ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("versionRestartHint") }) : null,
817
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsActions", children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", onClick: () => setRestartHint(true), children: t("versionRestart") }),
819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", onClick: () => window.location.reload(), children: t("versionRefresh") }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", onClick: () => setUpdated(void 0), children: t("versionLater") })
821
+ ] })
822
+ ] }) : null
823
+ ] });
824
+ }
690
825
  function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
691
826
  const ok = usage?.status === "ok";
692
827
  const usedNumber = ok && Number.isFinite(usage.usedPercent) ? usage.usedPercent : void 0;
@@ -751,6 +886,7 @@ function GrokSubscriptionSection({ rpc, t }) {
751
886
  const [error, setError] = (0, import_react2.useState)("");
752
887
  const [notice, setNotice] = (0, import_react2.useState)("");
753
888
  const [status, setStatus] = (0, import_react2.useState)(void 0);
889
+ const [loginPending, setLoginPending] = (0, import_react2.useState)(void 0);
754
890
  const initialUsageKick = (0, import_react2.useRef)(false);
755
891
  const applyPartial = (value) => {
756
892
  if (value?.account || value?.catalog || value?.usage) {
@@ -789,8 +925,29 @@ function GrokSubscriptionSection({ rpc, t }) {
789
925
  }).catch(() => {
790
926
  });
791
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]);
792
948
  const run = async (endpoint) => {
793
949
  const isUsageRefresh = endpoint === "usage/refresh";
950
+ const isLogin = endpoint === "login/cli" || endpoint === "login/device";
794
951
  if (isUsageRefresh) {
795
952
  setUsageBusy(true);
796
953
  } else {
@@ -800,7 +957,7 @@ function GrokSubscriptionSection({ rpc, t }) {
800
957
  setNotice("");
801
958
  let value;
802
959
  try {
803
- value = await callRpc(rpc, endpoint, {});
960
+ value = await callRpc(rpc, endpoint, {}, isLogin ? LOGIN_CALL_TIMEOUT_MS : RPC_CALL_TIMEOUT_MS);
804
961
  if (value?.account || value?.catalog || value?.usage) {
805
962
  applyPartial(value);
806
963
  } else {
@@ -809,6 +966,9 @@ function GrokSubscriptionSection({ rpc, t }) {
809
966
  if (value?.ok === false && value.error) {
810
967
  setNotice("");
811
968
  setError(value.error);
969
+ } else if (value?.pending === true) {
970
+ setLoginPending({ url: value.loginUrl, code: value.userCode, warning: value.warning });
971
+ setNotice("");
812
972
  } else if (value?.ok !== false) {
813
973
  const successKey = {
814
974
  pull: "pullOk",
@@ -830,7 +990,7 @@ function GrokSubscriptionSection({ rpc, t }) {
830
990
  if (isUsageRefresh) setUsageBusy(false);
831
991
  else setBusy("");
832
992
  }
833
- 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;
834
994
  if (shouldFollowUp) kickFollowUpRefresh();
835
995
  };
836
996
  const account = status?.account;
@@ -850,12 +1010,24 @@ function GrokSubscriptionSection({ rpc, t }) {
850
1010
  signedIn && account?.maskedAccount ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gsAccount", children: account.maskedAccount }) : null
851
1011
  ] }),
852
1012
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsActions", children: [
853
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy), onClick: () => void run("login/cli"), children: t("loginCli") }),
854
- /* @__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") }),
855
1015
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("pull"), children: t("pull") }),
856
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") })
857
1017
  ] }),
858
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,
859
1031
  notice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: notice }) : null,
860
1032
  error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--error", children: [
861
1033
  t("error"),
@@ -877,6 +1049,18 @@ function GrokSubscriptionSection({ rpc, t }) {
877
1049
  ] })
878
1050
  ] })
879
1051
  ] }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1053
+ VersionCard,
1054
+ {
1055
+ t,
1056
+ call: (endpoint, payload) => callRpc(
1057
+ rpc,
1058
+ endpoint,
1059
+ payload,
1060
+ endpoint === "plugin/update" ? 19e4 : RPC_CALL_TIMEOUT_MS
1061
+ )
1062
+ }
1063
+ ),
880
1064
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
881
1065
  UsagePanel,
882
1066
  {