dsh-workbuddy-connect 0.4.0 → 0.5.0

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
@@ -4,25 +4,31 @@ English | [中文](./README.md)
4
4
 
5
5
  Brings every model in the WorkBuddy desktop app (GLM-5.3, GLM-5.2, DeepSeek-V4-Pro, DeepSeek-V4-Flash, Kimi-K3, MiniMax-M3, Hy3, and more) straight into [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) — zero configuration in the DSH chat.
6
6
 
7
+ Both the CN **WorkBuddy** and the international **WorkBuddy AI** apps are supported (international support since **v0.5.0**): whichever one you have installed shows up as its own model group, and having both installed shows both, each with its own account and credit.
8
+
7
9
  ## Features
8
10
 
9
11
  - **Works out of the box**: install and enable the plugin, then use it directly in DSH — no extra configuration.
10
12
 
11
13
  ![WorkBuddy models in the DSH model picker](assets/1.png)
12
14
 
15
+ - **CN and international side by side**: the CN app appears as the **WorkBuddy** group and the international one as **WorkBuddy AI**. Their models, accounts, and credit never mix. **Each group follows only its own app's sign-in**: install just the international app and only WorkBuddy AI appears; install both and both groups appear; sign out of one and that group goes away. Settings likewise shows **one card per version**, each with its own account and balance.
16
+
17
+ ![WorkBuddy AI models in the DSH model picker](assets/5.png)
18
+
13
19
  - **Image input**: most models accept images — paste or drop one straight into the conversation (GLM-5.3-Flash, GLM-5.2, the DeepSeek-V4 series, and more); the few text-only models (e.g. GLM-5.1) clearly say so.
14
20
 
15
21
  - **Reasoning levels**: levels explicitly declared by WorkBuddy appear directly — for example, GLM-5.3 and GLM-5.3-Flash offer low / high / max. For some models that do not declare selectable levels, Web and Desktop provide a **Reasoning levels** control in the model picker for a manual check. It sends a few requests and may consume credit. Models without a check result or selectable levels continue to use WorkBuddy's default.
16
22
 
17
- - **Status and detection**: the Settings → Plugins → DSH WorkBuddy Connect card shows the account, token validity, remaining credit, and model offers. It also provides manual reasoning-level detection for eligible models.
23
+ - **Status and detection**: Settings → Plugins → the matching card shows the account, token validity, remaining credit, and model offers. It also lets you refresh the model list manually and shows whether the current list came from the upstream or from the built-in fallback, and provides manual reasoning-level detection for eligible models.
18
24
 
19
25
  - **Rate**: every model name carries its credits multiplier (e.g. `GLM-5.2 · x0.79`, `Hy3 · x0.00`) in both the `/model` popup and the composer's model dropdown. The rate is display-only and never affects requests.
20
26
 
21
- - **Promo badges**: promo badges (`限时免费`, `夜间折扣`) ride the model name itself (e.g. `Hy4 preview · x0.00 · 限时免费`), visible wherever you pick a model; the status card also collects currently-discounted models. Per the WorkBuddy service data, synced each time DSH starts.
27
+ - **Promo badges**: promo badges (`限时免费`, `夜间折扣`) ride the model name itself (e.g. `Hy4 preview · x0.00 · 限时免费`), visible wherever you pick a model; the status card also collects currently-discounted models. Per the WorkBuddy service data, synced each time DSH starts. The international version's promotions come from the service's `modelPromotions` (which carry an effective window). Once a promotion lapses its badge is withdrawn; because the service writes the discounted value into the model's own rate field, the original price cannot be reconstructed, so that model then reports "price unavailable — refresh to update" rather than repeating the discounted rate or claiming the model is free.
22
28
 
23
29
  ![Settings card showing the plugin](assets/2.png)
24
30
 
25
- The expanded card has three tabs: **Status** shows the account, token validity, total credit, and reasoning-level detection; **Context** lists each model's context window; **Details** shows per-package credit and model offers.
31
+ The expanded card has three tabs: **Status** shows the account, token validity, total credit, catalog source, and reasoning-level detection; **Context** lists each model's context window (the international version distinguishes the default window from a larger selectable one); **Details** shows per-package credit and model offers. The CN and international versions each get their own card, showing their own account's information.
26
32
 
27
33
  ![Settings card showing account and remaining credit](assets/3.png)
28
34
 
@@ -38,20 +44,20 @@ For models without declared levels, Web and Desktop instead use user-authorized,
38
44
 
39
45
  ## Install
40
46
 
41
- Prerequisite: the WorkBuddy desktop app is installed and signed in (the plugin reuses the app's sign-in state and follows account switches automatically).
47
+ Prerequisite: the WorkBuddy desktop app is installed and signed in. The plugin reuses the app's sign-in state and follows account switches automatically; the same applies to the international WorkBuddy AI app, and the two do not affect each other.
42
48
 
43
49
  **Match the plugin version to your DSH core** — a mismatched combination fails to start DSH:
44
50
 
45
51
  | Plugin | Required DSH core | Desktop app |
46
52
  |---|---|---|
47
- | **0.3.2+** | `0.1.5-rc.1` or newer | wait for the app's bundled core to follow |
48
- | **0.3.0 – 0.3.1** | `0.1.2-rc.1` | `2.0.5`+ recommended |
53
+ | **0.3.2+** (international support since `0.5.0`) | `0.1.5-rc.1` or newer | `2.0.7`+ (bundled core `0.1.5-rc.1`) |
54
+ | **0.3.0 – 0.3.1** | `0.1.2-rc.1` | `2.0.5` |
49
55
  | **0.2.6** | `0.1.1-rc.2` (older line) | `2.0.3` / `2.0.4` |
50
56
 
51
57
  - On DSH `0.1.5-rc.1` or newer, just install the latest: `dsh plugin --profile web add dsh-workbuddy-connect`
52
58
  - Still on DSH `0.1.2-rc.1`? Stay on `0.3.1`: `dsh plugin --profile web add dsh-workbuddy-connect@0.3.1`
53
59
  - Still on DSH `0.1.1-rc.2`? Stay on the older release: `dsh plugin --profile web add dsh-workbuddy-connect@0.2.6`
54
- - The desktop app (`2.0.5` today, bundled core still `0.1.2-rc.1`) should stay on `0.3.1` until its bundled core reaches `0.1.5`
60
+ - The desktop app has bundled `0.1.5-rc.1` since `2.0.7`, so it can use `0.3.2` and newer directly; `2.0.5` and earlier apps (bundled `0.1.2-rc.1`) should stay on `0.3.1`
55
61
 
56
62
  The plugin runs under all three DSH interfaces: **Web**, **Desktop**, and **TUI**. Pick the install command that matches the profile you use.
57
63
 
@@ -83,15 +89,27 @@ dsh --profile dsh-tui
83
89
 
84
90
  > Note: the `dsh-tui` profile requires pnpm 11 to install packages (a different pnpm on PATH fails with `ERR_PNPM_UNEXPECTED_STORE` — use `npx pnpm@11`).
85
91
 
86
- After installing, switch to a WorkBuddy model in the model picker of the interface you chose. On Web and Desktop, the settings card shows the account, token validity, and remaining credit, and can manually check eligible models for reasoning levels. On TUI, configure `authFile` in `/settings`.
92
+ After installing, switch to a WorkBuddy model in the model picker of the interface you chose. On Web and Desktop, the settings card shows the account, token validity, and remaining credit, can refresh the model list manually, and can check eligible models for reasoning levels; the CN and international versions each have their own card. On TUI, configure `authFile` in `/settings` (or `authFileAI` for the international version).
87
93
 
88
94
  ## CLI
89
95
 
90
96
  `dsh plugin --profile <web|desktop|dsh-tui> exec dsh-workbuddy-connect status`: sign-in state and remaining credit (`--json` for machine-readable output; `doctor` for diagnostics and `logout` for credential cleanup are also available).
91
97
 
98
+ Both commands target the CN version by default; add `--provider workbuddy-ai` for the international one:
99
+
100
+ ```sh
101
+ dsh plugin --profile web exec dsh-workbuddy-connect status --provider workbuddy-ai
102
+ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-ai
103
+ ```
104
+
105
+ `logout` removes only that version's plugin-owned credential copy. It leaves the desktop app's own sign-in alone and does not promise the model group will disappear (the app's credential file still supplies one).
106
+
92
107
  ## Known limitations
93
108
 
94
- - Verified on macOS with the DSH Web / Desktop / TUI profiles (as of 0.3.2 this requires `0.1.5-rc.1`+ and Node 22+; TUI requires the terminal UI package `0.10.0-beta.5` or newer — see the Install section). Windows probes Local and Roaming AppData in order; WSL first reads credentials from the mounted Windows user profile. If the Windows and Linux user names differ and Windows environment variables are not forwarded into WSL, point `WORKBUDDY_AUTH_FILE` at the actual file.
109
+ - Verified on macOS with the DSH Web / Desktop / TUI profiles (as of 0.3.2 this requires `0.1.5-rc.1`+ and Node 22+; TUI requires the terminal UI package `0.10.0-beta.5` or newer — see the Install section). Windows probes Local and Roaming AppData in order; WSL first reads credentials from the mounted Windows user profile. If the Windows and Linux user names differ and Windows environment variables are not forwarded into WSL, point `WORKBUDDY_AUTH_FILE` (or `WORKBUDDY_AI_AUTH_FILE` for the international version) at the actual file.
110
+ - **The international version's model catalog comes from the app's own interface**: the service splits it by User-Agent, which is a private implementation detail that a server-side change can break. When that happens the plugin degrades to this account's last successful catalog and then to its built-in roster, showing the source (live / saved / built-in), the fetch time, and the failure reason on the card — but long-term compatibility is not guaranteed. The CN version's catalog uses the same interface as the official CLI and is unaffected.
111
+ - **International-version environments not yet covered**: on Windows / WSL / Linux no reliable source for the international app's version has been located yet, so the saved value or the built-in default is used. On macOS, real-shim checks covered complete GPT-family replies, tool calls, and continued turns.
112
+ - **Behaviour change with no credentials**: a version whose app was never signed in — and that left no plugin-owned copy — no longer shows a model group. The CN version used to display a built-in fallback list, but every model on it failed when selected.
95
113
  - Relies on WorkBuddy client interfaces (not a public API); the plugin may need updates as WorkBuddy changes.
96
114
 
97
115
  ## Disclaimer
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  将 WorkBuddy 桌面 App 中包含的各种模型(GLM-5.3、GLM-5.2、DeepSeek-V4-Pro、DeepSeek-V4-Flash、Kimi-K3、MiniMax-M3 、Hy3等)自动接入 DeepSeek Harness,实现在 DSH 对话窗口里零配置使用。
8
8
 
9
+ 国内版 **WorkBuddy** 与国际版 **WorkBuddy AI** 同时支持(国际版自 **v0.5.0** 起):装哪个 App 就出现哪个模型分组,两个都装就两组并存,各自用自己的账号与积分。
10
+
9
11
 
10
12
  ## 功能
11
13
 
@@ -15,22 +17,27 @@
15
17
  ![WorkBuddy 模型出现在 DSH 模型选择器中](assets/1.png)
16
18
 
17
19
 
20
+ - **国内版与国际版并存**:国内版显示为「WorkBuddy」分组,国际版(WorkBuddy AI)显示为「WorkBuddy AI」分组。两版的模型、账号和积分互不混用。**各自只看自己那版 App 的登录状态**:只装国际版就只出现「WorkBuddy AI」,两版都装就两组都在,退出其中一版则对应分组消失。设置里也是**两张卡片**,分别展示各自的账号与余额。
21
+
22
+ ![WorkBuddy AI 模型出现在 DSH 模型选择器中](assets/5.png)
23
+
24
+
18
25
  - **图片输入**:大部分模型支持发图,在对话里直接粘贴或拖入图片即可(GLM-5.3-Flash、GLM-5.2、DeepSeek-V4 系列等);少数只支持文字的模型(如 GLM-5.1)会明确提示不支持。
19
26
 
20
27
 
21
28
  - **推理档位**:WorkBuddy 明确声明的档位会直接显示,例如 GLM-5.3 和 GLM-5.3-Flash 可选 low / high / max。对于部分没有声明可选档位的模型,Web 和 Desktop 可在模型选择器中点击「推理等级」手动检测;检测会发送少量请求,可能消耗积分。未检测或没有可用档位的模型仍使用 WorkBuddy 的默认档位。
22
29
 
23
30
 
24
- - **信息查看与检测**:设置 → 插件 → DSH WorkBuddy Connect 卡片可查看账号、令牌有效期、剩余积分和模型优惠;对于可检测模型,也可以在这里手动检测推理档位。
31
+ - **信息查看与检测**:设置 → 插件 → 对应卡片可查看账号、令牌有效期、剩余积分和模型优惠;也可以手动刷新模型列表,并在卡片上看到当前列表来自上游还是内置兜底。对于可检测模型,也可以在这里手动检测推理档位。
25
32
 
26
33
  - **费率比例**:模型选择列表里每个模型名后直接显示积分倍率(如 `GLM-5.2 · x0.79`、`Hy3 · x0.00`),`/model` 弹窗与输入框的模型下拉都能看到。倍率只是显示,不影响实际请求。
27
34
 
28
35
 
29
- - **徽章展示**:促销徽章(限时免费、夜间折扣)直接跟在模型名后面(如 `Hy4 preview · x0.00 · 限时免费`),选模型时一眼可见;设置卡片里也会汇总当前有优惠的模型。以 WorkBuddy 服务端的数据为准,每次启动 DSH 时同步。
36
+ - **徽章展示**:促销徽章(限时免费、夜间折扣)直接跟在模型名后面(如 `Hy4 preview · x0.00 · 限时免费`),选模型时一眼可见;设置卡片里也会汇总当前有优惠的模型。以 WorkBuddy 服务端的数据为准,每次启动 DSH 时同步。国际版的促销来自服务端的 `modelPromotions`(含生效时段):促销过期后徽章会撤销;由于服务端把折后价直接写在模型的倍率字段里,原价无法还原,此时该模型的倍率会显示为「价格未知 — 刷新后更新」,而不是继续显示折扣价或「免费」。
30
37
 
31
38
  ![设置卡片显示插件](assets/2.png)
32
39
 
33
- 卡片展开后分为「状态 / 上下文 / 明细」三个标签:状态页展示账号、令牌有效期、合计积分和推理档位检测;上下文页列出各模型的上下文窗口;明细页展示各套餐余量与模型优惠。
40
+ 卡片展开后分为「状态 / 上下文 / 明细」三个标签:状态页展示账号、令牌有效期、合计积分、模型列表来源与推理档位检测;上下文页列出各模型的上下文窗口(国际版会区分默认窗口与可选的更大窗口);明细页展示各套餐余量与模型优惠。国内版与国际版各有一张自己的卡片,各显示自己账号的信息。
34
41
 
35
42
  ![设置卡片显示账号与剩余积分](assets/3.png)
36
43
 
@@ -47,20 +54,20 @@ WorkBuddy 中模型的推理档位信息目前分散在上游接口与客户端
47
54
 
48
55
  ## 安装
49
56
 
50
- 前置:已安装并登录 WorkBuddy 桌面 App(插件复用 App 的登录状态,账号切换自动跟随)。
57
+ 前置:已安装并登录 WorkBuddy 桌面 App。插件复用 App 的登录状态,账号切换自动跟随;装了国际版 WorkBuddy AI 的同样适用,两版互不影响。
51
58
 
52
59
  **版本对应(重要)**:本插件与 DSH 核心版本一一对应,不可混用——不匹配的组合会导致 DSH 启动失败:
53
60
 
54
61
  | 插件版本 | 要求的 DSH 核心 | 桌面 App |
55
62
  |---|---|---|
56
- | **0.3.2+** | `0.1.5-rc.1` 及以上 | 待 App 内置核心跟进 |
57
- | **0.3.0 – 0.3.1** | `0.1.2-rc.1` | `2.0.5`+ |
63
+ | **0.3.2+**(国际版支持自 `0.5.0`) | `0.1.5-rc.1` 及以上 | `2.0.7`+(内置核心已跟进 `0.1.5-rc.1`) |
64
+ | **0.3.0 – 0.3.1** | `0.1.2-rc.1` | `2.0.5` |
58
65
  | **0.2.6** | `0.1.1-rc.2`(旧线) | `2.0.3` / `2.0.4` |
59
66
 
60
67
  - DSH `0.1.5-rc.1` 及以上的用户,正常安装最新版即可:`dsh plugin --profile web add dsh-workbuddy-connect`
61
68
  - 还在用 DSH `0.1.2-rc.1` 的用户,请停留在 `0.3.1`:`dsh plugin --profile web add dsh-workbuddy-connect@0.3.1`
62
69
  - 还在用 DSH `0.1.1-rc.2` 的用户,请停留在 `0.2.6`:`dsh plugin --profile web add dsh-workbuddy-connect@0.2.6`
63
- - 桌面 App(当前 `2.0.5`,内置核心仍为 `0.1.2-rc.1`)请在 App 内核跟进 `0.1.5` 前继续使用 `0.3.1`
70
+ - 桌面 App 自 `2.0.7` 起内置核心已是 `0.1.5-rc.1`,可直接使用 `0.3.2` 及以上版本;`2.0.5` 及更早的 App(内置 `0.1.2-rc.1`)请继续使用 `0.3.1`
64
71
 
65
72
  插件在三种 DSH 界面下均可运行:**Web**、**Desktop**、**TUI**。根据你使用的 profile 选对应命令安装。
66
73
 
@@ -92,15 +99,27 @@ dsh --profile dsh-tui
92
99
 
93
100
  > 提示:`dsh-tui` profile 需用 pnpm 11 安装(PATH 里是其他版本会报 `ERR_PNPM_UNEXPECTED_STORE`,用 `npx pnpm@11` 即可)。
94
101
 
95
- 安装后,在对应界面的模型选择器里切换到 WorkBuddy 模型即可使用。Web 和 Desktop 下,设置卡片可查看账号信息、令牌有效期与剩余积分,并手动检测符合条件模型的推理档位;TUI 下可在 `/settings` 里配置 `authFile`。
102
+ 安装后,在对应界面的模型选择器里切换到 WorkBuddy 模型即可使用。Web 和 Desktop 下,设置卡片可查看账号信息、令牌有效期与剩余积分,手动刷新模型列表,并手动检测符合条件模型的推理档位;国内版与国际版各有自己的卡片。TUI 下可在 `/settings` 里配置 `authFile`(国际版为 `authFileAI`)。
96
103
 
97
104
  ## 命令行
98
105
 
99
106
  `dsh plugin --profile <web|desktop|dsh-tui> exec dsh-workbuddy-connect status`:登录状态与剩余积分(`--json` 输出机器可读格式;另有 `doctor` 诊断、`logout` 清理凭据)。
100
107
 
108
+ 默认操作国内版;加 `--provider workbuddy-ai` 操作国际版:
109
+
110
+ ```sh
111
+ dsh plugin --profile web exec dsh-workbuddy-connect status --provider workbuddy-ai
112
+ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-ai
113
+ ```
114
+
115
+ `logout` 只删除该版插件自留的凭据副本,不动桌面 App 自己的登录,也不承诺一定让模型分组消失(App 的凭据文件仍在时依然生效)。
116
+
101
117
  ## 已知限制
102
118
 
103
- - 在 macOS 的 DSH Web / Desktop / TUI 下验证通过(0.3.2 起要求 `0.1.5-rc.1`+、Node 22+;TUI 需终端界面插件 `0.10.0-beta.5` 及以上,见安装章节说明)。Windows 会依次探测 Local 与 Roaming AppData;WSL 会优先从挂载的 Windows 用户目录读取登录凭据。若 Windows 与 Linux 用户名不同且 Windows 环境变量未传入 WSL,请通过 `WORKBUDDY_AUTH_FILE` 指定实际位置。
119
+ - 在 macOS 的 DSH Web / Desktop / TUI 下验证通过(0.3.2 起要求 `0.1.5-rc.1`+、Node 22+;TUI 需终端界面插件 `0.10.0-beta.5` 及以上,见安装章节说明)。Windows 会依次探测 Local 与 Roaming AppData;WSL 会优先从挂载的 Windows 用户目录读取登录凭据。若 Windows 与 Linux 用户名不同且 Windows 环境变量未传入 WSL,请通过 `WORKBUDDY_AUTH_FILE`(国际版为 `WORKBUDDY_AI_AUTH_FILE`)指定实际位置。
120
+ - **国际版的模型目录来自 App 界面接口**:服务端按 User-Agent 分流下发,属私有实现,上游改动可能使其失效。届时插件按「本账号上次成功目录 → 内置目录」降级,并在卡片上标明来源(实时 / 已保存 / 内置)、更新时间与失败原因,但不能保证长期兼容。国内版目录走官方 CLI 同款接口,不受此影响。
121
+ - **国际版仍未覆盖的环境**:Windows / WSL / Linux 下国际版 App 的版本读取尚未找到可靠来源,会退回最近保存的版本或内置值。macOS 上已通过真实 shim 验证 GPT 系完整回复、工具调用与续轮。
122
+ - **无凭据时的行为变化**:某版 App 从未登录、也没留下插件自留副本时,该版模型分组不再显示。此前国内版会显示一份内置兜底列表,但那些模型选了必然报错。
104
123
  - 依赖 WorkBuddy 客户端接口(非官方开放 API),WorkBuddy 更新后插件可能需要随之调整。
105
124
 
106
125
  ## 免责声明
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { T as workbuddyOwnAuthPath, _ as FALLBACK_WORKBUDDY_MODELS, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, l as WorkBuddyUpstreamClient, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, x as WorkBuddyCredentialStore } from "./host-heartbeat-0UAWqWac.js";
2
+ import { E as WorkBuddyUpstreamClient, V as resolveAppVersion, b as WorkBuddyCredentialStore, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, l as readHostHeartbeat, n as CN_VARIANT, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, u as workbuddyHostHeartbeatPath } from "./variants-GgLTJgZs.js";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/bin.ts
@@ -11,38 +11,61 @@ function safeMessage(error) {
11
11
  }
12
12
  function printHelp() {
13
13
  process.stdout.write([
14
- "Usage: dsh-workbuddy-connect <doctor|status|logout> [--json]",
14
+ "Usage: dsh-workbuddy-connect <doctor|status|logout> [--provider <id>] [--json]",
15
15
  "",
16
16
  " doctor secret-free sign-in and environment diagnostics",
17
17
  " status sign-in state, remaining WorkBuddy credit, and host-bundle health",
18
18
  " logout remove the plugin-owned credential copy (the desktop app keeps its sign-in)",
19
- " --json emit one secret-free JSON document (doctor/status only)",
19
+ "",
20
+ " --provider which product to inspect; defaults to workbuddy",
21
+ ` one of: ${WORKBUDDY_VARIANTS.map((variant) => variant.id).join(", ")}`,
22
+ " --json emit one secret-free JSON document (doctor/status only)",
20
23
  ""
21
24
  ].join("\n"));
22
25
  }
23
26
  function printJson(value) {
24
27
  process.stdout.write(`${JSON.stringify(value)}\n`);
25
28
  }
26
- function makeStore() {
29
+ /** One variant's store plus the client that performs its refreshes. */
30
+ function makeStore(variant) {
27
31
  const client = new WorkBuddyUpstreamClient();
28
- return new WorkBuddyCredentialStore({ refresh: (credential) => client.refreshToken(credential) });
32
+ return new WorkBuddyCredentialStore({
33
+ variant,
34
+ refresh: (credential) => client.refreshToken(credential)
35
+ });
36
+ }
37
+ /** The plugin-owned credential copy for one variant, for display. */
38
+ function ownAuthPath(variant) {
39
+ return makeStore(variant).ownAuthPath();
29
40
  }
30
- async function doctor(jsonOutput) {
31
- const store = makeStore();
41
+ /** Fallback roster size for one variant. */
42
+ function fallbackCount(variant) {
43
+ return variant.id === CN_VARIANT.id ? FALLBACK_WORKBUDDY_MODELS.length : FALLBACK_WORKBUDDY_AI_MODELS.length;
44
+ }
45
+ async function doctor(jsonOutput, variant) {
46
+ const store = makeStore(variant);
32
47
  const status = await store.status();
33
48
  const desktopPresent = await store.desktopFilePresent();
34
49
  const heartbeat = await readHostHeartbeat();
35
50
  const hostAlive = heartbeat !== void 0 && isHeartbeatProcessAlive(heartbeat);
51
+ const appVersion = variant.region === "global" ? await resolveAppVersion() : void 0;
36
52
  const report = {
37
53
  schemaVersion: JSON_SCHEMA_VERSION,
38
54
  package: "dsh-workbuddy-connect",
39
55
  version: WORKBUDDY_CONNECT_VERSION,
40
56
  node: process.version,
57
+ provider: variant.id,
58
+ displayName: variant.displayName,
41
59
  desktopAuthFile: {
42
- path: store.desktopAuthPath() ?? "(no platform default; set WORKBUDDY_AUTH_FILE)",
60
+ path: store.desktopAuthPath() ?? `(no platform default; set ${variant.env})`,
43
61
  present: desktopPresent
44
62
  },
45
- ownAuthFile: workbuddyOwnAuthPath(),
63
+ ownAuthFile: ownAuthPath(variant),
64
+ ...appVersion === void 0 ? {} : { catalogUserAgent: {
65
+ version: appVersion.version,
66
+ source: appVersion.source,
67
+ ...appVersion.bundle === void 0 ? {} : { bundle: appVersion.bundle }
68
+ } },
46
69
  hostHeartbeat: {
47
70
  path: workbuddyHostHeartbeatPath(),
48
71
  present: heartbeat !== void 0,
@@ -53,27 +76,28 @@ async function doctor(jsonOutput) {
53
76
  processAlive: hostAlive
54
77
  },
55
78
  signIn: status.state,
56
- fallbackModels: FALLBACK_WORKBUDDY_MODELS.length,
79
+ fallbackModels: fallbackCount(variant),
57
80
  hints: [
58
- ...status.state === "signed-in" ? [] : ["Sign in once in the WorkBuddy desktop app, then run status again."],
59
- ...desktopPresent ? [] : [`No WorkBuddy desktop auth file at the expected path; set WORKBUDDY_AUTH_FILE if it lives elsewhere.`],
81
+ ...status.state === "signed-in" ? [] : [`Sign in once in the ${variant.appName} desktop app, then run status again.`],
82
+ ...desktopPresent ? [] : [`No ${variant.appName} desktop auth file at the expected path; set ${variant.env} if it lives elsewhere.`],
60
83
  ...hostAlive ? [] : ["Host bundle not running in this DSH profile (or the process exited). The browser card and provider are unavailable until DSH starts the plugin."]
61
84
  ]
62
85
  };
63
86
  if (jsonOutput) printJson(report);
64
87
  else process.stdout.write([
65
- `WorkBuddy Connect ${WORKBUDDY_CONNECT_VERSION} on ${process.version}`,
88
+ `${variant.displayName} Connect ${WORKBUDDY_CONNECT_VERSION} on ${process.version}`,
66
89
  `Desktop auth file: ${report.desktopAuthFile.present ? "present" : "missing"} (${report.desktopAuthFile.path})`,
67
90
  `Host bundle: ${hostAlive ? `running (pid ${heartbeat.pid})` : heartbeat !== void 0 ? "stale heartbeat (process exited)" : "not started"}`,
68
91
  `Sign-in state: ${report.signIn}`,
69
92
  `Static fallback models: ${report.fallbackModels}`,
93
+ ...appVersion === void 0 ? [] : [`Catalog User-Agent version: ${appVersion.version} (${appVersion.source})`],
70
94
  ...report.hints.map((hint) => `Hint: ${hint}`),
71
95
  ""
72
96
  ].join("\n"));
73
97
  return status.state === "signed-in" && desktopPresent ? 0 : 1;
74
98
  }
75
- async function status(jsonOutput) {
76
- const store = makeStore();
99
+ async function status(jsonOutput, variant) {
100
+ const store = makeStore(variant);
77
101
  const client = new WorkBuddyUpstreamClient();
78
102
  const authStatus = await store.status();
79
103
  const heartbeat = await readHostHeartbeat();
@@ -84,10 +108,11 @@ async function status(jsonOutput) {
84
108
  schemaVersion: JSON_SCHEMA_VERSION,
85
109
  package: "dsh-workbuddy-connect",
86
110
  version: WORKBUDDY_CONNECT_VERSION,
111
+ provider: variant.id,
87
112
  status: "signed-out",
88
113
  hostBundle: hostState
89
114
  });
90
- else process.stdout.write(`WorkBuddy Connect: signed out\nHost bundle: ${hostState}\n`);
115
+ else process.stdout.write(`${variant.displayName} Connect: signed out\nHost bundle: ${hostState}\n`);
91
116
  return 1;
92
117
  }
93
118
  let credits;
@@ -106,6 +131,7 @@ async function status(jsonOutput) {
106
131
  schemaVersion: JSON_SCHEMA_VERSION,
107
132
  package: "dsh-workbuddy-connect",
108
133
  version: WORKBUDDY_CONNECT_VERSION,
134
+ provider: variant.id,
109
135
  status: "signed-in",
110
136
  ...expiresAt === void 0 ? {} : { accessTokenExpires: expiresAt },
111
137
  ...authStatus.nickname === void 0 ? {} : { nickname: authStatus.nickname },
@@ -118,7 +144,7 @@ async function status(jsonOutput) {
118
144
  return 0;
119
145
  }
120
146
  process.stdout.write([
121
- `WorkBuddy Connect: signed in${authStatus.nickname === void 0 ? "" : ` as ${authStatus.nickname}`}`,
147
+ `${variant.displayName} Connect: signed in${authStatus.nickname === void 0 ? "" : ` as ${authStatus.nickname}`}`,
122
148
  ...expiresAt === void 0 ? [] : [`Access token expires ${expiresAt} (refresh is automatic)`],
123
149
  credits?.error === void 0 ? `Remaining credit: ${credits?.total ?? "unknown"}` : `Remaining credit: unavailable (${credits.error})`,
124
150
  `Host bundle: ${hostAlive ? `running (pid ${heartbeat.pid})` : hostState === "stale" ? "stale heartbeat (DSH process exited)" : "not started in this profile"}`,
@@ -144,18 +170,40 @@ async function run(argv) {
144
170
  }
145
171
  const action = rawAction;
146
172
  const jsonOutput = flags.includes("--json");
147
- if (flags.filter((flag) => flag !== "--json").length > 0 || jsonOutput && action === "logout") {
173
+ let providerId;
174
+ const rest = [];
175
+ for (let index = 0; index < flags.length; index += 1) {
176
+ const flag = flags[index];
177
+ if (flag === "--provider") {
178
+ providerId = flags[index + 1];
179
+ index += 1;
180
+ continue;
181
+ }
182
+ if (flag.startsWith("--provider=")) {
183
+ providerId = flag.slice(11);
184
+ continue;
185
+ }
186
+ rest.push(flag);
187
+ }
188
+ const variant = providerId === void 0 ? CN_VARIANT : variantFor(providerId);
189
+ if (variant === void 0) {
190
+ process.stderr.write(`dsh-workbuddy-connect: unknown provider ${JSON.stringify(providerId)}; expected one of ${WORKBUDDY_VARIANTS.map((v) => v.id).join(", ")}\n`);
191
+ return 1;
192
+ }
193
+ if (rest.filter((flag) => flag !== "--json").length > 0 || jsonOutput && action === "logout") {
148
194
  process.stderr.write(`dsh-workbuddy-connect: invalid options for ${action}: ${flags.join(" ")}\n`);
149
195
  return 1;
150
196
  }
151
197
  try {
152
198
  switch (action) {
153
- case "doctor": return await doctor(jsonOutput);
154
- case "status": return await status(jsonOutput);
155
- case "logout":
156
- await makeStore().logout();
157
- process.stdout.write(`WorkBuddy Connect: removed ${workbuddyOwnAuthPath()}; the desktop app's sign-in is untouched\n`);
199
+ case "doctor": return await doctor(jsonOutput, variant);
200
+ case "status": return await status(jsonOutput, variant);
201
+ case "logout": {
202
+ const store = makeStore(variant);
203
+ await store.logout();
204
+ process.stdout.write(`${variant.displayName} Connect: removed ${store.ownAuthPath()}; the desktop app's sign-in is untouched\n`);
158
205
  return 0;
206
+ }
159
207
  }
160
208
  } catch (error) {
161
209
  process.stderr.write(`dsh-workbuddy-connect: ${action} failed: ${safeMessage(error)}\n`);