dsh-qoder-connect 0.2.6 → 0.2.8
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 +33 -8
- package/README.md +33 -8
- package/assets/3.png +0 -0
- package/assets/5.png +0 -0
- package/assets/6.png +0 -0
- package/assets/7.png +0 -0
- package/assets/8.png +0 -0
- package/lib/bin.js +109 -13
- package/lib/client.js +301 -117
- package/lib/index.d.ts +152 -22
- package/lib/index.js +369 -398
- package/lib/{variants-COxX6r1j.js → variants-Dl1AbWre.js} +474 -51
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -13,14 +13,16 @@ Configure only what you use: a variant with no saved PAT shows no model group an
|
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
|
-
- **
|
|
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
|
|
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.
|
|
22
23
|
- **Sidebar quota card + credit details** — the *“Qoder sidebar display”* card toggles a per-variant sidebar quota widget (off by default; each toggle needs its variant's PAT saved) with one shared refresh interval (default 5 min, minimum 1 min). Clicking the sidebar widget opens the *Qoder quota* panel: one row per credit package — *“Remaining / Total + bar | Expires”* — where a campaign grant (the international edition’s *SOTA Exclusive Credits*, for one) keeps its own upstream-given title and **its own expiry**. When the account reports no overall deadline (the upstream’s year-9999 sentinel), the panel states when the PAT was authorized rather than a fabricated date. Click the same widget again to close; click the other to switch variants.
|
|
23
24
|
- **Rate display `x<priceFactor>`** — each model name is suffixed with the upstream-reported price multiplier (e.g. `Some Model · x0.79`, free is `x0`), spelled `x<n>` from the catalog's `price_factor`. Display only — it never changes the request; models whose rate the upstream did not report simply show no suffix.
|
|
25
|
+
- **Upstream queue hints are honored** — when Qoder answers a queued or throttled request (401/403/429 body) with `retryAfterSeconds`, the plugin turns it into a local `Retry-After` header and hands it to DSH as `failure.providerRetryAfterMs`; the host retry engine can then wait until the moment the upstream asked for, instead of hammering the same still-closed window with its local backoff.
|
|
24
26
|
|
|
25
27
|
## Screenshots
|
|
26
28
|
|
|
@@ -80,7 +82,7 @@ Enable daily automatic check-in in the settings card and set each variant's own
|
|
|
80
82
|
|
|
81
83
|
Prerequisites:
|
|
82
84
|
|
|
83
|
-
- DSH core `0.1
|
|
85
|
+
- DSH core `0.2.1-rc.1` or newer (this plugin peers on the `@deepseek-ai/*` `>=0.2.1-rc.1` line);
|
|
84
86
|
- Node.js `^22.19.0 || >=24.0.0` (per `package.json` engines);
|
|
85
87
|
- your own Qoder account and at least one Personal Access Token.
|
|
86
88
|
|
|
@@ -93,6 +95,15 @@ dsh plugin --profile web add dsh-qoder-connect
|
|
|
93
95
|
|
|
94
96
|
Swap the `--profile` value for the profile you use (`web` / `desktop` / `tui`, e.g. `dsh plugin --profile desktop add dsh-qoder-connect`) — data stays inside that profile, so web / desktop / tui never collide. On a terminal-only profile (TUI) there is no browser card: manage the PAT and check status through the bundled CLI instead; the settings card and sidebar widgets require Web or Desktop.
|
|
95
97
|
|
|
98
|
+
> **DSH older than `0.2.1-rc.1`?** Use **[v0.2.5](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.2.5)** instead — the last release on the 0.1.7 line (DSH ≥ `0.1.7-rc.1`):
|
|
99
|
+
>
|
|
100
|
+
> ```sh
|
|
101
|
+
> # From GitHub (recommended, pin the tag)
|
|
102
|
+
> dsh plugin --profile web add github:masknull/dsh-qoder-connect#v0.2.5
|
|
103
|
+
> # Or from npm
|
|
104
|
+
> dsh plugin --profile web add dsh-qoder-connect@0.2.5
|
|
105
|
+
> ```
|
|
106
|
+
>
|
|
96
107
|
> **DSH older than `0.1.7-rc.1`?** Use **[v0.2.0](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.2.0)** instead — the last release supporting the 0.1.5 line (DSH ≥ `0.1.5-rc.2`):
|
|
97
108
|
>
|
|
98
109
|
> ```sh
|
|
@@ -102,7 +113,7 @@ Swap the `--profile` value for the profile you use (`web` / `desktop` / `tui`, e
|
|
|
102
113
|
> dsh plugin --profile web add dsh-qoder-connect@0.2.0
|
|
103
114
|
> ```
|
|
104
115
|
>
|
|
105
|
-
> Upgrading from 0.1.5?
|
|
116
|
+
> Upgrading from 0.1.5? Newer releases removed the automatic migration of the old host's `settings.yaml`. To carry your old configuration over, migrate through **[v0.1.9](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.1.9)** (the first release with the data migration) first, then upgrade.
|
|
106
117
|
|
|
107
118
|
## Configuration
|
|
108
119
|
|
|
@@ -113,9 +124,23 @@ Swap the `--profile` value for the profile you use (`web` / `desktop` / `tui`, e
|
|
|
113
124
|
|
|
114
125
|
(The card repeats the same guidance: *“Generate a PAT in your qoder.com account settings (Account → Personal Access Token), then paste it here.”*)
|
|
115
126
|
|
|
116
|
-
### Save
|
|
127
|
+
### Save and switch on the card
|
|
117
128
|
|
|
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
|
|
129
|
+
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.”*
|
|
130
|
+
|
|
131
|
+
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.
|
|
132
|
+
|
|
133
|
+
### Command line (optional)
|
|
134
|
+
|
|
135
|
+
On a terminal-only profile the CLI manages the same list (`--provider` defaults to `qoder`):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
dsh-qoder-connect pat set <token> --provider qoder # validate, save, and activate
|
|
139
|
+
dsh-qoder-connect pat list --provider qoder # list saved tokens (* = active)
|
|
140
|
+
dsh-qoder-connect pat use <id> --provider qoder # switch
|
|
141
|
+
dsh-qoder-connect pat rm <id> --provider qoder # remove
|
|
142
|
+
dsh-qoder-connect pat clear --provider qoder # remove all (logout is an alias)
|
|
143
|
+
```
|
|
119
144
|
|
|
120
145
|
### Environment-variable fallback
|
|
121
146
|
|
|
@@ -136,8 +161,8 @@ Everything the plugin owns lives in one data directory: `<profile>/.dsh-qoder-co
|
|
|
136
161
|
|
|
137
162
|
```text
|
|
138
163
|
<profile>/.dsh-qoder-connect/
|
|
139
|
-
├── .qoder-auth.json # China PAT ({version:
|
|
140
|
-
├── .qoder-global-auth.json # Global PAT
|
|
164
|
+
├── .qoder-auth.json # China PAT list ({version:3, activeId, credentials:[{id,pat,region,savedAt,label?}]})
|
|
165
|
+
├── .qoder-global-auth.json # Global PAT list
|
|
141
166
|
├── checkin-status.json # check-in status and history logs
|
|
142
167
|
└── state/
|
|
143
168
|
├── .qoder-catalog.json # China per-account saved catalog
|
package/README.md
CHANGED
|
@@ -13,14 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
## 功能
|
|
15
15
|
|
|
16
|
-
-
|
|
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),配备双端开关与开机防漏补签机制;卡片常驻「签到日志」面板,提供执行流水记录与「立即签到」、「刷新」、「清空日志」快捷操作。
|
|
22
23
|
- **侧栏额度展示 + 额度明细**:「Qoder 侧栏展示」卡片可分别为两版开启侧栏底部的额度小卡(默认关,开启前需为该变体保存 PAT),共享一个刷新间隔(默认 5 分钟,最小 1 分钟)。点侧栏小卡在中间面板打开「Qoder 额度」明细:按资源包逐行给出「剩余 / 总量 + 进度条|到期时间」,活动赠送包(如国际版「SOTA 专属积分」)按上游给定的名称与**各自的到期日**单独成行;账号整体无到期(上游回 9999 占位值)时展示 PAT 的授权时间而非假日期;再点同一张卡关闭,点另一张切换到对应版本。
|
|
23
24
|
- **倍率显示 `x<priceFactor>`**:模型名后直接拼上游报价的价格倍率(如 `某模型 · x0.79`,免费为 `x0`),来自目录接口的 `price_factor` 字段,仅作展示、不影响请求;上游未报倍率的模型不显示后缀,不会凭空补一个数。
|
|
25
|
+
- **尊重上游的排队建议**:Qoder 在排队/限流答复里给出 `retryAfterSeconds` 时(401/403/429 的 body),插件把它转成本机 `Retry-After` 响应头,并作为 `failure.providerRetryAfterMs` 交给 DSH 的重试策略;宿主重试引擎因此会等到上游要求的时刻再发起下一次请求,而不是用本地退避提前撞回同一个未打开的窗口。
|
|
24
26
|
|
|
25
27
|
## 界面一览
|
|
26
28
|
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
|
|
81
83
|
前置条件:
|
|
82
84
|
|
|
83
|
-
- DSH 核心 `0.1
|
|
85
|
+
- DSH 核心 `0.2.1-rc.1` 及以上(本插件 peer 依赖 `@deepseek-ai/*` `>=0.2.1-rc.1` 线);
|
|
84
86
|
- Node.js `^22.19.0 || >=24.0.0`(`package.json` engines);
|
|
85
87
|
- 你自己的 Qoder 账号,以及至少一枚个人访问令牌。
|
|
86
88
|
|
|
@@ -93,6 +95,15 @@ dsh plugin --profile web add dsh-qoder-connect
|
|
|
93
95
|
|
|
94
96
|
按你使用的 profile 换 `--profile` 的值(`web` / `desktop` / `tui`,如 `dsh plugin --profile desktop add dsh-qoder-connect`)。装到哪个 profile,数据就落在哪个 profile 目录里,web / desktop / tui 各自独立。没有浏览器卡片的终端环境(TUI)下依旧可用 CLI 保存 PAT 并驱动模型;设置卡片与侧栏额度卡需要 Web/Desktop 界面。
|
|
95
97
|
|
|
98
|
+
> **DSH 低于 `0.2.1-rc.1`?** 请改用最后一个支持 0.1.7 线的版本 **[v0.2.5](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.2.5)**(要求 DSH ≥ `0.1.7-rc.1`):
|
|
99
|
+
>
|
|
100
|
+
> ```sh
|
|
101
|
+
> # GitHub(推荐,指定 tag)
|
|
102
|
+
> dsh plugin --profile web add github:masknull/dsh-qoder-connect#v0.2.5
|
|
103
|
+
> # 或从 npm 安装
|
|
104
|
+
> dsh plugin --profile web add dsh-qoder-connect@0.2.5
|
|
105
|
+
> ```
|
|
106
|
+
>
|
|
96
107
|
> **DSH 低于 `0.1.7-rc.1`?** 请改用最后一个支持 0.1.5 线的版本 **[v0.2.0](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.2.0)**(要求 DSH ≥ `0.1.5-rc.2`):
|
|
97
108
|
>
|
|
98
109
|
> ```sh
|
|
@@ -102,7 +113,7 @@ dsh plugin --profile web add dsh-qoder-connect
|
|
|
102
113
|
> dsh plugin --profile web add dsh-qoder-connect@0.2.0
|
|
103
114
|
> ```
|
|
104
115
|
>
|
|
105
|
-
> 从 0.1.5
|
|
116
|
+
> 从 0.1.5 升级上来的老用户:新版本已移除旧宿主 `settings.yaml` 的自动迁移;如需保留旧配置,请先经 **[v0.1.9](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.1.9)**(首个带数据迁移的版本)完成迁移后,再升级。
|
|
106
117
|
|
|
107
118
|
## 配置与使用
|
|
108
119
|
|
|
@@ -113,9 +124,23 @@ dsh plugin --profile web add dsh-qoder-connect
|
|
|
113
124
|
|
|
114
125
|
(卡片内也有同样指引:「请先在 qoder.com.cn 的账号设置(账号 → 个人访问令牌)中生成 PAT,再粘贴到下方输入框。」)
|
|
115
126
|
|
|
116
|
-
###
|
|
127
|
+
### 在卡片上保存与切换
|
|
117
128
|
|
|
118
|
-
DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密码输入框,点「保存」(期间显示「校验并保存中…」)。保存动作会先经 Qoder 校验令牌对该变体的区域有效,失败则不落盘并提示「PAT 无效或已过期 —
|
|
129
|
+
DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密码输入框,点「保存」(期间显示「校验并保存中…」)。保存动作会先经 Qoder 校验令牌对该变体的区域有效,失败则不落盘并提示「PAT 无效或已过期 — 请到账号设置重新生成后再试。」
|
|
130
|
+
|
|
131
|
+
每个变体可以保存多枚 PAT:保存第二枚后,账号行出现下拉框,选中即切换;「添加 PAT」继续追加(可填一个仅本机显示的备注名),「删除当前」只删当前这一枚,「清除全部」一次清空。**配置永远共享**:模型开关、上下文窗口、签到时刻与侧栏额度设置都属于该变体的全局配置,切换账号不会改变;额度与签到作用于当前激活的账号。签到记录按变体保存,切换后不会自动为新账号补签当天,需要时可点「立即签到」。两版各填各的卡片即可两组并存。
|
|
132
|
+
|
|
133
|
+
### 命令行(可选)
|
|
134
|
+
|
|
135
|
+
终端环境可用 CLI 管理同一份多 PAT 列表(`--provider` 缺省为 `qoder`):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
dsh-qoder-connect pat set <token> --provider qoder # 校验、保存并设为当前
|
|
139
|
+
dsh-qoder-connect pat list --provider qoder # 列出已保存(* = 当前)
|
|
140
|
+
dsh-qoder-connect pat use <id> --provider qoder # 切换
|
|
141
|
+
dsh-qoder-connect pat rm <id> --provider qoder # 删除
|
|
142
|
+
dsh-qoder-connect pat clear --provider qoder # 清除全部(logout 同义)
|
|
143
|
+
```
|
|
119
144
|
|
|
120
145
|
### 环境变量兜底
|
|
121
146
|
|
|
@@ -136,8 +161,8 @@ DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密
|
|
|
136
161
|
|
|
137
162
|
```text
|
|
138
163
|
<profile>/.dsh-qoder-connect/
|
|
139
|
-
├── .qoder-auth.json # 国内版 PAT
|
|
140
|
-
├── .qoder-global-auth.json # 国际版 PAT
|
|
164
|
+
├── .qoder-auth.json # 国内版 PAT 列表({version:3, activeId, credentials:[{id,pat,region,savedAt,label?}]})
|
|
165
|
+
├── .qoder-global-auth.json # 国际版 PAT 列表
|
|
141
166
|
├── checkin-status.json # 签到状态与历史日志记录
|
|
142
167
|
└── state/
|
|
143
168
|
├── .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 {
|
|
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-Dl1AbWre.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
|
|
20
|
-
" pat
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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":
|
|
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
|
}
|