dsh-grok-subscription 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-grok-subscription contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # dsh-grok-subscription
2
+
3
+ 一个第一方形态的 **DeepSeek Harness 社区插件**:复用官方 Grok Build CLI 的 SuperGrok / X Premium 登录,在 DSH 中提供独立的 `grok-build` 模型路由,不覆盖 DSH 内置的 `xai` API-key 路由。
4
+
5
+ ## 它做什么
6
+
7
+ - 读取 `${GROK_HOME:-~/.grok}/auth.json` 中的 OAuth 会话(不接受纯 API-key 项作为订阅登录)。
8
+ - 通过 DSH credentials 仅保存短期 access token;refresh token 不离开 Grok CLI 文件。
9
+ - 推理使用 OpenAI Responses:`https://cli-chat-proxy.grok.com/v1/responses`,不是 `api.x.ai`。
10
+ - 登录后从 `GET https://cli-chat-proxy.grok.com/v1/models-v2` 获取账号可用模型;实时目录不可用或为空时使用小型静态目录(包含 `grok-4.7`)。`grok-4.7` 优先来自实时 `models-v2` 返回。
11
+ - 未登录时仍注册 adapter,但模型列表为 **0**(fail closed)。
12
+ - 设置页显示脱敏账户、登录状态、模型与最近一次目录错误,并提供 CLI 登录、设备码登录、从 Grok CLI 拉取、退出登录。
13
+ - Pull / 登录 / 退出 / 目录刷新后通过 `llm/adapters-updated` 通知 Web 客户端刷新 Chat / New Session 模型选择器。
14
+
15
+ ## 安装
16
+
17
+ 要求 Node.js `^22.19.0 || >=24`,并建议先安装官方 `grok` CLI。
18
+
19
+ ```bash
20
+ grok login
21
+ # 从 GitHub 安装(推荐)
22
+ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
23
+ # 或本地路径
24
+ # dsh plugin --profile web add /absolute/path/to/dsh-grok-subscription
25
+ # 或 npm(发布后)
26
+ # dsh plugin --profile web add dsh-grok-subscription@0.1.3
27
+ ```
28
+
29
+ 然后重启 `dsh web`,打开 **Settings → Grok 订阅**。也可以在设置页点击“CLI 登录”或“设备码登录”;设备码流程会在启动 DSH 的终端中显示提示。登录完成后点击“从 Grok CLI 拉取”可立即同步;Chat 模型列表会随之刷新。
30
+
31
+ 升级到新版本后请重新安装插件(`add` 同一路径)并重启 `dsh web`,否则会继续跑旧的 `lib/`。
32
+
33
+ 若权限不正确:
34
+
35
+ ```bash
36
+ chmod 600 "${GROK_HOME:-$HOME/.grok}/auth.json"
37
+ ```
38
+
39
+ 开发检查:
40
+
41
+ ```bash
42
+ npm install
43
+ npm test
44
+ npm run build
45
+ ```
46
+
47
+ ## v0.1.3
48
+
49
+ 修复多轮 Chat 中助手正文不显示的问题:自定义 Responses 流在 `block-end` 时把已累计的 text/reasoning 写成空字符串,Harness `BlockAssembler` 会用空块覆盖流式增量,导致第 2 轮起界面只见思考(如 “Deep diving…”)不见正文。同时为 `grok-build` 强制 `include: reasoning.encrypted_content`(与 pi-ai 对 `xai` 的处理对齐),保证推理密文可回放。
50
+
51
+ ## v0.1.2
52
+
53
+ 设置页 Pull / 登录 / 退出成功后显示明确的内联成功反馈。
54
+
55
+ ## v0.1.1
56
+
57
+ 修复 Chat / New Session 模型选择器在 Settings 已 Pull 到 `models-v2` 后仍只显示 DeepSeek 模型的问题:
58
+
59
+ - PiAiAdapter 的 `profiles` 每次重建 provider,且 `getModels()` 读取当前 `session.models()`(不再依赖一次性的 `models: []` + `fetchModels`)。
60
+ - Pull / 登录 / 退出 / 目录刷新后发出 `llm/adapters-updated`,让选择器刷新。
61
+
62
+ v0.1.0 范围仍适用:仅文本/工具调用所需的 Responses 流、账号同步、动态模型目录和设置页。没有图片生成、画板、额度预测或 Codex 功能。官方 CLI 负责登录与 refresh-token 生命周期;插件不会实现或写回供应商 OAuth refresh。
63
+
64
+ ## 注意事项
65
+
66
+ - 这是社区插件,不是 xAI、Grok 或 DeepSeek 官方产品。
67
+ - 订阅账号用于非官方客户端可能处于供应商条款灰色地带;**只使用你自己的账号**,风险自担。
68
+ - 官方文档没有发布完整 HTTP wire protocol;代理协议、请求头、模型 ID 或权限规则可能随时改变。
69
+ - 设置页发起 CLI 登录时,浏览器/设备码提示由官方 CLI 处理;远程部署请优先用 `grok login --device-auth`。
70
+
71
+ ## English
72
+
73
+ `dsh-grok-subscription` is a small community DSH plugin that reuses an official Grok Build CLI SuperGrok/X Premium session. It adds a separate `grok-build` route, calls the subscription Responses proxy, discovers models from `/v1/models-v2` (with a signed-in-only fallback including `grok-4.7`), and never exposes tokens to browser RPC. After Pull/login/logout the plugin emits `llm/adapters-updated` so the Chat model picker refreshes. Install locally with:
74
+
75
+ ```bash
76
+ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
77
+ ```
78
+
79
+ Reinstall after upgrades, restart `dsh web`, then open **Settings → Grok Subscription**.
80
+
81
+ ### v0.1.3
82
+ Fixes multi-turn Chat where assistant body text vanished from turn 2 onward: the custom Responses SSE mapper ended text/reasoning blocks with empty strings, so Harness `BlockAssembler` replaced streamed deltas with blanks (UI could show "Deep diving…" then no body). Also forces `include: reasoning.encrypted_content` for `grok-build` so encrypted reasoning can replay on later turns. This is unofficial, may fall into a vendor-ToS gray area, and the undocumented wire protocol can change. Use your own account only.
83
+
84
+ ## 还需要实机验证的部分
85
+
86
+ 单元测试覆盖 auth.json 解析、目录解析、请求头、RPC 脱敏、signed-out 空目录与 catalog-change 回调,不访问网络。以下需要本机已安装的 DSH + 已登录 SuperGrok 才能确认:
87
+
88
+ - Settings 页槽位、locale、connection.rpc 与 DSH web client 的实际装配
89
+ - Pull 后 Chat / New Session 是否出现 `grok-4.7` 等 `grok-build` 模型并可发起推理
90
+ - `PiAiAdapter` 对 `grok-build` 路由的流式映射(若宿主没有 `@deepseek-ai/dsh-llm-pi-ai` / `@earendil-works/pi-ai`,插件会降级到自带的 Responses SSE MVP adapter)
91
+ - 官方 CLI `grok login` / `grok login --device-auth` 从 Settings 按钮拉起后的终端交互
92
+ - 实时 `GET /v1/models-v2` 是否返回 `grok-4.7`(目录为空时使用静态回退)
93
+
94
+ ## 致谢 / Acknowledgments
95
+
96
+ - [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
97
+ - [dsh-kimi-subscription](https://github.com/BaronCyrus/dsh-kimi-subscription)(插件形态与发布方式参考)
98
+ - [dsh-codex-subscription](https://github.com/WSL043/dsh-codex-subscription)
99
+
100
+ ## License
101
+
102
+ [MIT](LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # 安全说明 / Security
2
+
3
+ - 插件只读取 `${GROK_HOME:-~/.grok}/auth.json`,从不写回;拒绝符号链接、非普通文件、非当前用户所有的文件,以及组/其他用户可读写执行的文件。Unix 下应为 `0600`。
4
+ - 只有短期 access token 会通过 DSH credentials 服务保存;refresh token 始终留在 Grok CLI 文件中。
5
+ - token 不会写入日志,也不会通过浏览器 RPC 返回。RPC 只返回脱敏账户、状态、模型与无敏感信息的错误摘要。
6
+ - 订阅 token 只发送到固定 HTTPS 主机 `cli-chat-proxy.grok.com`。
7
+ - 请勿在公开 issue、截图或诊断中粘贴 `auth.json`、token 或登录回调 URL。
8
+
9
+ This community plugin runs with DSH host privileges. Review the source before installation and use only your own account.
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: grok-subscription
3
+ name: 'dsh-grok-subscription'
package/lib/client.js ADDED
@@ -0,0 +1,265 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-grok-subscription", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client.jsx
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ GrokSubscriptionSection: () => GrokSubscriptionSection,
24
+ apply: () => apply,
25
+ inject: () => inject,
26
+ pickCopy: () => pickCopy
27
+ });
28
+ module.exports = __toCommonJS(client_exports);
29
+ var import_react = require("react");
30
+
31
+ // src/locales.js
32
+ var zh = {
33
+ nav: "Grok \u8BA2\u9605",
34
+ title: "Grok \u8BA2\u9605",
35
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002",
36
+ signedIn: "\u5DF2\u767B\u5F55",
37
+ signedOut: "\u672A\u767B\u5F55",
38
+ account: "\u8D26\u6237",
39
+ unknownAccount: "\u5DF2\u4FDD\u5B58\u4F1A\u8BDD\uFF08\u8D26\u6237\u5DF2\u8131\u654F\uFF09",
40
+ models: "\u53EF\u7528\u6A21\u578B",
41
+ noModels: "\u672A\u767B\u5F55\u65F6\u4E0D\u66B4\u9732\u4EFB\u4F55\u6A21\u578B\u3002",
42
+ catalogSourceLive: "\u6765\u6E90\uFF1A\u5B9E\u65F6 models-v2",
43
+ catalogSourceFallback: "\u6765\u6E90\uFF1A\u9759\u6001\u56DE\u9000\u76EE\u5F55\uFF08\u542B grok-4.7\uFF09",
44
+ catalogSourceSignedOut: "\u767B\u5F55\u540E\u624D\u4F1A\u5217\u51FA\u6A21\u578B\u3002",
45
+ catalogError: "\u76EE\u5F55\u9519\u8BEF",
46
+ loginCli: "CLI \u767B\u5F55",
47
+ loginDevice: "\u8BBE\u5907\u7801\u767B\u5F55",
48
+ pull: "\u4ECE Grok CLI \u62C9\u53D6",
49
+ logout: "\u9000\u51FA\u767B\u5F55",
50
+ 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",
51
+ 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",
52
+ 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",
53
+ busy: "\u5904\u7406\u4E2D\u2026",
54
+ error: "\u64CD\u4F5C\u5931\u8D25",
55
+ pullOk: "\u5DF2\u4ECE Grok CLI \u62C9\u53D6\u6210\u529F",
56
+ loginOk: "\u767B\u5F55\u5B8C\u6210\uFF0C\u4F1A\u8BDD\u5DF2\u540C\u6B65",
57
+ logoutOk: "\u5DF2\u9000\u51FA\u767B\u5F55",
58
+ cliMissing: "\u672A\u53D1\u73B0 grok CLI\u3002\u8BF7\u5148\u5B89\u88C5\u5B98\u65B9 CLI \u5E76\u8FD0\u884C grok login\uFF0C\u7136\u540E\u62C9\u53D6\u3002",
59
+ caveats: "\u793E\u533A\u63D2\u4EF6\uFF0C\u4E0D\u662F\u5B98\u65B9\u4EA7\u54C1\u3002\u8BA2\u9605\u7528\u4E8E\u975E\u5B98\u65B9\u5BA2\u6237\u7AEF\u53EF\u80FD\u5904\u4E8E\u4F9B\u5E94\u5546\u6761\u6B3E\u7070\u8272\u5730\u5E26\uFF1B\u53EA\u4F7F\u7528\u4F60\u81EA\u5DF1\u7684\u8D26\u53F7\u3002\u534F\u8BAE\u53EF\u80FD\u53D8\u5316\uFF0C\u56E0\u4E3A\u5B98\u65B9\u6587\u6863\u672A\u516C\u5F00\u5B8C\u6574 HTTP wire protocol\u3002"
60
+ };
61
+ var en = {
62
+ nav: "Grok Subscription",
63
+ title: "Grok Subscription",
64
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY.",
65
+ signedIn: "Signed in",
66
+ signedOut: "Signed out",
67
+ account: "Account",
68
+ unknownAccount: "Saved session (account masked)",
69
+ models: "Available models",
70
+ noModels: "No models are exposed until you sign in.",
71
+ catalogSourceLive: "Source: live models-v2",
72
+ catalogSourceFallback: "Source: static fallback catalog (includes grok-4.7)",
73
+ catalogSourceSignedOut: "Models appear after sign-in.",
74
+ catalogError: "Catalog error",
75
+ loginCli: "CLI login",
76
+ loginDevice: "Device-code login",
77
+ pull: "Pull from Grok CLI",
78
+ logout: "Log out",
79
+ loginHint: "Runs grok login in the terminal that started DSH. Finish the browser or CLI prompt there, then wait for sync.",
80
+ deviceHint: "The device-code prompt appears in the DSH terminal. You can also run grok login --device-auth yourself, then Pull from Grok CLI.",
81
+ pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files) and stores only the short-lived access token in DSH.",
82
+ busy: "Working\u2026",
83
+ error: "Request failed",
84
+ pullOk: "Pulled from Grok CLI successfully",
85
+ loginOk: "Signed in and session synced",
86
+ logoutOk: "Signed out",
87
+ cliMissing: "The grok CLI was not found. Install it, run grok login, then pull.",
88
+ caveats: "Community plugin, not an official product. Using a subscription from an unofficial client may be a vendor-ToS gray area; use your own account only. The protocol can change because official docs do not publish the full HTTP wire protocol."
89
+ };
90
+
91
+ // src/rpc-contract.js
92
+ var CHANNEL = "/grok-subscription";
93
+ var RPC_ENDPOINTS = Object.freeze([
94
+ "status",
95
+ "login/cli",
96
+ "login/device",
97
+ "pull",
98
+ "logout",
99
+ "catalog/refresh"
100
+ ]);
101
+ function createRpcClient(transport) {
102
+ return Object.freeze({
103
+ call(channel, endpoint, payload, signal) {
104
+ if (channel !== CHANNEL || !RPC_ENDPOINTS.includes(endpoint)) {
105
+ throw new Error("Invalid Grok subscription RPC target");
106
+ }
107
+ return transport.call("/api", `grok-subscription/${endpoint}`, payload, signal);
108
+ }
109
+ });
110
+ }
111
+ function unwrap(response) {
112
+ if (!response?.ok) throw new Error(response?.error?.message ?? "Grok subscription RPC failed");
113
+ return response.value;
114
+ }
115
+
116
+ // src/constants.js
117
+ var LOCALE_NS = "settings.grokSubscription";
118
+ var SESSION_AUTH_MODES = Object.freeze(["oidc", "external", "web_login", "grok"]);
119
+ var API_KEY_AUTH_MODES = Object.freeze(["api_key"]);
120
+ var STREAM_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
121
+ var MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
122
+ var REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
123
+ var REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
124
+ var FALLBACK_MODEL_IDS = Object.freeze(["grok-4.7", "grok-4.6", "grok-4.5"]);
125
+
126
+ // src/client.jsx
127
+ var import_jsx_runtime = require("react/jsx-runtime");
128
+ var inject = ["slots", "locale", "connection", "settingsScope"];
129
+ function pickCopy(locale) {
130
+ const language = typeof locale === "string" ? locale : locale?.language ?? locale?.lang;
131
+ const resolved = language ?? (typeof navigator !== "undefined" ? navigator.language : "zh-CN");
132
+ return String(resolved).toLowerCase().startsWith("zh") ? zh : en;
133
+ }
134
+ function sourceLabel(source, t) {
135
+ if (source === "live") return t("catalogSourceLive");
136
+ if (source === "fallback") return t("catalogSourceFallback");
137
+ return t("catalogSourceSignedOut");
138
+ }
139
+ function GrokSubscriptionSection({ rpc, t }) {
140
+ const [busy, setBusy] = (0, import_react.useState)("");
141
+ const [error, setError] = (0, import_react.useState)("");
142
+ const [notice, setNotice] = (0, import_react.useState)("");
143
+ const [status, setStatus] = (0, import_react.useState)(void 0);
144
+ const load = async () => {
145
+ const value = unwrap(await rpc.call(CHANNEL, "status", {}));
146
+ setStatus(value);
147
+ setError("");
148
+ };
149
+ (0, import_react.useEffect)(() => {
150
+ void load().catch((item) => {
151
+ setNotice("");
152
+ setError(item instanceof Error ? item.message : String(item));
153
+ });
154
+ }, [rpc]);
155
+ const run = async (endpoint) => {
156
+ setBusy(endpoint);
157
+ setError("");
158
+ setNotice("");
159
+ try {
160
+ const value = unwrap(await rpc.call(CHANNEL, endpoint, {}));
161
+ if (value?.account || value?.catalog) setStatus((current) => ({ ...current, ...value }));
162
+ else await load();
163
+ if (value?.ok === false && value.error) {
164
+ setNotice("");
165
+ setError(value.error);
166
+ } else if (value?.ok !== false) {
167
+ const successKey = {
168
+ pull: "pullOk",
169
+ "login/cli": "loginOk",
170
+ "login/device": "loginOk",
171
+ logout: "logoutOk"
172
+ }[endpoint];
173
+ if (successKey) setNotice(t(successKey));
174
+ }
175
+ } catch (item) {
176
+ setNotice("");
177
+ setError(item instanceof Error ? item.message : String(item));
178
+ } finally {
179
+ setBusy("");
180
+ }
181
+ };
182
+ const account = status?.account;
183
+ const catalog = status?.catalog;
184
+ const models = catalog?.models ?? [];
185
+ const signedIn = account?.signedIn === true;
186
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "grokSubscription", children: [
187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
188
+ .grokSubscription { display: grid; gap: 12px; max-width: 42rem; }
189
+ .grokSubscription h2 { margin: 0 0 4px; font-size: 1.15rem; }
190
+ .grokSubscription p, .grokSubscription li { line-height: 1.5; }
191
+ .grokSubscription .muted { opacity: 0.78; font-size: 0.92rem; }
192
+ .grokSubscription .row { display: flex; flex-wrap: wrap; gap: 8px; }
193
+ .grokSubscription button { cursor: pointer; }
194
+ .grokSubscription .error { color: #b42318; }
195
+ .grokSubscription .notice { color: #067647; }
196
+ .grokSubscription ul { margin: 0; padding-left: 1.2rem; }
197
+ ` }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("title") }),
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("subtitle") })
201
+ ] }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
204
+ t("account"),
205
+ ": "
206
+ ] }),
207
+ signedIn ? `${t("signedIn")} \xB7 ${account.maskedAccount ?? t("unknownAccount")}` : t("signedOut")
208
+ ] }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(busy), onClick: () => void run("login/cli"), children: t("loginCli") }),
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(busy), onClick: () => void run("login/device"), children: t("loginDevice") }),
212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(busy), onClick: () => void run("pull"), children: t("pull") }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: Boolean(busy) || !signedIn, onClick: () => void run("logout"), children: t("logout") })
214
+ ] }),
215
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("busy") }) : null,
216
+ notice ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: notice }) : null,
217
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "error", children: [
218
+ t("error"),
219
+ ": ",
220
+ error
221
+ ] }) : null,
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("loginHint") }),
223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("deviceHint") }),
224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("pullHint") }),
225
+ status?.cliAvailable === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("cliMissing") }) : null,
226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("models") }),
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: sourceLabel(catalog?.source, t) }),
229
+ models.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("noModels") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: models.map((model) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
230
+ model.name,
231
+ " ",
232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: model.id })
233
+ ] }, model.id)) }),
234
+ catalog?.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "error", children: [
235
+ t("catalogError"),
236
+ ": ",
237
+ catalog.error
238
+ ] }) : null
239
+ ] }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: t("caveats") })
241
+ ] });
242
+ }
243
+ function apply(ctx) {
244
+ ctx.effect?.(() => ctx.locale?.register?.(LOCALE_NS, { zh, en }), "grok-subscription: copy");
245
+ const connection = ctx.get?.("connection") ?? ctx.connection;
246
+ const rpc = createRpcClient(connection.rpc);
247
+ const bound = ctx.locale?.bind?.(LOCALE_NS);
248
+ const t = (key) => {
249
+ try {
250
+ const value = bound?.(key);
251
+ if (typeof value === "string" && value && value !== key) return value;
252
+ } catch {
253
+ }
254
+ return pickCopy(ctx.locale)[key] ?? key;
255
+ };
256
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
257
+ name: "settings.section",
258
+ id: "grok-subscription",
259
+ order: 16,
260
+ label: () => t("nav"),
261
+ locale: LOCALE_NS,
262
+ inject: () => ({ rpc, t })
263
+ }, GrokSubscriptionSection));
264
+ }
265
+ return module.exports; } });