dsh-cursor-subscription 0.5.7 → 0.6.1

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/AGENTS.md CHANGED
@@ -32,6 +32,23 @@ Then ensure `dsh-cursor-subscription` is listed in the profile
32
32
  `package.json` under `dsh.profile.bundles` (the `dsh plugin add` command does
33
33
  this automatically for published packages).
34
34
 
35
+ pnpm (11+) applies a built-in 24-hour `minimumReleaseAge` supply-chain gate, so a
36
+ range such as `^0.5.0` never selects a version published within the last 24
37
+ hours. Right after a release, a plain `add` or `update` therefore falls back to
38
+ the newest release older than 24 hours — for a young package, the earliest
39
+ published version. Install the newest release by exempting this package by name
40
+ in the profile `pnpm-workspace.yaml`:
41
+
42
+ ```yaml
43
+ minimumReleaseAgeExclude:
44
+ - dsh-cursor-subscription
45
+ ```
46
+
47
+ Requesting one version explicitly (`dsh plugin --profile web add
48
+ dsh-cursor-subscription@0.5.8`) also works: pnpm then appends that single
49
+ version to the same list. Do not set `minimumReleaseAge: 0` just to install this
50
+ plugin; that drops the protection for every package in the profile.
51
+
35
52
  Update with `dsh plugin --profile web update dsh-cursor-subscription`.
36
53
  Uninstall with `dsh plugin --profile web remove dsh-cursor-subscription`.
37
54
 
@@ -44,7 +61,9 @@ dsh --profile web --dump-config
44
61
 
45
62
  Success requires:
46
63
 
47
- 1. The requested package version appears once.
64
+ 1. The requested package version appears once. A version older than the one
65
+ requested means pnpm's 24-hour release-age gate resolved the range; see
66
+ Install.
48
67
  2. `cursor-subscription` appears once in the composed config after install
49
68
  or update, and is absent after uninstall.
50
69
  3. No unrelated profile or plugin changed.
@@ -57,10 +76,33 @@ If the user authorizes a live check, restart DSH manually, open
57
76
  explicitly requests a live check, run one simple chat message and confirm the
58
77
  streamed reply appears in the conversation.
59
78
 
79
+ The section title carries the version of the installed build next to it. That
80
+ number comes from the plugin's own manifest over the account channel, so it
81
+ matches `dsh plugin --profile web list dsh-cursor-subscription --depth 0`; a
82
+ missing chip on a loaded panel means the version request failed, not that the
83
+ plugin is unloaded.
84
+
85
+ The panel also contributes languages: besides the built-in Simplified Chinese and
86
+ English, **Settings -> General -> Language** lists Traditional Chinese, Japanese,
87
+ Korean, Spanish, French, German, Italian, Brazilian Portuguese, Russian, and
88
+ Arabic, and the panel mirrors itself for Arabic. Those entries come from the
89
+ panel's own `ctx.locale.addLanguage` calls, so they only appear on a DSH whose
90
+ locale runtime supports language packs; elsewhere the panel silently stays
91
+ zh/en. A missing language in that list is a locale-runtime question, not a
92
+ plugin-load failure.
93
+
60
94
  ## Failure handling
61
95
 
62
96
  - If the settings page reports "无法读取 Cursor 状态" the loopback RPC failed;
63
97
  confirm the plugin bundle is listed once in the composed config.
98
+ - A boot failure reading `plugin tree failed to load: failed to apply loader
99
+ entry cursor-subscription ... cannot get property "webServer" without inject`
100
+ means a plugin build that mounted its account channel through
101
+ `connection.rpc.handle` ran against a DSH whose Connection plugin no longer
102
+ declares `webServer` (for example DSH 0.1.5-rc.1). Install version 0.5.8 or
103
+ later, whose channel mount passes an owner context that declares `webServer`.
104
+ Do not fix this by adding `webServer` to the plugin's own `inject`: the failing
105
+ read happens on the providing plugin's scope and stays broken.
64
106
  - A "Cursor subscription is not signed in" error on a model call means the
65
107
  credential store is empty; the user must complete the browser login flow.
66
108
  - Cursor's Agent protocol is undocumented and changes; a transport or
package/README.md CHANGED
@@ -12,10 +12,11 @@ Sign in to your Cursor account and use your Cursor subscription directly from De
12
12
  - Store credentials in DSH's local credential store and refresh access tokens automatically.
13
13
  - Discover models available to the current account dynamically through Cursor's `GetUsableModels`, with a built-in fallback list if discovery fails.
14
14
  - Stream conversations with reasoning output, image attachments, and DSH tool calls.
15
- - View sign-in status and token expiration in the settings page.
15
+ - View sign-in status and token expiration in the settings page, which labels itself with the installed build version.
16
16
  - View and manually refresh subscription usage, including requests, Auto + Composer usage, other-model (API) usage, per-model spend, on-demand spend, and billing cycle.
17
17
  - View and manually refresh the models available to the current account.
18
18
  - Configure the per-run tool-round limit and HTTP retry count, interval, and status codes from the settings page.
19
+ - Read the settings page in Simplified Chinese, English, Traditional Chinese, Japanese, Korean, Spanish, French, German, Italian, Brazilian Portuguese, Russian, or Arabic. The panel mirrors itself for Arabic; contributed languages join DSH's own language picker under **Settings → General → Language** and fall back to English outside the panel.
19
20
 
20
21
  ## Installation
21
22
 
@@ -33,6 +34,13 @@ dsh --profile web --dump-config
33
34
 
34
35
  The installed package list should contain exactly one `dsh-cursor-subscription`, and the composed configuration should contain exactly one `cursor-subscription` entry.
35
36
 
37
+ pnpm 11 and later ship a built-in 24-hour `minimumReleaseAge` gate, so a plain `add` never selects a version published within the last 24 hours. Right after a release, install it explicitly (`dsh plugin --profile web add dsh-cursor-subscription@0.5.8`) or exempt this package by name in the profile's `pnpm-workspace.yaml`:
38
+
39
+ ```yaml
40
+ minimumReleaseAgeExclude:
41
+ - dsh-cursor-subscription
42
+ ```
43
+
36
44
  ### Local Development Installation
37
45
 
38
46
  Run the following command in the profile directory, such as `%USERPROFILE%\.dsh\profiles\web`:
@@ -68,6 +76,8 @@ If DSH is running, restart it manually after installation or an update.
68
76
  - **DSH still reports that you are signed out after sign-in:** Make sure the browser completed the entire authorization flow and redirected to the completion page. Polling waits for up to approximately 2.5 minutes.
69
77
  - **The model list is empty:** `GetUsableModels` depends on the account type. If discovery fails, the plugin uses its built-in model list, and you can still enter a model name manually.
70
78
  - **Requests return 401:** If the access token has expired and the refresh token is no longer valid, sign in again from the settings page.
79
+ - **DSH fails to start with `cannot get property "webServer" without inject`:** The installed DSH's Connection plugin does not declare `webServer`, which the older channel registration relied on. Install version 0.5.8 or later of this plugin.
80
+ - **A plain install picks an old version:** pnpm's built-in 24-hour `minimumReleaseAge` gate skips releases published within the last day. Install an explicit version or exempt this package by name; see the installation section.
71
81
  - **The server protocol changed:** Cursor's Agent protocol is unpublished. If requests fail, check for a plugin update.
72
82
 
73
83
  ## Scope and Support
package/README_zh.md CHANGED
@@ -17,10 +17,11 @@
17
17
  - 登录凭据保存在本机 DSH credential 存储中,自动刷新访问令牌;
18
18
  - 通过 Cursor 的 `GetUsableModels` 动态发现当前账户可用的模型(失败时回退到内置列表);
19
19
  - 流式对话,支持思考过程(reasoning)、用户图片附件与 DSH 工具调用;
20
- - 设置页可查看登录状态与令牌有效期;
20
+ - 设置页可查看登录状态与令牌有效期,标题旁标出当前安装的插件版本号;
21
21
  - 设置页可查询订阅用量(包含请求、Auto + Composer、其他模型占比、各模型消费、按需消费、账单周期)并手动刷新;
22
22
  - 设置页可查看当前账户可用的模型列表并手动刷新;
23
- - 可在设置页调整单次任务工具轮次上限,以及 HTTP 重试次数、间隔和状态码。
23
+ - 可在设置页调整单次任务工具轮次上限,以及 HTTP 重试次数、间隔和状态码;
24
+ - 设置页支持简体中文、英文、繁体中文、日语、韩语、西班牙语、法语、德语、意大利语、巴西葡萄牙语、俄语和阿拉伯语;阿拉伯语下界面自动镜像为从右到左。新增语言会注册进 DSH 的语言列表(**设置 → 通用 → 语言**),面板之外的其他界面回退到英文。
24
25
 
25
26
  ## 安装
26
27
 
@@ -39,6 +40,16 @@ dsh --profile web --dump-config
39
40
  安装列表中应只有一个 `dsh-cursor-subscription`,配置中应只有一个
40
41
  `cursor-subscription` 条目。
41
42
 
43
+ pnpm 11 及以后内置了 24 小时的 `minimumReleaseAge` 供应链防护:普通 `add`
44
+ 不会选择 24 小时内发布的版本。刚发布完新版本时,请显式指定版本安装
45
+ (`dsh plugin --profile web add dsh-cursor-subscription@0.5.8`),或在 profile 的
46
+ `pnpm-workspace.yaml` 里按包名豁免:
47
+
48
+ ```yaml
49
+ minimumReleaseAgeExclude:
50
+ - dsh-cursor-subscription
51
+ ```
52
+
42
53
  ### 本地开发安装
43
54
 
44
55
  在 profile 目录(如 `%USERPROFILE%\.dsh\profiles\web`)执行:
@@ -81,6 +92,11 @@ dsh plugin --profile web remove dsh-cursor-subscription # 卸载
81
92
  - **模型列表为空**:`GetUsableModels` 依赖账户类型,失败时插件会使用内置模型列表,
82
93
  仍可手动输入模型名。
83
94
  - **请求报 401**:令牌过期且 refresh token 失效时,需要在设置页重新登录。
95
+ - **DSH 启动失败并报 `cannot get property "webServer" without inject`**:当前 DSH 的
96
+ Connection 插件不再自己声明 `webServer`,旧版插件注册账户通道时依赖了它。请安装
97
+ 0.5.8 或更高版本的本插件。
98
+ - **普通安装装到的是旧版本**:pnpm 内置的 24 小时 `minimumReleaseAge` 防护会跳过
99
+ 最近一天内发布的版本。请显式指定版本安装,或按包名豁免该包;见安装章节。
84
100
  - **服务端协议变更**:Cursor 的 Agent 协议是未公开接口,若请求失败请检查插件更新。
85
101
 
86
102
  ## 边界与支持
package/lib/client.js CHANGED
@@ -33,7 +33,6 @@ window.__ModuleLoader__.load({
33
33
  usageRefreshing: "刷新中…",
34
34
  usageNotSignedIn: "登录后可查看 Cursor 返回的用量。",
35
35
  usageLoading: "正在读取用量…",
36
- usageError: "无法读取用量",
37
36
  usageUpdated: "更新于 {value}",
38
37
  plan: "套餐",
39
38
  unlimited: "不限额",
@@ -43,7 +42,6 @@ window.__ModuleLoader__.load({
43
42
  autoUsage: "计划 Cursor 模型使用量",
44
43
  otherModels: "计划其他模型使用量",
45
44
  modelUsage: "模型用量",
46
- spendUsed: "已消费 ${used}",
47
45
  spendUsedOf: "已消费 ${used} / ${limit}",
48
46
  billingCycle: "账单周期",
49
47
  billingDaysLeft: "剩余 {value} 天",
@@ -54,7 +52,6 @@ window.__ModuleLoader__.load({
54
52
  models: "模型列表",
55
53
  modelsRefresh: "刷新模型列表",
56
54
  modelsRefreshing: "刷新中…",
57
- modelsEmpty: "暂无模型数据",
58
55
  modelsCount: "{value} 个模型",
59
56
  modelsLoading: "正在读取模型列表…",
60
57
  runtimeSettings: "运行设置",
@@ -96,7 +93,6 @@ window.__ModuleLoader__.load({
96
93
  usageRefreshing: "Refreshing…",
97
94
  usageNotSignedIn: "Sign in to read usage reported by Cursor.",
98
95
  usageLoading: "Reading usage…",
99
- usageError: "Could not read usage",
100
96
  usageUpdated: "Updated {value}",
101
97
  plan: "Plan",
102
98
  unlimited: "Unlimited",
@@ -106,11 +102,9 @@ window.__ModuleLoader__.load({
106
102
  autoUsage: "Plan Cursor model usage",
107
103
  otherModels: "Plan other model usage",
108
104
  modelUsage: "Model usage",
109
- spendUsed: "${used} spent",
110
105
  spendUsedOf: "${used} / ${limit} spent",
111
106
  billingCycle: "Billing cycle",
112
107
  billingDaysLeft: "{value} days left",
113
- planPercent: "Plan used {value}%",
114
108
  autoPercent: "Plan Cursor models {value}%",
115
109
  otherModelsPercent: "Plan other models {value}%",
116
110
  otherModelsUnknown: "This usage value has not been read yet",
@@ -118,7 +112,6 @@ window.__ModuleLoader__.load({
118
112
  models: "Models",
119
113
  modelsRefresh: "Refresh models",
120
114
  modelsRefreshing: "Refreshing…",
121
- modelsEmpty: "No model data yet",
122
115
  modelsCount: "{value} models",
123
116
  modelsLoading: "Reading model list…",
124
117
  runtimeSettings: "Runtime settings",
@@ -140,6 +133,647 @@ window.__ModuleLoader__.load({
140
133
  settingsSaveFailed: "Could not save runtime settings.",
141
134
  settingsInvalid: "Check the tool rounds, retry count, interval, and HTTP status codes.",
142
135
  };
136
+ const zhHant = {
137
+ nav: "Cursor 訂閱",
138
+ title: "Cursor 訂閱",
139
+ connected: "已登入",
140
+ disconnected: "未登入",
141
+ accountLoading: "正在讀取帳戶狀態…",
142
+ login: "瀏覽器登入",
143
+ logout: "登出",
144
+ cancel: "取消",
145
+ openLogin: "開啟登入頁",
146
+ waiting: "正在等待登入完成…",
147
+ failed: "登入失敗,請重試。",
148
+ loadFailed: "無法讀取 Cursor 狀態。",
149
+ note: "在瀏覽器登入 Cursor 帳戶後,即可在模型選擇器中使用 Cursor 訂閱模型。登入資訊僅保存在本機,不會上傳。",
150
+ expiresAt: "權杖有效至 {value}",
151
+ usage: "訂閱用量",
152
+ usageRefresh: "重新整理",
153
+ usageRefreshing: "重新整理中…",
154
+ usageNotSignedIn: "登入後可查看 Cursor 回報的用量。",
155
+ usageLoading: "正在讀取用量…",
156
+ usageUpdated: "更新於 {value}",
157
+ plan: "方案",
158
+ unlimited: "不限額",
159
+ includedRequests: "包含請求",
160
+ requestsUsed: "已用 {used} / {limit} 次請求({pct}%)",
161
+ onDemandSpend: "隨用隨付支出",
162
+ autoUsage: "方案 Cursor 模型用量",
163
+ otherModels: "方案其他模型用量",
164
+ modelUsage: "模型用量",
165
+ spendUsedOf: "已消費 ${used} / ${limit}",
166
+ billingCycle: "帳單週期",
167
+ billingDaysLeft: "剩餘 {value} 天",
168
+ autoPercent: "方案 Cursor 模型 {value}%",
169
+ otherModelsPercent: "方案其他模型 {value}%",
170
+ otherModelsUnknown: "尚未讀取到該用量",
171
+ modelSpendPct: "{model} 已用 ${used}({pct}%)",
172
+ models: "模型清單",
173
+ modelsRefresh: "重新整理模型清單",
174
+ modelsRefreshing: "重新整理中…",
175
+ modelsCount: "{value} 個模型",
176
+ modelsLoading: "正在讀取模型清單…",
177
+ runtimeSettings: "執行設定",
178
+ runtimeSettingsNote: "設定會儲存到目前的 DSH profile,並從下一次 Cursor 請求開始生效。",
179
+ maxToolRounds: "最大工具輪次",
180
+ maxToolRoundsHint: "單次 Cursor 任務允許的工具輪次,超過後以 TOOL_LIMIT 終止。範圍 1–1000。",
181
+ retryCount: "重試次數",
182
+ retryCountHint: "符合狀態碼時允許的額外請求次數。預設 0(關閉),啟用後可能重複 Cursor 遠端模型請求或用量。範圍 0–10。",
183
+ retryInterval: "重試間隔(毫秒)",
184
+ retryIntervalHint: "每次 HTTP 重試前等待的固定時間。範圍 0–300000。",
185
+ retryStatuses: "重試 HTTP 狀態碼",
186
+ retryStatusesHint: "以逗號分隔,例如 408, 425, 429, 500, 502, 503, 504。",
187
+ saveSettings: "儲存設定",
188
+ savingSettings: "儲存中…",
189
+ reloadSettings: "重新載入",
190
+ loadingSettings: "載入中…",
191
+ settingsSaved: "設定已儲存。",
192
+ settingsLoadFailed: "無法讀取執行設定。",
193
+ settingsSaveFailed: "無法儲存執行設定。",
194
+ settingsInvalid: "請檢查工具輪次、重試次數、間隔和 HTTP 狀態碼。",
195
+ };
196
+ const ja = {
197
+ nav: "Cursor",
198
+ title: "Cursor サブスクリプション",
199
+ connected: "サインイン済み",
200
+ disconnected: "未サインイン",
201
+ accountLoading: "アカウント状態を読み込み中…",
202
+ login: "ブラウザーでサインイン",
203
+ logout: "サインアウト",
204
+ cancel: "キャンセル",
205
+ openLogin: "サインインページを開く",
206
+ waiting: "サインインの完了を待っています…",
207
+ failed: "サインインに失敗しました。もう一度お試しください。",
208
+ loadFailed: "Cursor の状態を読み取れませんでした。",
209
+ note: "ブラウザーで Cursor アカウントにサインインすると、モデル選択で Cursor サブスクリプションのモデルを利用できます。認証情報はこのマシン内にのみ保存されます。",
210
+ expiresAt: "トークン有効期限 {value}",
211
+ usage: "サブスクリプション使用量",
212
+ usageRefresh: "更新",
213
+ usageRefreshing: "更新中…",
214
+ usageNotSignedIn: "サインインすると Cursor が報告する使用量を確認できます。",
215
+ usageLoading: "使用量を読み込み中…",
216
+ usageUpdated: "{value} に更新",
217
+ plan: "プラン",
218
+ unlimited: "無制限",
219
+ includedRequests: "含まれるリクエスト",
220
+ requestsUsed: "{used} / {limit} リクエスト使用({pct}%)",
221
+ onDemandSpend: "オンデマンド利用額",
222
+ autoUsage: "プランの Cursor モデル使用量",
223
+ otherModels: "プランのその他モデル使用量",
224
+ modelUsage: "モデル使用量",
225
+ spendUsedOf: "${used} / ${limit} 使用",
226
+ billingCycle: "請求サイクル",
227
+ billingDaysLeft: "残り {value} 日",
228
+ autoPercent: "プランの Cursor モデル {value}%",
229
+ otherModelsPercent: "プランのその他モデル {value}%",
230
+ otherModelsUnknown: "この使用量はまだ取得していません",
231
+ modelSpendPct: "{model} ${used}({pct}%)",
232
+ models: "モデル",
233
+ modelsRefresh: "モデルを更新",
234
+ modelsRefreshing: "更新中…",
235
+ modelsCount: "{value} 個のモデル",
236
+ modelsLoading: "モデル一覧を読み込み中…",
237
+ runtimeSettings: "実行設定",
238
+ runtimeSettingsNote: "設定は現在の DSH profile に保存され、次回の Cursor リクエストから適用されます。",
239
+ maxToolRounds: "最大ツールラウンド",
240
+ maxToolRoundsHint: "1 回の Cursor タスクで許可されるツールラウンド数。超えると TOOL_LIMIT で終了します。範囲: 1〜1000。",
241
+ retryCount: "リトライ回数",
242
+ retryCountHint: "該当するステータスに対する追加リクエスト回数。既定 0(無効)。有効にすると Cursor のリモートモデル処理や使用量が重複する場合があります。範囲: 0〜10。",
243
+ retryInterval: "リトライ間隔(ミリ秒)",
244
+ retryIntervalHint: "各 HTTP リトライ前の固定待機時間。範囲: 0〜300000。",
245
+ retryStatuses: "リトライする HTTP ステータスコード",
246
+ retryStatusesHint: "カンマ区切り、例: 408, 425, 429, 500, 502, 503, 504。",
247
+ saveSettings: "設定を保存",
248
+ savingSettings: "保存中…",
249
+ reloadSettings: "再読み込み",
250
+ loadingSettings: "読み込み中…",
251
+ settingsSaved: "設定を保存しました。",
252
+ settingsLoadFailed: "実行設定を読み取れませんでした。",
253
+ settingsSaveFailed: "実行設定を保存できませんでした。",
254
+ settingsInvalid: "ツールラウンド、リトライ回数、間隔、HTTP ステータスコードを確認してください。",
255
+ };
256
+ const ko = {
257
+ nav: "Cursor",
258
+ title: "Cursor 구독",
259
+ connected: "로그인됨",
260
+ disconnected: "로그인하지 않음",
261
+ accountLoading: "계정 상태를 불러오는 중…",
262
+ login: "브라우저로 로그인",
263
+ logout: "로그아웃",
264
+ cancel: "취소",
265
+ openLogin: "로그인 페이지 열기",
266
+ waiting: "로그인 완료를 기다리는 중…",
267
+ failed: "로그인에 실패했습니다. 다시 시도하세요.",
268
+ loadFailed: "Cursor 상태를 읽을 수 없습니다.",
269
+ note: "브라우저에서 Cursor 계정으로 로그인하면 모델 선택기에서 Cursor 구독 모델을 사용할 수 있습니다. 자격 증명은 이 컴퓨터에만 저장됩니다.",
270
+ expiresAt: "토큰 만료 {value}",
271
+ usage: "구독 사용량",
272
+ usageRefresh: "새로 고침",
273
+ usageRefreshing: "새로 고치는 중…",
274
+ usageNotSignedIn: "로그인하면 Cursor가 보고하는 사용량을 볼 수 있습니다.",
275
+ usageLoading: "사용량을 불러오는 중…",
276
+ usageUpdated: "{value}에 업데이트",
277
+ plan: "플랜",
278
+ unlimited: "무제한",
279
+ includedRequests: "포함된 요청",
280
+ requestsUsed: "요청 {used} / {limit} 사용 ({pct}%)",
281
+ onDemandSpend: "종량제 지출",
282
+ autoUsage: "플랜 Cursor 모델 사용량",
283
+ otherModels: "플랜 기타 모델 사용량",
284
+ modelUsage: "모델 사용량",
285
+ spendUsedOf: "${used} / ${limit} 사용",
286
+ billingCycle: "결제 주기",
287
+ billingDaysLeft: "{value}일 남음",
288
+ autoPercent: "플랜 Cursor 모델 {value}%",
289
+ otherModelsPercent: "플랜 기타 모델 {value}%",
290
+ otherModelsUnknown: "이 사용량은 아직 읽지 않았습니다",
291
+ modelSpendPct: "{model} ${used} ({pct}%)",
292
+ models: "모델",
293
+ modelsRefresh: "모델 새로 고침",
294
+ modelsRefreshing: "새로 고치는 중…",
295
+ modelsCount: "모델 {value}개",
296
+ modelsLoading: "모델 목록을 불러오는 중…",
297
+ runtimeSettings: "실행 설정",
298
+ runtimeSettingsNote: "설정은 현재 DSH profile에 저장되며 다음 Cursor 요청부터 적용됩니다.",
299
+ maxToolRounds: "최대 도구 라운드",
300
+ maxToolRoundsHint: "Cursor 작업 하나에서 허용되는 도구 라운드 수이며 초과하면 TOOL_LIMIT으로 종료됩니다. 범위: 1–1000.",
301
+ retryCount: "재시도 횟수",
302
+ retryCountHint: "일치하는 상태 코드에 대한 추가 요청 횟수. 기본값 0(해제). 사용하면 Cursor 원격 모델 작업이나 사용량이 중복될 수 있습니다. 범위: 0–10.",
303
+ retryInterval: "재시도 간격(ms)",
304
+ retryIntervalHint: "각 HTTP 재시도 전에 기다리는 고정 시간. 범위: 0–300000.",
305
+ retryStatuses: "재시도할 HTTP 상태 코드",
306
+ retryStatusesHint: "쉼표로 구분합니다. 예: 408, 425, 429, 500, 502, 503, 504.",
307
+ saveSettings: "설정 저장",
308
+ savingSettings: "저장 중…",
309
+ reloadSettings: "다시 불러오기",
310
+ loadingSettings: "불러오는 중…",
311
+ settingsSaved: "설정을 저장했습니다.",
312
+ settingsLoadFailed: "실행 설정을 읽을 수 없습니다.",
313
+ settingsSaveFailed: "실행 설정을 저장할 수 없습니다.",
314
+ settingsInvalid: "도구 라운드, 재시도 횟수, 간격, HTTP 상태 코드를 확인하세요.",
315
+ };
316
+ const es = {
317
+ nav: "Cursor",
318
+ title: "Suscripción a Cursor",
319
+ connected: "Sesión iniciada",
320
+ disconnected: "Sin sesión",
321
+ accountLoading: "Leyendo el estado de la cuenta…",
322
+ login: "Iniciar sesión en el navegador",
323
+ logout: "Cerrar sesión",
324
+ cancel: "Cancelar",
325
+ openLogin: "Abrir la página de inicio de sesión",
326
+ waiting: "Esperando a que termine el inicio de sesión…",
327
+ failed: "No se pudo iniciar sesión. Inténtalo de nuevo.",
328
+ loadFailed: "No se pudo leer el estado de Cursor.",
329
+ note: "Después de iniciar sesión con una cuenta de Cursor en el navegador, los modelos de la suscripción a Cursor estarán disponibles en el selector de modelos. Las credenciales permanecen en este equipo.",
330
+ expiresAt: "Token válido hasta {value}",
331
+ usage: "Uso de la suscripción",
332
+ usageRefresh: "Actualizar",
333
+ usageRefreshing: "Actualizando…",
334
+ usageNotSignedIn: "Inicia sesión para ver el uso que informa Cursor.",
335
+ usageLoading: "Leyendo el uso…",
336
+ usageUpdated: "Actualizado {value}",
337
+ plan: "Plan",
338
+ unlimited: "Ilimitado",
339
+ includedRequests: "Solicitudes incluidas",
340
+ requestsUsed: "{used} / {limit} solicitudes usadas ({pct}%)",
341
+ onDemandSpend: "Gasto bajo demanda",
342
+ autoUsage: "Uso de modelos Cursor del plan",
343
+ otherModels: "Uso de otros modelos del plan",
344
+ modelUsage: "Uso por modelo",
345
+ spendUsedOf: "${used} / ${limit} gastados",
346
+ billingCycle: "Ciclo de facturación",
347
+ billingDaysLeft: "Quedan {value} días",
348
+ autoPercent: "Modelos Cursor del plan {value}%",
349
+ otherModelsPercent: "Otros modelos del plan {value}%",
350
+ otherModelsUnknown: "Este valor de uso aún no se ha leído",
351
+ modelSpendPct: "{model} ${used} ({pct}%)",
352
+ models: "Modelos",
353
+ modelsRefresh: "Actualizar modelos",
354
+ modelsRefreshing: "Actualizando…",
355
+ modelsCount: "{value} modelos",
356
+ modelsLoading: "Leyendo la lista de modelos…",
357
+ runtimeSettings: "Ajustes de ejecución",
358
+ runtimeSettingsNote: "Los ajustes se guardan en el profile de DSH actual y se aplican desde la siguiente solicitud a Cursor.",
359
+ maxToolRounds: "Rondas de herramientas máximas",
360
+ maxToolRoundsHint: "Rondas de herramientas permitidas en una tarea de Cursor antes de que TOOL_LIMIT la detenga. Rango: 1–1000.",
361
+ retryCount: "Número de reintentos",
362
+ retryCountHint: "Intentos adicionales para los estados coincidentes. Predeterminado: 0 (desactivado). Activar los reintentos puede repetir trabajo o uso del modelo remoto de Cursor. Rango: 0–10.",
363
+ retryInterval: "Intervalo de reintento (ms)",
364
+ retryIntervalHint: "Espera fija antes de cada reintento HTTP. Rango: 0–300000.",
365
+ retryStatuses: "Códigos de estado HTTP con reintento",
366
+ retryStatusesHint: "Separados por comas, por ejemplo: 408, 425, 429, 500, 502, 503, 504.",
367
+ saveSettings: "Guardar ajustes",
368
+ savingSettings: "Guardando…",
369
+ reloadSettings: "Recargar",
370
+ loadingSettings: "Cargando…",
371
+ settingsSaved: "Ajustes guardados.",
372
+ settingsLoadFailed: "No se pudieron leer los ajustes de ejecución.",
373
+ settingsSaveFailed: "No se pudieron guardar los ajustes de ejecución.",
374
+ settingsInvalid: "Comprueba las rondas de herramientas, los reintentos, el intervalo y los códigos de estado HTTP.",
375
+ };
376
+ const fr = {
377
+ nav: "Cursor",
378
+ title: "Abonnement Cursor",
379
+ connected: "Connecté",
380
+ disconnected: "Non connecté",
381
+ accountLoading: "Lecture de l'état du compte…",
382
+ login: "Se connecter via le navigateur",
383
+ logout: "Se déconnecter",
384
+ cancel: "Annuler",
385
+ openLogin: "Ouvrir la page de connexion",
386
+ waiting: "En attente de la fin de la connexion…",
387
+ failed: "Échec de la connexion. Réessayez.",
388
+ loadFailed: "Impossible de lire l'état de Cursor.",
389
+ note: "Après vous être connecté à un compte Cursor dans le navigateur, les modèles de l'abonnement Cursor apparaissent dans le sélecteur de modèles. Les identifiants restent sur cette machine.",
390
+ expiresAt: "Jeton valide jusqu'au {value}",
391
+ usage: "Utilisation de l'abonnement",
392
+ usageRefresh: "Actualiser",
393
+ usageRefreshing: "Actualisation…",
394
+ usageNotSignedIn: "Connectez-vous pour consulter l'utilisation signalée par Cursor.",
395
+ usageLoading: "Lecture de l'utilisation…",
396
+ usageUpdated: "Mis à jour {value}",
397
+ plan: "Forfait",
398
+ unlimited: "Illimité",
399
+ includedRequests: "Requêtes incluses",
400
+ requestsUsed: "{used} / {limit} requêtes utilisées ({pct} %)",
401
+ onDemandSpend: "Dépenses à la demande",
402
+ autoUsage: "Utilisation des modèles Cursor du forfait",
403
+ otherModels: "Utilisation des autres modèles du forfait",
404
+ modelUsage: "Utilisation par modèle",
405
+ spendUsedOf: "${used} / ${limit} dépensés",
406
+ billingCycle: "Cycle de facturation",
407
+ billingDaysLeft: "{value} jours restants",
408
+ autoPercent: "Modèles Cursor du forfait {value} %",
409
+ otherModelsPercent: "Autres modèles du forfait {value} %",
410
+ otherModelsUnknown: "Cette valeur d'utilisation n'a pas encore été lue",
411
+ modelSpendPct: "{model} ${used} ({pct} %)",
412
+ models: "Modèles",
413
+ modelsRefresh: "Actualiser les modèles",
414
+ modelsRefreshing: "Actualisation…",
415
+ modelsCount: "{value} modèles",
416
+ modelsLoading: "Lecture de la liste des modèles…",
417
+ runtimeSettings: "Paramètres d'exécution",
418
+ runtimeSettingsNote: "Les paramètres sont enregistrés dans le profile DSH actuel et s'appliquent à partir de la prochaine requête Cursor.",
419
+ maxToolRounds: "Nombre maximal de tours d'outils",
420
+ maxToolRoundsHint: "Tours d'outils autorisés dans une tâche Cursor avant l'arrêt par TOOL_LIMIT. Plage : 1–1000.",
421
+ retryCount: "Nombre de tentatives",
422
+ retryCountHint: "Tentatives supplémentaires pour les statuts correspondants. Par défaut : 0 (désactivé). Activer les tentatives peut répéter le travail ou l'utilisation du modèle Cursor distant. Plage : 0–10.",
423
+ retryInterval: "Intervalle de nouvelle tentative (ms)",
424
+ retryIntervalHint: "Délai fixe avant chaque nouvelle tentative HTTP. Plage : 0–300000.",
425
+ retryStatuses: "Codes d'état HTTP à réessayer",
426
+ retryStatusesHint: "Séparés par des virgules, par exemple : 408, 425, 429, 500, 502, 503, 504.",
427
+ saveSettings: "Enregistrer les paramètres",
428
+ savingSettings: "Enregistrement…",
429
+ reloadSettings: "Recharger",
430
+ loadingSettings: "Chargement…",
431
+ settingsSaved: "Paramètres enregistrés.",
432
+ settingsLoadFailed: "Impossible de lire les paramètres d'exécution.",
433
+ settingsSaveFailed: "Impossible d'enregistrer les paramètres d'exécution.",
434
+ settingsInvalid: "Vérifiez les tours d'outils, le nombre de tentatives, l'intervalle et les codes d'état HTTP.",
435
+ };
436
+ const de = {
437
+ nav: "Cursor",
438
+ title: "Cursor-Abonnement",
439
+ connected: "Angemeldet",
440
+ disconnected: "Nicht angemeldet",
441
+ accountLoading: "Kontostatus wird gelesen…",
442
+ login: "Im Browser anmelden",
443
+ logout: "Abmelden",
444
+ cancel: "Abbrechen",
445
+ openLogin: "Anmeldeseite öffnen",
446
+ waiting: "Warten auf den Abschluss der Anmeldung…",
447
+ failed: "Anmeldung fehlgeschlagen. Bitte erneut versuchen.",
448
+ loadFailed: "Cursor-Status konnte nicht gelesen werden.",
449
+ note: "Nach der Anmeldung mit einem Cursor-Konto im Browser stehen die Modelle des Cursor-Abonnements in der Modellauswahl bereit. Die Anmeldedaten bleiben auf diesem Rechner.",
450
+ expiresAt: "Token gültig bis {value}",
451
+ usage: "Abonnement-Nutzung",
452
+ usageRefresh: "Aktualisieren",
453
+ usageRefreshing: "Wird aktualisiert…",
454
+ usageNotSignedIn: "Melden Sie sich an, um die von Cursor gemeldete Nutzung zu sehen.",
455
+ usageLoading: "Nutzung wird gelesen…",
456
+ usageUpdated: "Aktualisiert {value}",
457
+ plan: "Tarif",
458
+ unlimited: "Unbegrenzt",
459
+ includedRequests: "Enthaltene Anfragen",
460
+ requestsUsed: "{used} / {limit} Anfragen verwendet ({pct} %)",
461
+ onDemandSpend: "Nutzungsabhängige Ausgaben",
462
+ autoUsage: "Cursor-Modellnutzung des Tarifs",
463
+ otherModels: "Nutzung anderer Modelle des Tarifs",
464
+ modelUsage: "Nutzung je Modell",
465
+ spendUsedOf: "${used} / ${limit} ausgegeben",
466
+ billingCycle: "Abrechnungszeitraum",
467
+ billingDaysLeft: "Noch {value} Tage",
468
+ autoPercent: "Cursor-Modelle des Tarifs {value} %",
469
+ otherModelsPercent: "Andere Modelle des Tarifs {value} %",
470
+ otherModelsUnknown: "Dieser Nutzungswert wurde noch nicht gelesen",
471
+ modelSpendPct: "{model} ${used} ({pct} %)",
472
+ models: "Modelle",
473
+ modelsRefresh: "Modelle aktualisieren",
474
+ modelsRefreshing: "Wird aktualisiert…",
475
+ modelsCount: "{value} Modelle",
476
+ modelsLoading: "Modellliste wird gelesen…",
477
+ runtimeSettings: "Laufzeiteinstellungen",
478
+ runtimeSettingsNote: "Einstellungen werden im aktuellen DSH-profile gespeichert und gelten ab der nächsten Cursor-Anfrage.",
479
+ maxToolRounds: "Maximale Tool-Runden",
480
+ maxToolRoundsHint: "Erlaubte Tool-Runden in einer Cursor-Aufgabe, bevor TOOL_LIMIT abbricht. Bereich: 1–1000.",
481
+ retryCount: "Anzahl der Wiederholungen",
482
+ retryCountHint: "Zusätzliche Versuche bei passenden Statuscodes. Standard: 0 (aus). Aktivierte Wiederholungen können Remote-Arbeit oder Nutzung des Cursor-Modells duplizieren. Bereich: 0–10.",
483
+ retryInterval: "Wiederholungsintervall (ms)",
484
+ retryIntervalHint: "Feste Wartezeit vor jedem HTTP-Wiederholungsversuch. Bereich: 0–300000.",
485
+ retryStatuses: "HTTP-Statuscodes für Wiederholung",
486
+ retryStatusesHint: "Kommagetrennt, zum Beispiel: 408, 425, 429, 500, 502, 503, 504.",
487
+ saveSettings: "Einstellungen speichern",
488
+ savingSettings: "Wird gespeichert…",
489
+ reloadSettings: "Neu laden",
490
+ loadingSettings: "Wird geladen…",
491
+ settingsSaved: "Einstellungen gespeichert.",
492
+ settingsLoadFailed: "Laufzeiteinstellungen konnten nicht gelesen werden.",
493
+ settingsSaveFailed: "Laufzeiteinstellungen konnten nicht gespeichert werden.",
494
+ settingsInvalid: "Prüfen Sie Tool-Runden, Anzahl der Wiederholungen, Intervall und HTTP-Statuscodes.",
495
+ };
496
+ const ru = {
497
+ nav: "Cursor",
498
+ title: "Подписка Cursor",
499
+ connected: "Вход выполнен",
500
+ disconnected: "Вход не выполнен",
501
+ accountLoading: "Чтение состояния аккаунта…",
502
+ login: "Войти через браузер",
503
+ logout: "Выйти",
504
+ cancel: "Отмена",
505
+ openLogin: "Открыть страницу входа",
506
+ waiting: "Ожидание завершения входа…",
507
+ failed: "Не удалось войти. Повторите попытку.",
508
+ loadFailed: "Не удалось прочитать состояние Cursor.",
509
+ note: "После входа в аккаунт Cursor в браузере модели подписки Cursor появятся в выборе моделей. Учётные данные остаются на этом компьютере.",
510
+ expiresAt: "Токен действителен до {value}",
511
+ usage: "Использование подписки",
512
+ usageRefresh: "Обновить",
513
+ usageRefreshing: "Обновление…",
514
+ usageNotSignedIn: "Войдите, чтобы увидеть использование, о котором сообщает Cursor.",
515
+ usageLoading: "Чтение использования…",
516
+ usageUpdated: "Обновлено {value}",
517
+ plan: "Тариф",
518
+ unlimited: "Без ограничений",
519
+ includedRequests: "Включённые запросы",
520
+ requestsUsed: "Использовано {used} / {limit} запросов ({pct}%)",
521
+ onDemandSpend: "Расходы по требованию",
522
+ autoUsage: "Использование моделей Cursor по тарифу",
523
+ otherModels: "Использование других моделей по тарифу",
524
+ modelUsage: "Использование по моделям",
525
+ spendUsedOf: "Потрачено ${used} / ${limit}",
526
+ billingCycle: "Расчётный период",
527
+ billingDaysLeft: "Осталось {value} дн.",
528
+ autoPercent: "Модели Cursor по тарифу {value}%",
529
+ otherModelsPercent: "Другие модели по тарифу {value}%",
530
+ otherModelsUnknown: "Это значение использования ещё не прочитано",
531
+ modelSpendPct: "{model} ${used} ({pct}%)",
532
+ models: "Модели",
533
+ modelsRefresh: "Обновить модели",
534
+ modelsRefreshing: "Обновление…",
535
+ modelsCount: "Моделей: {value}",
536
+ modelsLoading: "Чтение списка моделей…",
537
+ runtimeSettings: "Параметры выполнения",
538
+ runtimeSettingsNote: "Параметры сохраняются в текущем profile DSH и применяются со следующего запроса к Cursor.",
539
+ maxToolRounds: "Максимум раундов инструментов",
540
+ maxToolRoundsHint: "Раунды инструментов, разрешённые в одной задаче Cursor, после чего срабатывает TOOL_LIMIT. Диапазон: 1–1000.",
541
+ retryCount: "Число повторов",
542
+ retryCountHint: "Дополнительные попытки для совпадающих статусов. По умолчанию: 0 (выключено). Включение повторов может продублировать работу удалённой модели Cursor или расход. Диапазон: 0–10.",
543
+ retryInterval: "Интервал повтора (мс)",
544
+ retryIntervalHint: "Фиксированная задержка перед каждым повтором HTTP. Диапазон: 0–300000.",
545
+ retryStatuses: "HTTP-статусы для повтора",
546
+ retryStatusesHint: "Через запятую, например: 408, 425, 429, 500, 502, 503, 504.",
547
+ saveSettings: "Сохранить параметры",
548
+ savingSettings: "Сохранение…",
549
+ reloadSettings: "Перезагрузить",
550
+ loadingSettings: "Загрузка…",
551
+ settingsSaved: "Параметры сохранены.",
552
+ settingsLoadFailed: "Не удалось прочитать параметры выполнения.",
553
+ settingsSaveFailed: "Не удалось сохранить параметры выполнения.",
554
+ settingsInvalid: "Проверьте раунды инструментов, число повторов, интервал и HTTP-статусы.",
555
+ };
556
+ const ptBr = {
557
+ nav: "Cursor",
558
+ title: "Assinatura do Cursor",
559
+ connected: "Conectado",
560
+ disconnected: "Não conectado",
561
+ accountLoading: "Lendo o status da conta…",
562
+ login: "Entrar pelo navegador",
563
+ logout: "Sair",
564
+ cancel: "Cancelar",
565
+ openLogin: "Abrir a página de login",
566
+ waiting: "Aguardando a conclusão do login…",
567
+ failed: "Falha no login. Tente novamente.",
568
+ loadFailed: "Não foi possível ler o estado do Cursor.",
569
+ note: "Depois de entrar com uma conta Cursor no navegador, os modelos da assinatura do Cursor ficam disponíveis no seletor de modelos. As credenciais permanecem neste computador.",
570
+ expiresAt: "Token válido até {value}",
571
+ usage: "Uso da assinatura",
572
+ usageRefresh: "Atualizar",
573
+ usageRefreshing: "Atualizando…",
574
+ usageNotSignedIn: "Entre para ver o uso informado pelo Cursor.",
575
+ usageLoading: "Lendo o uso…",
576
+ usageUpdated: "Atualizado em {value}",
577
+ plan: "Plano",
578
+ unlimited: "Ilimitado",
579
+ includedRequests: "Requisições incluídas",
580
+ requestsUsed: "{used} / {limit} requisições usadas ({pct}%)",
581
+ onDemandSpend: "Gasto sob demanda",
582
+ autoUsage: "Uso dos modelos Cursor do plano",
583
+ otherModels: "Uso de outros modelos do plano",
584
+ modelUsage: "Uso por modelo",
585
+ spendUsedOf: "${used} / ${limit} gastos",
586
+ billingCycle: "Ciclo de cobrança",
587
+ billingDaysLeft: "{value} dias restantes",
588
+ autoPercent: "Modelos Cursor do plano {value}%",
589
+ otherModelsPercent: "Outros modelos do plano {value}%",
590
+ otherModelsUnknown: "Este valor de uso ainda não foi lido",
591
+ modelSpendPct: "{model} ${used} ({pct}%)",
592
+ models: "Modelos",
593
+ modelsRefresh: "Atualizar modelos",
594
+ modelsRefreshing: "Atualizando…",
595
+ modelsCount: "{value} modelos",
596
+ modelsLoading: "Lendo a lista de modelos…",
597
+ runtimeSettings: "Configurações de execução",
598
+ runtimeSettingsNote: "As configurações são salvas no profile do DSH atual e valem a partir da próxima requisição ao Cursor.",
599
+ maxToolRounds: "Máximo de rodadas de ferramentas",
600
+ maxToolRoundsHint: "Rodadas de ferramentas permitidas em uma tarefa do Cursor antes de o TOOL_LIMIT interromper. Faixa: 1–1000.",
601
+ retryCount: "Número de tentativas",
602
+ retryCountHint: "Tentativas adicionais para os status correspondentes. Padrão: 0 (desativado). Ativar tentativas pode repetir trabalho ou uso do modelo remoto do Cursor. Faixa: 0–10.",
603
+ retryInterval: "Intervalo entre tentativas (ms)",
604
+ retryIntervalHint: "Espera fixa antes de cada nova tentativa HTTP. Faixa: 0–300000.",
605
+ retryStatuses: "Códigos de status HTTP com nova tentativa",
606
+ retryStatusesHint: "Separados por vírgula, por exemplo: 408, 425, 429, 500, 502, 503, 504.",
607
+ saveSettings: "Salvar configurações",
608
+ savingSettings: "Salvando…",
609
+ reloadSettings: "Recarregar",
610
+ loadingSettings: "Carregando…",
611
+ settingsSaved: "Configurações salvas.",
612
+ settingsLoadFailed: "Não foi possível ler as configurações de execução.",
613
+ settingsSaveFailed: "Não foi possível salvar as configurações de execução.",
614
+ settingsInvalid: "Verifique as rodadas de ferramentas, o número de tentativas, o intervalo e os códigos de status HTTP.",
615
+ };
616
+ const it = {
617
+ nav: "Cursor",
618
+ title: "Abbonamento Cursor",
619
+ connected: "Connesso",
620
+ disconnected: "Non connesso",
621
+ accountLoading: "Lettura dello stato dell'account…",
622
+ login: "Accedi dal browser",
623
+ logout: "Esci",
624
+ cancel: "Annulla",
625
+ openLogin: "Apri la pagina di accesso",
626
+ waiting: "In attesa del completamento dell'accesso…",
627
+ failed: "Accesso non riuscito. Riprova.",
628
+ loadFailed: "Impossibile leggere lo stato di Cursor.",
629
+ note: "Dopo aver eseguito l'accesso con un account Cursor nel browser, i modelli dell'abbonamento Cursor saranno disponibili nel selettore dei modelli. Le credenziali restano su questo computer.",
630
+ expiresAt: "Token valido fino al {value}",
631
+ usage: "Utilizzo dell'abbonamento",
632
+ usageRefresh: "Aggiorna",
633
+ usageRefreshing: "Aggiornamento…",
634
+ usageNotSignedIn: "Accedi per vedere l'utilizzo comunicato da Cursor.",
635
+ usageLoading: "Lettura dell'utilizzo…",
636
+ usageUpdated: "Aggiornato {value}",
637
+ plan: "Piano",
638
+ unlimited: "Illimitato",
639
+ includedRequests: "Richieste incluse",
640
+ requestsUsed: "{used} / {limit} richieste usate ({pct}%)",
641
+ onDemandSpend: "Spesa on demand",
642
+ autoUsage: "Utilizzo dei modelli Cursor del piano",
643
+ otherModels: "Utilizzo degli altri modelli del piano",
644
+ modelUsage: "Utilizzo per modello",
645
+ spendUsedOf: "${used} / ${limit} spesi",
646
+ billingCycle: "Ciclo di fatturazione",
647
+ billingDaysLeft: "{value} giorni rimanenti",
648
+ autoPercent: "Modelli Cursor del piano {value}%",
649
+ otherModelsPercent: "Altri modelli del piano {value}%",
650
+ otherModelsUnknown: "Questo valore di utilizzo non è ancora stato letto",
651
+ modelSpendPct: "{model} ${used} ({pct}%)",
652
+ models: "Modelli",
653
+ modelsRefresh: "Aggiorna i modelli",
654
+ modelsRefreshing: "Aggiornamento…",
655
+ modelsCount: "{value} modelli",
656
+ modelsLoading: "Lettura dell'elenco dei modelli…",
657
+ runtimeSettings: "Impostazioni di esecuzione",
658
+ runtimeSettingsNote: "Le impostazioni vengono salvate nel profile DSH corrente e si applicano dalla successiva richiesta a Cursor.",
659
+ maxToolRounds: "Numero massimo di round di strumenti",
660
+ maxToolRoundsHint: "Round di strumenti consentiti in un'attività Cursor prima che TOOL_LIMIT la interrompa. Intervallo: 1–1000.",
661
+ retryCount: "Numero di tentativi",
662
+ retryCountHint: "Tentativi aggiuntivi per gli stati corrispondenti. Predefinito: 0 (disattivato). Attivare i tentativi può ripetere lavoro o utilizzo del modello Cursor remoto. Intervallo: 0–10.",
663
+ retryInterval: "Intervallo tra tentativi (ms)",
664
+ retryIntervalHint: "Attesa fissa prima di ogni nuovo tentativo HTTP. Intervallo: 0–300000.",
665
+ retryStatuses: "Codici di stato HTTP da ritentare",
666
+ retryStatusesHint: "Separati da virgole, ad esempio: 408, 425, 429, 500, 502, 503, 504.",
667
+ saveSettings: "Salva impostazioni",
668
+ savingSettings: "Salvataggio…",
669
+ reloadSettings: "Ricarica",
670
+ loadingSettings: "Caricamento…",
671
+ settingsSaved: "Impostazioni salvate.",
672
+ settingsLoadFailed: "Impossibile leggere le impostazioni di esecuzione.",
673
+ settingsSaveFailed: "Impossibile salvare le impostazioni di esecuzione.",
674
+ settingsInvalid: "Controlla i round di strumenti, il numero di tentativi, l'intervallo e i codici di stato HTTP.",
675
+ };
676
+ const ar = {
677
+ nav: "Cursor",
678
+ title: "اشتراك Cursor",
679
+ connected: "تم تسجيل الدخول",
680
+ disconnected: "لم يتم تسجيل الدخول",
681
+ accountLoading: "جارٍ قراءة حالة الحساب…",
682
+ login: "تسجيل الدخول عبر المتصفح",
683
+ logout: "تسجيل الخروج",
684
+ cancel: "إلغاء",
685
+ openLogin: "فتح صفحة تسجيل الدخول",
686
+ waiting: "في انتظار اكتمال تسجيل الدخول…",
687
+ failed: "فشل تسجيل الدخول. حاول مرة أخرى.",
688
+ loadFailed: "تعذّر قراءة حالة Cursor.",
689
+ note: "بعد تسجيل الدخول بحساب Cursor في المتصفح، تصبح نماذج اشتراك Cursor متاحة في منتقي النماذج. تبقى بيانات الاعتماد على هذا الجهاز.",
690
+ expiresAt: "الرمز صالح حتى {value}",
691
+ usage: "استخدام الاشتراك",
692
+ usageRefresh: "تحديث",
693
+ usageRefreshing: "جارٍ التحديث…",
694
+ usageNotSignedIn: "سجّل الدخول لعرض الاستخدام الذي يبلّغ عنه Cursor.",
695
+ usageLoading: "جارٍ قراءة الاستخدام…",
696
+ usageUpdated: "حُدّث في {value}",
697
+ plan: "الخطة",
698
+ unlimited: "غير محدود",
699
+ includedRequests: "الطلبات المضمّنة",
700
+ requestsUsed: "استُخدم {used} / {limit} طلبًا ({pct}%)",
701
+ onDemandSpend: "الإنفاق عند الطلب",
702
+ autoUsage: "استخدام نماذج Cursor في الخطة",
703
+ otherModels: "استخدام النماذج الأخرى في الخطة",
704
+ modelUsage: "الاستخدام حسب النموذج",
705
+ spendUsedOf: "أُنفق ${used} / ${limit}",
706
+ billingCycle: "دورة الفوترة",
707
+ billingDaysLeft: "بقي {value} يومًا",
708
+ autoPercent: "نماذج Cursor في الخطة {value}%",
709
+ otherModelsPercent: "النماذج الأخرى في الخطة {value}%",
710
+ otherModelsUnknown: "لم تُقرأ قيمة الاستخدام هذه بعد",
711
+ modelSpendPct: "{model} ${used} ({pct}%)",
712
+ models: "النماذج",
713
+ modelsRefresh: "تحديث النماذج",
714
+ modelsRefreshing: "جارٍ التحديث…",
715
+ modelsCount: "{value} نموذجًا",
716
+ modelsLoading: "جارٍ قراءة قائمة النماذج…",
717
+ runtimeSettings: "إعدادات التشغيل",
718
+ runtimeSettingsNote: "تُحفظ الإعدادات في profile DSH الحالي وتُطبَّق بدءًا من طلب Cursor التالي.",
719
+ maxToolRounds: "الحد الأقصى لجولات الأدوات",
720
+ maxToolRoundsHint: "جولات الأدوات المسموح بها في مهمة Cursor واحدة قبل أن يوقفها TOOL_LIMIT. النطاق: 1–1000.",
721
+ retryCount: "عدد المحاولات",
722
+ retryCountHint: "محاولات إضافية لحالات الاستجابة المطابقة. الافتراضي: 0 (معطّل). قد يؤدي تفعيل المحاولات إلى تكرار عمل النموذج البعيد في Cursor أو استخدامه. النطاق: 0–10.",
723
+ retryInterval: "الفاصل بين المحاولات (مللي ثانية)",
724
+ retryIntervalHint: "انتظار ثابت قبل كل محاولة HTTP جديدة. النطاق: 0–300000.",
725
+ retryStatuses: "رموز حالة HTTP المُعاد المحاولة فيها",
726
+ retryStatusesHint: "مفصولة بفواصل، مثل: 408, 425, 429, 500, 502, 503, 504.",
727
+ saveSettings: "حفظ الإعدادات",
728
+ savingSettings: "جارٍ الحفظ…",
729
+ reloadSettings: "إعادة التحميل",
730
+ loadingSettings: "جارٍ التحميل…",
731
+ settingsSaved: "تم حفظ الإعدادات.",
732
+ settingsLoadFailed: "تعذّر قراءة إعدادات التشغيل.",
733
+ settingsSaveFailed: "تعذّر حفظ إعدادات التشغيل.",
734
+ settingsInvalid: "تحقق من جولات الأدوات وعدد المحاولات والفاصل ورموز حالة HTTP.",
735
+ };
736
+
737
+ /** Every dictionary carries the same key set as `en`; a test enforces it. */
738
+ const DICTIONARIES = {
739
+ zh,
740
+ en,
741
+ "zh-Hant": zhHant,
742
+ ja,
743
+ ko,
744
+ es,
745
+ fr,
746
+ de,
747
+ it,
748
+ "pt-BR": ptBr,
749
+ ru,
750
+ ar,
751
+ };
752
+
753
+ /**
754
+ * Languages this panel contributes to the DSH catalog. DSH ships `zh` and
755
+ * `en` only, so every other language joins the shared catalog through
756
+ * `locale.addLanguage` and then appears in Settings -> General -> Language.
757
+ * Each one falls back to `en`, which keeps the rest of the DSH shell
758
+ * readable for a reader of a contributed language.
759
+ */
760
+ const LANGUAGES = [
761
+ { id: "zh-Hant", label: "繁體中文", fallback: "zh" },
762
+ { id: "ja", label: "日本語", fallback: "en" },
763
+ { id: "ko", label: "한국어", fallback: "en" },
764
+ { id: "es", label: "Español", fallback: "en" },
765
+ { id: "fr", label: "Français", fallback: "en" },
766
+ { id: "de", label: "Deutsch", fallback: "en" },
767
+ { id: "it", label: "Italiano", fallback: "en" },
768
+ { id: "pt-BR", label: "Português (Brasil)", fallback: "en" },
769
+ { id: "ru", label: "Русский", fallback: "en" },
770
+ { id: "ar", label: "العربية", fallback: "en" },
771
+ ];
772
+
773
+ /** Contributed locales written right to left; the panel mirrors for these. */
774
+ const RTL_LOCALES = new Set(["ar"]);
775
+ /** Marker the stylesheet keys the mirrored rules on. */
776
+ const RTL_ATTRIBUTE = "cursorSubscriptionRtl";
143
777
 
144
778
  const STYLE = `
145
779
  .cursorSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
@@ -158,6 +792,7 @@ window.__ModuleLoader__.load({
158
792
  .cursorSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
159
793
  .cursorSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}
160
794
  .cursorSubscriptionNote{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
795
+ .cursorSubscriptionVersion{font:500 11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:1px 6px;direction:ltr}
161
796
  .cursorSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
162
797
  .cursorSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}
163
798
  .cursorSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}
@@ -188,6 +823,10 @@ window.__ModuleLoader__.load({
188
823
  .cursorSubscriptionFieldHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
189
824
  .cursorSubscriptionSettingsFoot{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
190
825
  .cursorSubscriptionSuccess{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
826
+ html[data-cursor-subscription-rtl] .cursorSubscription{direction:rtl}
827
+ html[data-cursor-subscription-rtl] .cursorSubscriptionUsageRow progress{direction:rtl}
828
+ html[data-cursor-subscription-rtl] .cursorSubscriptionField input{direction:ltr;text-align:left}
829
+ html[data-cursor-subscription-rtl] .cursorSubscriptionModelChip{direction:ltr}
191
830
  @container (max-width:520px){.cursorSubscriptionSettingsGrid{grid-template-columns:1fr}.cursorSubscriptionFieldWide{grid-column:auto}}
192
831
  `;
193
832
 
@@ -195,6 +834,25 @@ window.__ModuleLoader__.load({
195
834
  if (!response?.ok) throw new Error(response?.error?.message ?? "Cursor RPC failed");
196
835
  return response.value;
197
836
  };
837
+ /**
838
+ * Label one reported build version for the panel header.
839
+ * @param value - version string reported by the Host.
840
+ * @returns the `v`-prefixed label, or undefined when nothing usable was reported.
841
+ */
842
+ const formatVersion = (value) => (typeof value === "string" && value.trim() !== "" ? `v${value.trim()}` : undefined);
843
+ /**
844
+ * Read the serving build's version through the account channel.
845
+ * @param rpc - connection RPC client bound to this panel.
846
+ * @returns the label, or undefined when the call fails or answers without a version.
847
+ */
848
+ const readVersion = async (rpc) => {
849
+ try {
850
+ const response = await rpc.call(CHANNEL, "version", {});
851
+ return response?.ok ? formatVersion(response.value?.version) : undefined;
852
+ } catch {
853
+ return undefined;
854
+ }
855
+ };
198
856
  const fill = (text, values) => Object.entries(values).reduce((next, [key, value]) => next.replace(`{${key}}`, String(value)), text);
199
857
  const validDate = (value) => {
200
858
  const date = new Date(value);
@@ -780,6 +1438,16 @@ window.__ModuleLoader__.load({
780
1438
  const [account, setAccount] = react.useState();
781
1439
  const [error, setError] = react.useState();
782
1440
  const [resetKey, setResetKey] = react.useState(0);
1441
+ const [version, setVersion] = react.useState();
1442
+ react.useEffect(() => {
1443
+ let live = true;
1444
+ readVersion(rpc).then((label) => {
1445
+ if (live) setVersion(label);
1446
+ });
1447
+ return () => {
1448
+ live = false;
1449
+ };
1450
+ }, []);
783
1451
  react.useEffect(() => {
784
1452
  let live = true;
785
1453
  rpc.call(CHANNEL, "status", {})
@@ -800,7 +1468,16 @@ window.__ModuleLoader__.load({
800
1468
  children: [
801
1469
  react_jsx_runtime.jsx("div", {
802
1470
  className: "cursorSubscriptionHead",
803
- children: react_jsx_runtime.jsx("h2", { children: t("title") }),
1471
+ children: [
1472
+ react_jsx_runtime.jsx("h2", { children: t("title") }),
1473
+ version === undefined
1474
+ ? null
1475
+ : react_jsx_runtime.jsx("span", {
1476
+ className: "cursorSubscriptionVersion",
1477
+ title: `dsh-cursor-subscription ${version}`,
1478
+ children: version,
1479
+ }),
1480
+ ],
804
1481
  }),
805
1482
  error === undefined
806
1483
  ? null
@@ -821,7 +1498,43 @@ window.__ModuleLoader__.load({
821
1498
  }
822
1499
 
823
1500
  function apply(ctx) {
824
- ctx.effect(() => ctx.locale.register(NS, { zh, en }), "cursor-subscription: copy");
1501
+ ctx.effect(() => ctx.locale.register(NS, DICTIONARIES), "cursor-subscription: copy");
1502
+ // Contribute the panel's extra languages to the shared catalog. A locale
1503
+ // id may already be owned by DSH itself or by another language pack: that
1504
+ // is not an error, the dictionary above simply joins the existing one. An
1505
+ // older locale runtime without language packs has no `addLanguage`, which
1506
+ // leaves the built-in zh/en panel in place instead of failing the client.
1507
+ ctx.effect(() => {
1508
+ const disposers = [];
1509
+ for (const language of LANGUAGES) {
1510
+ const taken = ctx.locale.getLocale().locales.some((entry) => entry.id === language.id);
1511
+ if (taken) continue;
1512
+ try {
1513
+ disposers.push(ctx.locale.addLanguage(language));
1514
+ } catch {
1515
+ /* an occupied id or an unavailable fallback leaves the panel working */
1516
+ }
1517
+ }
1518
+ return () => {
1519
+ for (const dispose of disposers) dispose();
1520
+ };
1521
+ }, "cursor-subscription: languages");
1522
+ // Mirror the panel while a right-to-left language is active. The marker
1523
+ // rides the document element so the stylesheet can scope it; nothing else
1524
+ // in the shell is affected.
1525
+ ctx.effect(() => {
1526
+ const root = document.documentElement;
1527
+ const sync = () => {
1528
+ if (RTL_LOCALES.has(ctx.locale.getLocale().active)) root.dataset[RTL_ATTRIBUTE] = "";
1529
+ else delete root.dataset[RTL_ATTRIBUTE];
1530
+ };
1531
+ sync();
1532
+ const off = ctx.locale.subscribe(sync);
1533
+ return () => {
1534
+ off();
1535
+ delete root.dataset[RTL_ATTRIBUTE];
1536
+ };
1537
+ }, "cursor-subscription: direction");
825
1538
  ctx.effect(() => {
826
1539
  const tag = document.createElement("style");
827
1540
  tag.dataset.plugin = "dsh-cursor-subscription";
@@ -848,6 +1561,9 @@ window.__ModuleLoader__.load({
848
1561
  //#endregion
849
1562
  exports.apply = apply;
850
1563
  exports.inject = inject;
1564
+ // Pure helpers the panel's tests pin; the DSH client module loader reads only apply/inject.
1565
+ exports.formatVersion = formatVersion;
1566
+ exports.readVersion = readVersion;
851
1567
  return module.exports;
852
1568
  },
853
1569
  });
package/lib/index.js CHANGED
@@ -17,6 +17,7 @@ import z from "@deepseek-ai/schemastery";
17
17
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
18
18
  import { ToolCallId, LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
19
19
  import { createHash, randomUUID } from "node:crypto";
20
+ import { readFileSync } from "node:fs";
20
21
  import http2 from "node:http2";
21
22
  import http from "node:http";
22
23
  import https from "node:https";
@@ -24,6 +25,21 @@ import { spawn } from "node:child_process";
24
25
  import { gunzipSync } from "node:zlib";
25
26
  import { encodeValue, decodeValue, Reader, Writer } from "./proto.js";
26
27
 
28
+ /**
29
+ * Read this package's own version from the manifest beside `lib/`.
30
+ * @returns the manifest version, or undefined when it cannot be read.
31
+ */
32
+ function readVersion() {
33
+ try {
34
+ return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
35
+ } catch {
36
+ /* An unreadable manifest leaves the version unknown instead of failing the plugin. */
37
+ return undefined;
38
+ }
39
+ }
40
+ /** Build version the settings panel prints; undefined when the manifest is unreadable. */
41
+ export const VERSION = readVersion();
42
+
27
43
  //#region native fetch
28
44
  /**
29
45
  * DSH web replaces the ambient `fetch` with an MCP `createFetchWithInit`
@@ -119,11 +135,13 @@ function makeResponse(status, res, data) {
119
135
 
120
136
  //#region constants
121
137
  export const name = "cursor-subscription";
122
- export const inject = ["llm", "credentials", "connection", "webServer"];
138
+ export const inject = ["llm", "credentials", "connection"];
123
139
 
124
140
  export const PROVIDER = "cursor-subscription";
125
141
  export const CREDENTIAL_REF = credentialRef("CURSOR_SUBSCRIPTION_OAUTH");
126
142
  export const CHANNEL = "/cursor-subscription";
143
+ /** Connection registry options: accept the account channel from loopback Hosts only. */
144
+ const LOOPBACK_RPC = Object.freeze({ authority: "loopback" });
127
145
 
128
146
  export const CURSOR_BASE_URL = "https://api2.cursor.sh";
129
147
  export const CURSOR_LOGIN_URL = "https://cursor.com/loginDeepControl";
@@ -776,11 +794,15 @@ export function createCursorRpcHandler(coordinator, options = {}) {
776
794
  const usageReader = options.usageReader;
777
795
  const modelsProvider = options.modelsProvider;
778
796
  const settingsController = options.settings;
797
+ const version = options.version ?? VERSION;
779
798
  const publicError = (code, message) => ({ ok: false, error: { code, message, details: { issues: [] } } });
780
799
  return async (endpoint, payload, signal) => {
781
800
  try {
782
801
  signal.throwIfAborted();
783
802
  const input = asObject(payload);
803
+ // The panel labels itself with the build that is actually serving it, so the
804
+ // version never has to be duplicated into the browser bundle.
805
+ if (endpoint === "version") return ok({ version });
784
806
  if (endpoint === "settings") return ok(settingsController.read());
785
807
  if (endpoint === "settings/update") {
786
808
  if (!Number.isSafeInteger(input.revision) || input.revision < 0) throw new Error("invalid Cursor settings revision");
@@ -2917,9 +2939,26 @@ export function apply(ctx, config = {}) {
2917
2939
  modelsProvider: adapter,
2918
2940
  settings: settingsController,
2919
2941
  });
2920
- ctx.effect(
2921
- () => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }),
2922
- "cursor-subscription: loopback account RPC",
2923
- );
2942
+ // Mount the loopback account channel through Connection. `rpc.handle`
2943
+ // registers the channel on the Context that reads the Connection service,
2944
+ // but resolves that Context's `webServer` through the providing plugin's own
2945
+ // scope, so it only works while the Connection plugin itself declares
2946
+ // `webServer`. @deepseek-ai/dsh-client-connection 0.1.5-rc.1 declares
2947
+ // `credentials` alone and mounts `/api` from a nested `webServer` Context, so
2948
+ // `rpc.handle` fails the entire plugin tree with
2949
+ // `cannot get property "webServer" without inject`. Calling the same registry
2950
+ // method Connection uses internally with an explicit owner Context that
2951
+ // declares `webServer` reuses Connection's envelope, trust fence, and browser
2952
+ // authentication, and keeps the route on this plugin's fiber. Fall back to
2953
+ // the public `rpc.handle` if that registry method ever disappears.
2954
+ ctx.inject(["connection", "webServer"], (webCtx) => {
2955
+ const connection = webCtx.connection;
2956
+ webCtx.effect(() => {
2957
+ if (typeof connection.register === "function") {
2958
+ return connection.register(webCtx, CHANNEL, handler, LOOPBACK_RPC);
2959
+ }
2960
+ return connection.rpc.handle(CHANNEL, handler, LOOPBACK_RPC);
2961
+ }, "cursor-subscription: loopback account RPC");
2962
+ });
2924
2963
  }
2925
2964
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cursor-subscription",
3
- "version": "0.5.7",
3
+ "version": "0.6.1",
4
4
  "packageManager": "pnpm@11.19.0",
5
5
  "description": "Cursor subscription for DeepSeek Harness with browser login, token refresh, model discovery, and the Cursor Agent chat protocol",
6
6
  "type": "module",