dsh-qoder-connect 0.2.6 → 0.2.7

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
@@ -13,9 +13,10 @@ Configure only what you use: a variant with no saved PAT shows no model group an
13
13
 
14
14
  ## Features
15
15
 
16
- - **PAT: save it and it works** — paste a PAT into the card and press *Save*; the token is validated live against that variant's region before anything is written, and the model group appears immediately — no DSH restart. Once signed in, *Replace PAT* (the new token overwrites only after it validates, so a mistyped paste can never strand a working credential) and *Clear PAT* are one click away.
16
+ - **Multiple PATs, switched by hand** — each variant keeps a list of saved PATs and the card offers a select to choose the active one. Switching only changes the signing token: model toggles, context-window preferences, check-in and sidebar-quota settings are one shared configuration per variant and never follow the account. *Add PAT*, *Remove current* and *Clear all* manage the list; saving validates first, and removing or switching never touches another account's saved catalog or probe records.
17
+ - **PAT: save it and it works** — paste a PAT into the card and press *Save*; the token is validated live against that variant's region before anything is written, and the model group appears immediately — no DSH restart. A mistyped paste can never strand a working credential.
17
18
  - **Two independent variants** — `qoder` (china) and `qoder-global` (global) keep separate credential files, routes and saved catalogs: a token minted on qoder.com does not work against the China deployment and vice versa.
18
- - **Three-tier model catalog (live → saved → fallback)** — the card states where the list on screen came from: *“Model list updated …”* (fetched now), *“Showing the saved model list from …”* (this account's last successful fetch, restored after a restart or a failed fetch), or *“Showing the built-in model list (not yet updated from Qoder)”* (the roster compiled into the plugin). The reason of the most recent failed fetch is shown too, and a *Refresh model list* button sits right there.
19
+ - **Three-tier model catalog (live → saved → fallback)** — the card states where the list on screen came from: *“Model list updated …”* (fetched now), *“Showing the saved model list from …”* (this account's last successful fetch, restored after a restart or a failed fetch), or *“Showing the built-in model list (not yet updated from Qoder)”* (the roster compiled into the plugin). The reason of the most recent failed fetch is shown too, and the card's *Refresh* action re-reads the status and re-fetches the model list.
19
20
  - **One-toggle context window** — each variant card's *Context window* tab lists every model's capacity (the default and the largest declared window) and carries its own *“Use the largest declared context window”* preference (on by default): on, requests declare the maximum (e.g. Qwen3.8-Max at 1M); off, the default (200K). The two variants keep independent toggle state.
20
21
  - **Model visibility toggles & batch controls** — each variant card provides a dedicated *Model Toggles* tab (between *Context window* and *Credit details*) to freely enable or hide individual models; disabled models are hidden from the DSH model picker, keeping it tidy; includes instant search by name/ID and one-click *Enable all* / *Disable all* batch operations.
21
22
  - **Daily auto check-in & logs** — claims the daily 100 Credits automatically, **at a time you can set per variant on the card** (10:00 UTC+8 by default, the upstream's reset moment), with independent toggles and startup catch-up protection. The card features a dedicated *Check-in log* panel with audit trails, *Check in now*, *Refresh*, and *Clear logs* quick actions.
@@ -113,9 +114,23 @@ Swap the `--profile` value for the profile you use (`web` / `desktop` / `tui`, e
113
114
 
114
115
  (The card repeats the same guidance: *“Generate a PAT in your qoder.com account settings (Account → Personal Access Token), then paste it here.”*)
115
116
 
116
- ### Save it on the card
117
+ ### Save and switch on the card
117
118
 
118
- DSH → Settings → Plugin Settings (插件设置) → the Qoder card: paste the PAT into the password field and press *Save* (*“Validating and saving…”* while it runs). The token is validated against that variant's region first — a refusal saves nothing and the card says *“That PAT was rejected — generate a new one in your account settings and try again.”*. Do this on both cards to run both groups side by side.
119
+ DSH → Settings → Plugin Settings (插件设置) → the Qoder card: paste the PAT into the password field and press *Save* (*“Validating and saving…”* while it runs). The token is validated against that variant's region first — a refusal saves nothing and the card says *“That PAT was rejected — generate a new one in your account settings and try again.”*
120
+
121
+ Each variant can hold several PATs: once a second one is saved, the account row shows a select — choosing an entry switches to it. *Add PAT* appends another (with an optional local-only label), *Remove current* deletes only the active one, and *Clear all* empties the list. **Configuration is shared**: model toggles, context-window preferences, check-in time and sidebar-quota settings belong to the variant, not to the account, and switching never changes them. Quota and check-in act on the active account. Check-in history is kept per variant, so switching does not retroactively check the new account in for today — use *Check in now* when needed. Do this on both cards to run both groups side by side.
122
+
123
+ ### Command line (optional)
124
+
125
+ On a terminal-only profile the CLI manages the same list (`--provider` defaults to `qoder`):
126
+
127
+ ```bash
128
+ dsh-qoder-connect pat set <token> --provider qoder # validate, save, and activate
129
+ dsh-qoder-connect pat list --provider qoder # list saved tokens (* = active)
130
+ dsh-qoder-connect pat use <id> --provider qoder # switch
131
+ dsh-qoder-connect pat rm <id> --provider qoder # remove
132
+ dsh-qoder-connect pat clear --provider qoder # remove all (logout is an alias)
133
+ ```
119
134
 
120
135
  ### Environment-variable fallback
121
136
 
@@ -136,8 +151,8 @@ Everything the plugin owns lives in one data directory: `<profile>/.dsh-qoder-co
136
151
 
137
152
  ```text
138
153
  <profile>/.dsh-qoder-connect/
139
- ├── .qoder-auth.json # China PAT ({version:2, pat, region, savedAt})
140
- ├── .qoder-global-auth.json # Global PAT
154
+ ├── .qoder-auth.json # China PAT list ({version:3, activeId, credentials:[{id,pat,region,savedAt,label?}]})
155
+ ├── .qoder-global-auth.json # Global PAT list
141
156
  ├── checkin-status.json # check-in status and history logs
142
157
  └── state/
143
158
  ├── .qoder-catalog.json # China per-account saved catalog
package/README.md CHANGED
@@ -13,9 +13,10 @@
13
13
 
14
14
  ## 功能
15
15
 
16
- - **PAT 即存即用**:在卡片上粘贴 PAT 点「保存」,插件先对该变体的区域实时校验,通过才落盘,模型分组立即出现,无需重启 DSH。已登录后可「更换 PAT」(新令牌校验通过才覆盖,粘贴打错不会把可用凭据弄丢)与「清除 PAT」。
16
+ - **多 PAT 保存与手动切换**:每个变体可保存多枚 PAT,卡片上用下拉框切换当前账号(切换只换签名令牌,模型开关、上下文窗口、签到与侧栏额度等配置全部共享,不随账号变化);可「添加 PAT」「删除当前」「清除全部」。保存时先实时校验,通过才落盘;删除或切换不会误删其他账号的已保存模型目录与探测记录。
17
+ - **PAT 即存即用**:保存时先校验,通过才落盘,模型分组立即出现,无需重启 DSH;粘贴打错不会把可用凭据弄丢。
17
18
  - **双变体独立配置**:`qoder`(china)与 `qoder-global`(global)各有自己的凭证文件、路由与已保存目录——qoder.com 生成的令牌对中国区无效,反之亦然,两套永不互串。
18
- - **模型目录三级降级(live → saved → fallback)**:卡片明示当前列表来自哪里——「模型列表更新于 …」(实时拉取)、「当前显示已保存的模型列表,更新于 …」(本账号上次成功目录,重启或断网后顶上来)、「当前显示内置模型列表(尚未从 Qoder 更新)」(编译进插件的兜底名单);最近一次拉取失败时附带原因,并可在卡片上手动「刷新模型列表」。
19
+ - **模型目录三级降级(live → saved → fallback)**:卡片明示当前列表来自哪里——「模型列表更新于 …」(实时拉取)、「当前显示已保存的模型列表,更新于 …」(本账号上次成功目录,重启或断网后顶上来)、「当前显示内置模型列表(尚未从 Qoder 更新)」(编译进插件的兜底名单);最近一次拉取失败时附带原因,并可在卡片上手动「刷新」(同时刷新状态与模型列表)。
19
20
  - **上下文窗口一键切换**:两张变体卡片的「上下文窗口」页列出每个模型的容量(默认值与上游声明的最大值),并各自提供「使用上游声明的最大上下文窗口」开关(默认勾选):勾选按最大窗口(如 Qwen3.8-Max 的 1M)发请求,不勾选按默认窗口(200K),两版开关状态独立保存、互不影响。
20
21
  - **模型自由开关与批量管理**:两张变体卡片均提供「模型开关」页(位于上下文窗口与额度明细之间),可自由开启或关闭指定模型;关闭的模型将自动从 DeepSeek Harness 的模型选择器中隐藏,避免误选与列表冗长;顶部提供按名称/ID的即时搜索框,并支持全选与一键「批量开启」/「批量关闭」。
21
22
  - **每日自动签到与日志明细**:支持每天自动签到领取 100 Credits 算力额度,**签到时刻可在卡片上自定义**(默认 10:00,即上游刷新时间,按 UTC+8),配备双端开关与开机防漏补签机制;卡片常驻「签到日志」面板,提供执行流水记录与「立即签到」、「刷新」、「清空日志」快捷操作。
@@ -113,9 +114,23 @@ dsh plugin --profile web add dsh-qoder-connect
113
114
 
114
115
  (卡片内也有同样指引:「请先在 qoder.com.cn 的账号设置(账号 → 个人访问令牌)中生成 PAT,再粘贴到下方输入框。」)
115
116
 
116
- ### 在卡片上保存
117
+ ### 在卡片上保存与切换
117
118
 
118
- DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密码输入框,点「保存」(期间显示「校验并保存中…」)。保存动作会先经 Qoder 校验令牌对该变体的区域有效,失败则不落盘并提示「PAT 无效或已过期 — 请到账号设置重新生成后再试。」两版各填各的卡片即可两组并存。
119
+ DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密码输入框,点「保存」(期间显示「校验并保存中…」)。保存动作会先经 Qoder 校验令牌对该变体的区域有效,失败则不落盘并提示「PAT 无效或已过期 — 请到账号设置重新生成后再试。」
120
+
121
+ 每个变体可以保存多枚 PAT:保存第二枚后,账号行出现下拉框,选中即切换;「添加 PAT」继续追加(可填一个仅本机显示的备注名),「删除当前」只删当前这一枚,「清除全部」一次清空。**配置永远共享**:模型开关、上下文窗口、签到时刻与侧栏额度设置都属于该变体的全局配置,切换账号不会改变;额度与签到作用于当前激活的账号。签到记录按变体保存,切换后不会自动为新账号补签当天,需要时可点「立即签到」。两版各填各的卡片即可两组并存。
122
+
123
+ ### 命令行(可选)
124
+
125
+ 终端环境可用 CLI 管理同一份多 PAT 列表(`--provider` 缺省为 `qoder`):
126
+
127
+ ```bash
128
+ dsh-qoder-connect pat set <token> --provider qoder # 校验、保存并设为当前
129
+ dsh-qoder-connect pat list --provider qoder # 列出已保存(* = 当前)
130
+ dsh-qoder-connect pat use <id> --provider qoder # 切换
131
+ dsh-qoder-connect pat rm <id> --provider qoder # 删除
132
+ dsh-qoder-connect pat clear --provider qoder # 清除全部(logout 同义)
133
+ ```
119
134
 
120
135
  ### 环境变量兜底
121
136
 
@@ -136,8 +151,8 @@ DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密
136
151
 
137
152
  ```text
138
153
  <profile>/.dsh-qoder-connect/
139
- ├── .qoder-auth.json # 国内版 PAT({version:2, pat, region, savedAt})
140
- ├── .qoder-global-auth.json # 国际版 PAT
154
+ ├── .qoder-auth.json # 国内版 PAT 列表({version:3, activeId, credentials:[{id,pat,region,savedAt,label?}]})
155
+ ├── .qoder-global-auth.json # 国际版 PAT 列表
141
156
  ├── checkin-status.json # 签到状态与历史日志记录
142
157
  └── state/
143
158
  ├── .qoder-catalog.json # 国内版按账号保存的模型目录
package/assets/3.png CHANGED
Binary file
package/assets/5.png CHANGED
Binary file
package/assets/6.png CHANGED
Binary file
package/assets/7.png CHANGED
Binary file
package/assets/8.png CHANGED
Binary file
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as QoderCatalogStore, O as qoderCatalogPath, U as qoderCredentialIdentity, V as QoderCredentialStore, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey } from "./variants-COxX6r1j.js";
2
+ import { K as QoderCredentialStore, M as QoderCatalogStore, N as qoderCatalogPath, P as FALLBACK_QODER_MODELS, T as createQoderTransport, Y as qoderCredentialIdentity, _ as qoderProbePath, f as QODER_CONNECT_VERSION, i as variantFor, l as qoderHostHeartbeatPath, m as QoderProbeStore, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, w as validateApiKey, y as QoderUpstreamClient } from "./variants-B_ZzENbC.js";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { realpathSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
@@ -16,8 +16,11 @@ function printHelp() {
16
16
  "",
17
17
  " doctor secret-free environment diagnostics",
18
18
  " status credential state and remaining Qoder credit",
19
- " pat set validate and store a Personal Access Token (arg, --file, or stdin)",
20
- " pat clear remove the stored credential",
19
+ " pat set validate, store, and activate a Personal Access Token (arg, --file, or stdin)",
20
+ " pat list list saved tokens and mark the active one",
21
+ " pat use <id> switch to a saved token",
22
+ " pat rm <id> remove a saved token",
23
+ " pat clear remove every saved token",
21
24
  " logout alias for `pat clear`",
22
25
  " catalog refresh re-fetch the model list and save it for this account",
23
26
  "",
@@ -147,8 +150,7 @@ async function status(jsonOutput, variant) {
147
150
  }
148
151
  }
149
152
  /** Read a token from the positional arg, --file, or standard input. */
150
- async function readPat(source, rest) {
151
- const inline = rest.find((argument) => !argument.startsWith("--"));
153
+ async function readPat(source, inline) {
152
154
  const pat = (source === void 0 ? inline ?? await readStdin() : source === "-" ? await readStdin() : await readFile(source, "utf8"))?.trim() ?? "";
153
155
  if (pat === "") throw new Error("no token given: pass it as an argument, via --file <path>, or on standard input");
154
156
  return pat;
@@ -158,9 +160,25 @@ async function readStdin() {
158
160
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
159
161
  return Buffer.concat(chunks).toString("utf8");
160
162
  }
161
- async function patSet(variant, file, rest) {
163
+ /**
164
+ * Drop the saved catalog and probe records that belong to one token.
165
+ *
166
+ * The host does this when a saved token is removed from the card; the CLI
167
+ * writes the same files, so it has to leave the same state behind. Leaving them
168
+ * would orphan another account's only copy — or resurrect data for a token the
169
+ * user just deleted.
170
+ */
171
+ function forgetAccount(variant, credential) {
172
+ const identity = qoderCredentialIdentity(credential);
173
+ new QoderCatalogStore({ path: qoderCatalogPath(variant.catalogFilename) }).delete(identity);
174
+ new QoderProbeStore({
175
+ pluginVersion: QODER_CONNECT_VERSION,
176
+ path: qoderProbePath(variant.probeFilename)
177
+ }).clearFor(identity);
178
+ }
179
+ async function patSet(variant, file, inline) {
162
180
  const store = makeStore(variant);
163
- const pat = await readPat(file, rest);
181
+ const pat = await readPat(file, inline);
164
182
  process.stdout.write(`Validating a token against ${variant.displayName} (${variant.region})…\n`);
165
183
  if (!await validateApiKey(pat, variant.region)) {
166
184
  process.stderr.write(`dsh-qoder-connect: the token was refused by ${variant.displayName} (${variant.region}). Nothing was saved.\n`);
@@ -170,17 +188,79 @@ async function patSet(variant, file, rest) {
170
188
  await store.save(pat, "cli");
171
189
  const status = await store.status();
172
190
  process.stdout.write([
173
- `Saved to ${store.ownAuthPath()}${status.pat?.patTail === void 0 ? "" : ` (tail …${status.pat.patTail})`}`,
191
+ `Saved and activated in ${store.ownAuthPath()}${status.pat?.patTail === void 0 ? "" : ` (tail …${status.pat.patTail})`}`,
192
+ `${status.profiles?.length ?? 1} saved token(s); switch with \`dsh-qoder-connect pat use <id>\`.`,
174
193
  "The running plugin picks the token up on its next sweep; no restart needed.",
175
194
  ""
176
195
  ].join("\n"));
177
196
  return 0;
178
197
  }
198
+ /** List the saved tokens, marking the active one; never prints a token. */
199
+ async function patList(jsonOutput, variant) {
200
+ const status = await makeStore(variant).status();
201
+ const profiles = status.profiles ?? [];
202
+ if (jsonOutput) {
203
+ printJson({
204
+ schemaVersion: JSON_SCHEMA_VERSION,
205
+ provider: variant.id,
206
+ activeId: status.activeId ?? null,
207
+ profiles,
208
+ ...status.pat?.source === "env" ? { source: "env" } : {}
209
+ });
210
+ return 0;
211
+ }
212
+ if (profiles.length === 0) {
213
+ process.stdout.write(status.pat?.source === "env" ? `${variant.displayName}: no saved token; the environment token is in use${status.pat.patTail === void 0 ? "" : ` (tail …${status.pat.patTail})`}.\n` : `${variant.displayName}: no saved tokens.\n`);
214
+ return 0;
215
+ }
216
+ const lines = profiles.map((profile) => [
217
+ profile.id === status.activeId ? "*" : " ",
218
+ profile.id,
219
+ profile.label ?? "(no label)",
220
+ profile.patTail === void 0 ? "" : `tail …${profile.patTail}`
221
+ ].filter((part) => part !== "").join(" "));
222
+ process.stdout.write([
223
+ `${variant.displayName} saved tokens (* = active):`,
224
+ ...lines,
225
+ ""
226
+ ].join("\n"));
227
+ return 0;
228
+ }
229
+ /** Make one saved token active. */
230
+ async function patUse(variant, id) {
231
+ const entry = await makeStore(variant).activate(id);
232
+ if (entry === void 0) {
233
+ process.stderr.write(`dsh-qoder-connect: no saved token with id ${JSON.stringify(id)}; run \`pat list\` to see the ids.\n`);
234
+ return 1;
235
+ }
236
+ const tail = entry.pat.slice(-4);
237
+ process.stdout.write(`${variant.displayName}: active token is now ${entry.id}${tail.length === 4 ? ` (tail …${tail})` : ""}.\n`);
238
+ return 0;
239
+ }
240
+ /** Remove one saved token and the account-scoped data that belonged to it. */
241
+ async function patRemove(variant, id) {
242
+ const store = makeStore(variant);
243
+ const removed = await store.remove(id);
244
+ if (removed === void 0) {
245
+ process.stderr.write(`dsh-qoder-connect: no saved token with id ${JSON.stringify(id)}; run \`pat list\` to see the ids.\n`);
246
+ return 1;
247
+ }
248
+ forgetAccount(variant, removed);
249
+ const status = await store.status();
250
+ process.stdout.write([
251
+ `${variant.displayName}: removed ${removed.id}.`,
252
+ status.activeId === void 0 ? "No saved token remains; the plugin is signed out of this variant." : `Active token is now ${status.activeId}.`,
253
+ ""
254
+ ].join("\n"));
255
+ return 0;
256
+ }
179
257
  async function patClear(variant) {
180
258
  const store = makeStore(variant);
259
+ const saved = await store.entries();
260
+ for (const entry of saved) forgetAccount(variant, entry);
181
261
  await store.clear();
182
262
  const status = await store.status();
183
- process.stdout.write(status.state === "configured" ? `${variant.displayName} Connect: removed the stored file, but a token is still active from the environment (${status.pat?.source ?? "env"}).\n` : `${variant.displayName} Connect: signed out; removed ${store.ownAuthPath()}\n`);
263
+ process.stdout.write(status.state === "configured" ? `${variant.displayName} Connect: removed ${saved.length} saved token(s), but a token is still active from the environment (${status.pat?.source ?? "env"}).\n` : `${variant.displayName} Connect: signed out; removed ${store.ownAuthPath()}\n`);
184
264
  return 0;
185
265
  }
186
266
  async function catalogRefresh(variant) {
@@ -249,11 +329,18 @@ async function run(argv) {
249
329
  }
250
330
  let patCommand;
251
331
  let catalogCommand;
332
+ /** The positional a subcommand takes: the token (`set`) or the entry id. */
333
+ let commandArg;
252
334
  if (action === "pat") {
253
335
  const sub = rest.shift();
254
- patCommand = sub === "set" || sub === "clear" ? sub : void 0;
336
+ patCommand = sub === "set" || sub === "list" || sub === "use" || sub === "rm" || sub === "clear" ? sub : void 0;
255
337
  if (patCommand === void 0) {
256
- process.stderr.write(`dsh-qoder-connect: pat needs set or clear; got ${JSON.stringify(sub ?? "")}\n`);
338
+ process.stderr.write(`dsh-qoder-connect: pat needs set, list, use, rm, or clear; got ${JSON.stringify(sub ?? "")}\n`);
339
+ return 1;
340
+ }
341
+ if (patCommand !== "list" && patCommand !== "clear") commandArg = rest.shift();
342
+ if ((patCommand === "use" || patCommand === "rm") && (commandArg === void 0 || commandArg.startsWith("--"))) {
343
+ process.stderr.write(`dsh-qoder-connect: pat ${patCommand} needs an entry id; run \`pat list\` to see them\n`);
257
344
  return 1;
258
345
  }
259
346
  }
@@ -265,7 +352,8 @@ async function run(argv) {
265
352
  return 1;
266
353
  }
267
354
  }
268
- if (rest.filter((flag) => flag !== "--json" && !flag.startsWith("--")).length > 0 || jsonOutput && action !== "doctor" && action !== "status") {
355
+ const patListJson = action === "pat" && patCommand === "list";
356
+ if (rest.filter((flag) => flag !== "--json" && !flag.startsWith("--")).length > 0 || jsonOutput && action !== "doctor" && action !== "status" && !patListJson) {
269
357
  process.stderr.write(`dsh-qoder-connect: invalid options for ${action}: ${flags.join(" ")}\n`);
270
358
  return 1;
271
359
  }
@@ -277,7 +365,15 @@ async function run(argv) {
277
365
  switch (action) {
278
366
  case "doctor": return await doctor(jsonOutput, variant);
279
367
  case "status": return await status(jsonOutput, variant);
280
- case "pat": return patCommand === "set" ? await patSet(variant, file, rest) : await patClear(variant);
368
+ case "pat":
369
+ switch (patCommand) {
370
+ case "set": return await patSet(variant, file, commandArg);
371
+ case "list": return await patList(jsonOutput, variant);
372
+ case "use": return await patUse(variant, commandArg);
373
+ case "rm": return await patRemove(variant, commandArg);
374
+ case "clear": return await patClear(variant);
375
+ }
376
+ return 1;
281
377
  case "logout": return await patClear(variant);
282
378
  case "catalog": return await catalogRefresh(variant);
283
379
  }