dsh-notify-tray 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,160 @@
1
- # Temporary Holding Version
1
+ # dsh-notify-tray
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **English** | [简体中文](README.zh-CN.md)
4
+
5
+ A notification + status-bar plugin for DeepSeek Harness (DSH). Works on macOS, Windows and Linux.
6
+
7
+ - **System notifications** — when a task finishes, fails, needs approval, needs you to pick an option, or waits for your input, you get a native notification written in the voice of a little whale (e.g. *"🐳 All done! 'Write the weekly report' is finished — come check it out"*). Clicking a notification jumps straight to the session.
8
+ - **Status-bar whale** — a small whale lives in your status bar and tells you what's going on through its posture. The drop-down menu lists running tasks, items waiting for you, today's usage and recently finished tasks; click any entry to open that session.
9
+ - **Settings page** — every option can be changed directly on the DSH **Plugins** page, including the API price of the model you're currently using and the plan you subscribe to (Kimi / GLM / OpenCode / MiMo / Gemini / Grok / Claude / GPT / MiniMax / DeepSeek or custom). Balance and remaining quota are fetched automatically where the provider exposes an API; anything else can be filled in by hand.
10
+
11
+ ![The four whale states](assets/preview.svg)
12
+
13
+ | What the whale is doing | Meaning |
14
+ |---|---|
15
+ | Resting quietly | Idle |
16
+ | Blowing bubbles (animated) | A task is running |
17
+ | A `!` bubble | Something is waiting for you (approval / choice / input) |
18
+ | A `×` bubble | A task just failed (shown for 10 minutes by default) |
19
+
20
+ On macOS the whale is a menu-bar icon that follows the menu-bar colour, with a short label next to it such as `Pending 1` / `Running 2`. On Windows / Linux the state is colour-coded instead (🔵 idle / running, 🟠 waiting for you, 🔴 just failed). The Windows desktop app already ships its own tray icon, so the plugin doesn't add a second one there — it only provides notifications.
21
+
22
+ ## Installation
23
+
24
+ The plugin is published on npm as [`dsh-notify-tray`](https://www.npmjs.com/package/dsh-notify-tray). Installing from npm needs nothing beyond DSH itself — no Git required.
25
+
26
+ **Option 1 — from inside DSH (recommended)**
27
+ Open DSH **Plugins → Add plugin**, type `dsh-notify-tray` into the input box and click **Install**.
28
+
29
+ **Option 2 — command line**
30
+
31
+ ```sh
32
+ # Desktop app (the dsh command bundled with the app)
33
+ dsh plugin --profile desktop add dsh-notify-tray
34
+ # Or the web / CLI profile
35
+ dsh plugin --profile web add dsh-notify-tray
36
+ # Pin a specific version
37
+ dsh plugin --profile desktop add dsh-notify-tray@0.1.0
38
+ ```
39
+
40
+ **Option 3 — from the Git repository** (latest unreleased code)
41
+
42
+ Paste `https://github.com/ROYLeXJ/dsh-notify-tray.git` into **Plugins → Add plugin**, or run `dsh plugin --profile desktop add https://github.com/ROYLeXJ/dsh-notify-tray.git`. You can also clone the repo and install from a local directory: `dsh plugin --profile desktop add /path/to/dsh-notify-tray`.
43
+
44
+ This route **requires Git**; without it you'll see `ERR_PNPM_GIT_RESOLVE_FAILED … 'git' is not recognized as an internal or external command`. On Windows install Git from <https://git-scm.com/download/win> (the defaults are fine) and restart DSH; on macOS run `git --version` once in Terminal and follow the prompt to install the command-line tools.
45
+
46
+ Restart DSH after installing. To update: `dsh plugin --profile desktop update dsh-notify-tray`. To uninstall: `dsh plugin --profile desktop remove dsh-notify-tray`.
47
+
48
+ ## Configuration
49
+
50
+ Every option can be changed under DSH **Plugins → Whale Notify Station → Configure**, or written into the profile's `cordis.patch.yml`:
51
+
52
+ ```yaml
53
+ - id: notify-tray
54
+ config:
55
+ channel: auto # auto | client | system | both | off
56
+ notifyInput: true
57
+ spendSource: auto # auto | balance | estimate
58
+ planProvider: kimi # provider of your plan, see the table below
59
+ planId: cn-pro # preset plan ID; leave empty for "manual"
60
+ planApiKeyEnv: KIMI_CODE_TOKEN
61
+ prices: # optional: price per million tokens, keyed by "provider/model", model ID or prefix
62
+ deepseek-flash: { input: 2, cacheRead: 0.04, output: 8 }
63
+ ```
64
+
65
+ ### Notifications & status bar
66
+
67
+ | Option | Default | Description |
68
+ |---|---|---|
69
+ | `notifyCompleted` / `notifyFailed` / `notifyApproval` / `notifyChoice` / `notifyInput` | all on | Toggle each of the five notification kinds |
70
+ | `channel` | `auto` | `auto`: the DSH window shows the notification when one is open, otherwise the OS does |
71
+ | `suppressWhenFocused` | `true` | Don't notify while you're looking at that session (switch to another app and you will be) |
72
+ | `tray` | `true` | Show the status-bar icon (takes effect after a restart) |
73
+ | `titleStyle` | `text` | What to show next to the whale in the macOS menu bar: `text` (Pending 1 / Running 2), `count` (numbers only), `icon` (whale only) |
74
+ | `showSpendInTitle` | `false` | Append today's spend to the macOS menu-bar label |
75
+ | `errorHoldMinutes` | `10` | How many minutes the icon stays red after a task fails |
76
+
77
+ ### Spend & pricing
78
+
79
+ | Option | Default | Description |
80
+ |---|---|---|
81
+ | `spendSource` | `auto` | Use the DeepSeek account balance when available, otherwise estimate from prices |
82
+ | `currency` | `CNY` | Display currency |
83
+ | `balancePollMinutes` | `5` | How often (minutes) to refresh the DeepSeek account balance |
84
+ | `priceSource` | `auto` | Which price to estimate with: `auto` (price table → custom price → DSH's built-in model catalog → plugin's built-in prices), `catalog`, `custom` |
85
+ | `customInput` / `customCacheRead` / `customOutput` | empty | Custom price per million tokens; leave empty to disable |
86
+ | `customPriceCurrency` | `CNY` | Currency of the custom price |
87
+ | `exchangeRate` | `7.2` | 1 USD = ? CNY; catalog prices are in USD and converted when displaying CNY |
88
+ | `prices` | `{}` | Per-model price table, highest priority |
89
+
90
+ The **Current API price** section shows the model you're using (provider / model) and its catalog price. DSH's pi-ai adapter ships a model catalog (Claude, GPT, Gemini, Grok, Groq, Kimi, GLM, MiMo, MiniMax, OpenCode Go, DeepSeek, …); the plugin reads it directly and falls back to its built-in prices or your custom price when a model is missing. A one-click **Copy catalog price into custom price** lets you tweak from the catalog value.
91
+
92
+ ### Plans
93
+
94
+ | Option | Default | Description |
95
+ |---|---|---|
96
+ | `planProvider` | `none` | `none` / `custom` / `deepseek` / `kimi` / `glm` / `opencode` / `mimo` / `gemini` / `grok` / `claude` / `gpt` / `minimax` |
97
+ | `planId` | empty | Preset plan ID (pick it from the drop-down on the settings page; name, price, period, currency and quota description are filled in automatically) |
98
+ | `planName` / `planPrice` / `planPeriod` / `planCurrency` / `planQuota` | preset | Each can be overridden; leave empty to use the preset |
99
+ | `planBalance` | empty | Plan / prepaid balance (manual; leave empty when it can be fetched) |
100
+ | `planRemaining` | empty | Remaining quota, e.g. `68%` or `1200 requests` (a manual value takes precedence over the API) |
101
+ | `planResetAt` | empty | Next reset time (manual) |
102
+ | `planApiKeyEnv` | empty | API key reference used for quota lookups — an environment-variable name, the same one DSH's **Models** page uses when it saves a key. The plugin only reads it when querying quota |
103
+ | `planQuotaUrl` | preset | Quota endpoint; leave empty for the preset |
104
+ | `planAutoQuota` | `true` | Poll the endpoint automatically when there is one |
105
+ | `quotaPollMinutes` | `10` | Quota refresh interval (minutes) |
106
+
107
+ Built-in plan presets (prices are the list prices at the time of writing and may change — all editable):
108
+
109
+ | Provider | Presets | Fetched automatically |
110
+ |---|---|---|
111
+ | DeepSeek | Pay-as-you-go | Account balance `GET /user/balance` |
112
+ | Kimi | Membership Go / Plus / Pro / Max / Ultra (CN ¥49–1399, intl. $19–199), legacy Kimi Code, Moonshot pay-as-you-go | Kimi Code quota `GET api.kimi.com/coding/v1/usages` (needs the Kimi Code OAuth token, not a Moonshot API key); Moonshot balance |
113
+ | GLM | Coding Plan Lite / Pro / Max (CN ¥118 / 538 / 1078, Z.ai $18 / 80 / 168) | 5-hour and weekly credits `GET /api/monitor/usage/quota/limit` |
114
+ | OpenCode | Go $10, Go Plus $40, Zen pay-as-you-go | No public endpoint |
115
+ | MiMo | Token Plan Lite / Standard / Pro / Max (¥39 / 99 / 329 / 659) | No API-key endpoint (console cookie only) |
116
+ | Gemini | Google AI Plus / Pro / Ultra, API pay-as-you-go | None |
117
+ | Grok | SuperGrok Lite / Standard / Plus / Heavy, API pay-as-you-go | Key validation only (`GET /v1/api-key`), no balance |
118
+ | Claude | Pro / Max 5x / Max 20x / Team, API pay-as-you-go | No endpoint for regular keys |
119
+ | GPT | ChatGPT Go / Plus / Pro 5x / Pro 20x / Business, API pay-as-you-go | No endpoint for regular keys |
120
+ | MiniMax | M Plan Go / Explore / Build (CN ¥49 / 119 / 469, intl. $22 / 55 / 132), legacy Coding Plan | 5-hour window remaining `GET /v1/coding_plan/remains` |
121
+
122
+ Anything that can't be fetched can be typed in by hand; empty fields are simply hidden. The status-bar menu gains a **Plan · name · price · remaining** row whose submenu shows balance, remaining quota, reset time, the raw details returned by the endpoint, a **Refresh quota** action and links to the usage / pricing pages.
123
+
124
+ ## Notes
125
+
126
+ - **When notifications fire** — a task that runs to completion counts as *completed*; an error, hitting the token limit or being blocked counts as *failed*; stopping a task yourself doesn't notify. For `ask_user_question`, questions with options and plan approvals count as *needs a choice*, questions without options as *needs input*; tool approvals count as *needs approval*. Only top-level sessions are tracked — sub-agents don't get their own notifications.
127
+ - **How spend is computed** — when signed in with a DeepSeek account, today's spend is accumulated from drops in the account balance (top-ups don't count as negative spend; subject to the platform's billing delay). In parallel, it's estimated from each `provider/model` route's tokens × unit price (price sources described under `priceSource` above); the **Today's usage** submenu breaks it down per model. The ledger lives in `~/.dsh/storages/dsh-notify-tray/ledger.json` and keeps 31 days.
128
+ - **Clicking the tray icon / a notification / "Open DeepSeek Harness" in the menu** — brings up the desktop app when there is one, otherwise (`dsh web`) opens the web UI in the browser. Browser notifications require granting notification permission first.
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ npm install
134
+ npm test # node --test
135
+ node scripts/demo.mjs # walk a demo-only whale through every state (macOS menu bar)
136
+ node scripts/demo.mjs --live # drive the real whale (DSH must be idle); restored afterwards
137
+ node scripts/demo.mjs --all # also show "needs approval" / "failed"
138
+ ```
139
+
140
+ Layout:
141
+
142
+ | Path | Purpose |
143
+ |---|---|
144
+ | `lib/index.js` | Host half: session tracking, spend ledger, plan / quota polling, tray driver, OS-notification fallback, `GET notify-tray/state` API |
145
+ | `lib/client.js` | Browser half: native notifications from the DSH window and the settings page |
146
+ | `lib/tray/` | Status-bar drivers (macOS helper via `assets/tray-darwin.js`, Linux StatusNotifierItem over D-Bus) |
147
+ | `lib/catalog.js` / `lib/pricing.js` / `lib/quota.js` | Plan presets, price resolution, provider quota endpoints |
148
+ | `lib/voice.js` / `lib/whale.js` | Notification copy in the whale's voice; icon rendering |
149
+ | `locale/` | Plugin title / description in `en` and `zh` |
150
+ | `reference/` | DSH type declarations and example bundles used while developing |
151
+
152
+ Requires Node ≥ 20. Linux uses [`dbus-next`](https://www.npmjs.com/package/dbus-next) for the tray.
153
+
154
+ ## Known limitations
155
+
156
+ - Verified on macOS and Windows only; Linux has not been fully verified yet.
157
+
158
+ ## License
159
+
160
+ MIT © ROYLeXJ
@@ -0,0 +1,132 @@
1
+ # dsh-notify-tray
2
+
3
+ [English](README.md) | **简体中文**
4
+
5
+ DeepSeek Harness(DSH)的通知 + 状态栏插件,支持 macOS、Windows、Linux。
6
+
7
+ - **系统通知**:任务完成、任务失败、需要批准、需要你选择、需要你输入时弹系统通知,文案是小鲸鱼的口吻(比如「🐳 搞定啦~『写周报』做完了!快来验收」)。点击通知会跳到对应会话。
8
+ - **状态栏小鲸鱼**:状态栏常驻一只小鲸鱼,用姿态告诉你现在的状态;下拉菜单里是正在运行的任务、等你处理的事项、今日用量和最近结束的任务,点一下就能跳到对应会话。
9
+ - **插件设置页**:所有选项都能在 DSH「插件」页里直接改,包括当前模型的 API 价格和你买的套餐(Kimi / GLM / OpenCode / MiMo / Gemini / Grok / Claude / GPT / MiniMax / DeepSeek 或自定义),能从接口拿到的余额 / 剩余额度自动拿,拿不到的手填。
10
+
11
+ ![四种状态](assets/preview.svg)
12
+
13
+ | 鲸鱼的样子 | 含义 |
14
+ |---|---|
15
+ | 安静地待着 | 空闲 |
16
+ | 鼻子前冒泡泡(会动) | 有任务在运行 |
17
+ | 鼻子前冒出 `!` | 有事等你处理(批准 / 选择 / 输入) |
18
+ | 鼻子前冒出 `×` | 刚有任务失败(默认保持 10 分钟) |
19
+
20
+ macOS 上鲸鱼是菜单栏图标,颜色自动跟随菜单栏,旁边显示 `待处理 1` / `运行中 2` 这样的文字;Windows / Linux 上用颜色区分状态(🔵 空闲/运行中、🟠 等你处理、🔴 刚失败)。Windows 桌面版自带托盘图标,插件不再额外加一个,只提供通知。
21
+
22
+ ## 安装
23
+
24
+ 插件已发布到 npm:[`dsh-notify-tray`](https://www.npmjs.com/package/dsh-notify-tray)。从 npm 安装只需要 DSH 本身,**不需要装 Git**。
25
+
26
+ **方式一:在 DSH 里装(推荐)**
27
+ 打开 DSH「插件 → 添加插件」,输入框里填 `dsh-notify-tray`,点「安装」。
28
+
29
+ **方式二:命令行**
30
+
31
+ ```sh
32
+ # 桌面版(App 内置的 dsh 命令)
33
+ dsh plugin --profile desktop add dsh-notify-tray
34
+ # 或者 Web / 命令行 profile
35
+ dsh plugin --profile web add dsh-notify-tray
36
+ # 指定版本
37
+ dsh plugin --profile desktop add dsh-notify-tray@0.1.0
38
+ ```
39
+
40
+ **方式三:从 Git 仓库装**(想用还没发版的最新代码时)
41
+
42
+ 在「插件 → 添加插件」里填 `https://github.com/ROYLeXJ/dsh-notify-tray.git`,或运行 `dsh plugin --profile desktop add https://github.com/ROYLeXJ/dsh-notify-tray.git`;也可以先克隆仓库再从本地目录安装:`dsh plugin --profile desktop add /path/to/dsh-notify-tray`。
43
+
44
+ 这条路**需要本机装有 Git**,没有 Git 会报 `ERR_PNPM_GIT_RESOLVE_FAILED … 'git' 不是内部或外部命令`。Windows 先到 <https://git-scm.com/download/win> 下载安装(一路默认即可),装完重启 DSH;macOS 在终端里运行一次 `git --version`,按提示安装命令行工具即可。
45
+
46
+ 装好后重启 DSH。更新:`dsh plugin --profile desktop update dsh-notify-tray`。卸载:`dsh plugin --profile desktop remove dsh-notify-tray`。
47
+
48
+ ## 设置
49
+
50
+ 所有选项都可以在 DSH「插件 → 小鲸鱼通知站 → 配置」里修改,也可以写在 profile 的 `cordis.patch.yml` 里:
51
+
52
+ ```yaml
53
+ - id: notify-tray
54
+ config:
55
+ channel: auto # auto | client | system | both | off
56
+ notifyInput: true
57
+ spendSource: auto # auto | balance | estimate
58
+ planProvider: kimi # 套餐所属平台,见下表
59
+ planId: cn-pro # 预设套餐 ID;选「手填」留空
60
+ planApiKeyEnv: KIMI_CODE_TOKEN
61
+ prices: # 可选:每百万 token 价格,键是「provider/model」、模型 ID 或前缀
62
+ deepseek-flash: { input: 2, cacheRead: 0.04, output: 8 }
63
+ ```
64
+
65
+ ### 通知与状态栏
66
+
67
+ | 选项 | 默认值 | 说明 |
68
+ |---|---|---|
69
+ | `notifyCompleted` / `notifyFailed` / `notifyApproval` / `notifyChoice` / `notifyInput` | 全部开启 | 分别控制五类通知 |
70
+ | `channel` | `auto` | `auto`:有 DSH 窗口时由窗口弹,没有窗口时由系统弹 |
71
+ | `suppressWhenFocused` | `true` | 你正在看这个会话时不弹通知(切到别的应用去就有了) |
72
+ | `tray` | `true` | 是否显示状态栏图标(修改后重启生效) |
73
+ | `titleStyle` | `text` | macOS 菜单栏鲸鱼旁边显示什么:`text`(待处理 1 / 运行中 2)、`count`(只有数字)、`icon`(只有鲸鱼) |
74
+ | `showSpendInTitle` | `false` | macOS 菜单栏文字里附带今日花费 |
75
+ | `errorHoldMinutes` | `10` | 任务失败后图标保持红色的分钟数 |
76
+
77
+ ### 花费与价格
78
+
79
+ | 选项 | 默认值 | 说明 |
80
+ |---|---|---|
81
+ | `spendSource` | `auto` | 有 DeepSeek 账户余额时用余额扣费,否则按价格估算 |
82
+ | `currency` | `CNY` | 显示币种 |
83
+ | `balancePollMinutes` | `5` | DeepSeek 账户余额刷新间隔(分钟) |
84
+ | `priceSource` | `auto` | 估算用哪个价:`auto`(价格表 → 自定义价 → DSH 自带的模型目录价 → 插件内置价)、`catalog`、`custom` |
85
+ | `customInput` / `customCacheRead` / `customOutput` | 空 | 自定义价,每百万 token;留空不用 |
86
+ | `customPriceCurrency` | `CNY` | 自定义价的币种 |
87
+ | `exchangeRate` | `7.2` | 1 美元 = ? 人民币;目录价是美元,显示人民币时按它换算 |
88
+ | `prices` | `{}` | 按模型的价格表,优先级最高 |
89
+
90
+ 「当前 API 价格」区域会显示你正在用的模型(provider / model)和它的目录价:DSH 的 pi-ai 适配器自带一份模型目录(Claude、GPT、Gemini、Grok、Groq、Kimi、GLM、MiMo、MiniMax、OpenCode Go、DeepSeek 等),插件直接读它,拿不到的再用内置价或你填的自定义价。一键「把目录价填进自定义价」可以在目录价的基础上改。
91
+
92
+ ### 套餐
93
+
94
+ | 选项 | 默认值 | 说明 |
95
+ |---|---|---|
96
+ | `planProvider` | `none` | `none` / `custom` / `deepseek` / `kimi` / `glm` / `opencode` / `mimo` / `gemini` / `grok` / `claude` / `gpt` / `minimax` |
97
+ | `planId` | 空 | 预设套餐 ID(在设置页的下拉框里选,会自动填好名称、价格、周期、币种、额度说明) |
98
+ | `planName` / `planPrice` / `planPeriod` / `planCurrency` / `planQuota` | 预设 | 每一项都可以改;留空用预设 |
99
+ | `planBalance` | 空 | 套餐余额 / 预付费余额(手填;能从接口拿到时留空) |
100
+ | `planRemaining` | 空 | 剩余额度,例如「68%」「1200 次」(手填优先于接口) |
101
+ | `planResetAt` | 空 | 下次重置时间(手填) |
102
+ | `planApiKeyEnv` | 空 | 查询额度用的 API Key 引用(环境变量名,和 DSH「模型」页保存密钥时用的名字一样;插件只在查询额度时读取) |
103
+ | `planQuotaUrl` | 预设 | 额度查询地址,留空用预设 |
104
+ | `planAutoQuota` | `true` | 有接口时定时自动获取 |
105
+ | `quotaPollMinutes` | `10` | 额度刷新间隔(分钟) |
106
+
107
+ 内置套餐预设(价格是写入时的官网标价,随时可能变,都能改):
108
+
109
+ | 平台 | 预设 | 能自动获取的 |
110
+ |---|---|---|
111
+ | DeepSeek | 按量付费 | 账户余额 `GET /user/balance` |
112
+ | Kimi | 会员 Go / Plus / Pro / Max / Ultra(国内 ¥49–1399、国际 $19–199)、旧版 Kimi Code、Moonshot 按量 | Kimi Code 额度 `GET api.kimi.com/coding/v1/usages`(要 Kimi Code 的 OAuth token,不是 Moonshot API Key);Moonshot 余额 |
113
+ | GLM | Coding Plan Lite / Pro / Max(国内 ¥118 / 538 / 1078,Z.ai $18 / 80 / 168) | 5 小时与每周积分 `GET /api/monitor/usage/quota/limit` |
114
+ | OpenCode | Go $10、Go Plus $40、Zen 按量 | 无公开接口 |
115
+ | MiMo | Token Plan Lite / Standard / Pro / Max(¥39 / 99 / 329 / 659) | 无 API Key 可用的接口(只认控制台 Cookie) |
116
+ | Gemini | Google AI Plus / Pro / Ultra、API 按量 | 无 |
117
+ | Grok | SuperGrok Lite / 标准 / Plus / Heavy、API 按量 | 只能验证 Key 有效(`GET /v1/api-key`),无余额 |
118
+ | Claude | Pro / Max 5x / Max 20x / Team、API 按量 | 普通 Key 无接口 |
119
+ | GPT | ChatGPT Go / Plus / Pro 5x / Pro 20x / Business、API 按量 | 普通 Key 无接口 |
120
+ | MiniMax | M Plan Go / Explore / Build(国内 ¥49 / 119 / 469,国际 $22 / 55 / 132)、旧版 Coding Plan | 5 小时窗口剩余 `GET /v1/coding_plan/remains` |
121
+
122
+ 拿不到的信息就手填;留空也可以,留空的项不显示。状态栏菜单里会多一行「套餐 · 名称 · 价格 · 剩余」,子菜单里有余额、剩余额度、重置时间、接口返回的明细、「刷新额度」和用量页 / 价格页链接。
123
+
124
+ ## 说明
125
+
126
+ - **通知的触发时机**:任务跑完算「完成」;出错、超出 Token 上限、被拦截算「失败」;你自己点停止不弹通知。调用 `ask_user_question` 时,带选项的问题和计划审批算「需要选择」,不带选项的算「需要输入」;工具审批算「需要批准」。只跟踪顶层会话,子 agent 不单独弹通知。
127
+ - **花费怎么算**:用 DeepSeek 账户登录时,按账户余额的下降累计当天花费(充值不算负花费,会受平台扣费延迟影响);同时按当天各「provider/model」的 Token 乘以单价估算(单价来源见上面的 `priceSource`),「今日用量」子菜单里按模型分行显示。账本存在 `~/.dsh/storages/dsh-notify-tray/ledger.json`,保留 31 天。
128
+ - **点击托盘图标 / 通知 / 菜单里的「打开 DeepSeek Harness」**:有桌面版时唤起桌面版,没有时(`dsh web`)在浏览器里打开 Web 界面。浏览器里的通知需要先允许通知权限。
129
+
130
+ ## 已知限制
131
+
132
+ - 只在 macOS 和 Windows 上完成了验证,Linux 上还没有完成验证。
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="600" height="124" viewBox="0 0 600 124"><rect width="100%" height="100%" rx="12" fill="#f5f6fa"/><g transform="translate(10 26.25)"><path d="M22 5.14 C24.83 4.47 27.5 4.29 31 4.04 C34.5 3.8 40 4.1 43 3.67 C46 3.25 49 1.47 49 1.47 C49 1.47 53 3.31 53 3.31 C53 3.31 51.5 4.96 51 5.88 C50.5 6.8 49.83 7.84 50 8.82 C50.17 9.8 51 10.78 52 11.76 C53 12.74 54.5 13.47 56 14.7 C57.5 15.92 59.5 17.64 61 19.11 C62.5 20.58 63.5 22.17 65 23.52 C66.5 24.87 68 26.4 70 27.2 C72 27.99 74.67 27.81 77 28.3 C79.33 28.79 84 30.13 84 30.13 C84 30.13 84.33 32.71 84 34.55 C83.67 36.38 82.83 39.08 82 41.16 C81.17 43.24 80.17 45.08 79 47.04 C77.83 49 76.17 51.08 75 52.92 C73.83 54.76 72.33 56.59 72 58.06 C71.67 59.53 72 60.76 73 61.74 C74 62.72 77 63.15 78 63.95 C79 64.74 79.5 65.78 79 66.52 C78.5 67.25 77.17 68.05 75 68.36 C72.83 68.66 66 68.36 66 68.36 C66 68.36 63.33 66.89 62 65.78 C60.67 64.68 59.42 63.15 58 61.74 C56.58 60.33 55 58.92 53.5 57.33 C52 55.74 50.42 53.9 49 52.19 C47.58 50.47 46.33 48.76 45 47.04 C43.67 45.32 42.5 43.61 41 41.89 C39.5 40.18 38 38.46 36 36.75 C34 35.04 31.83 33.2 29 31.61 C26.17 30.01 22.42 28.24 19 27.2 C15.58 26.15 8.5 25.36 8.5 25.36 C8.5 25.36 6.75 27.99 6.5 29.4 C6.25 30.81 6.75 32.34 7 33.81 C7.25 35.28 7.67 36.75 8 38.22 C8.33 39.69 8.5 41.16 9 42.63 C9.5 44.1 10.17 45.57 11 47.04 C11.83 48.51 12.83 49.98 14 51.45 C15.17 52.92 16.5 54.39 18 55.86 C19.5 57.33 21.33 59.11 23 60.27 C24.67 61.43 26.5 62.17 28 62.84 C29.5 63.52 32 64.31 32 64.31 C32 64.31 32.83 61.13 33 59.53 C33.17 57.94 33 54.76 33 54.76 C33 54.76 39 54.76 39 54.76 C39 54.76 42.5 57.45 44 58.8 C45.5 60.15 46.83 61.74 48 62.84 C49.17 63.95 51 65.41 51 65.41 C51 65.41 54.33 66.33 56 66.89 C57.67 67.44 61 68.72 61 68.72 C61 68.72 58 69.95 56 70.56 C54 71.17 51.67 71.97 49 72.4 C46.33 72.83 43.5 73.32 40 73.13 C36.5 72.95 31.67 72.21 28 71.3 C24.33 70.38 20.83 69.21 18 67.62 C15.17 66.03 13.17 64.07 11 61.74 C8.83 59.41 6.58 56.59 5 53.66 C3.42 50.72 2.25 47.41 1.5 44.1 C0.75 40.79 0.42 37.24 0.5 33.81 C0.58 30.38 0.92 26.83 2 23.52 C3.08 20.21 5 16.54 7 13.96 C9 11.39 11.5 9.55 14 8.08 C16.5 6.61 19.17 5.82 22 5.14 Z M52 28.66 C52 28.66 52.83 31.61 53.5 33.08 C54.17 34.55 55.17 36.02 56 37.48 C56.83 38.95 57.67 40.73 58.5 41.89 C59.33 43.06 60 44.16 61 44.47 C62 44.77 63.5 44.41 64.5 43.73 C65.5 43.06 66.75 41.59 67 40.42 C67.25 39.26 66.83 37.98 66 36.75 C65.17 35.52 63.5 34.12 62 33.08 C60.5 32.03 58.67 31.24 57 30.5 C55.33 29.77 52 28.66 52 28.66 Z M55.2 32.71 C56.42 32.71 57.4 33.7 57.4 34.91 C57.4 36.13 56.42 37.11 55.2 37.11 C53.98 37.11 53 36.13 53 34.91 C53 33.7 53.98 32.71 55.2 32.71 Z M70 0.73 C70 0.73 72 2.57 73 3.68 C74 4.78 75 6.25 76 7.35 C77 8.45 77.92 9.43 79 10.29 C80.08 11.15 82.5 12.5 82.5 12.5 C82.5 12.5 85.25 11.02 87 10.29 C88.75 9.55 90.83 8.57 93 8.08 C95.17 7.59 100 7.35 100 7.35 C100 7.35 99.5 12.13 99 13.96 C98.5 15.8 98 17.03 97 18.38 C96 19.72 94.67 20.95 93 22.05 C91.33 23.15 88.5 24.01 87 24.99 C85.5 25.97 85.17 26.58 84 27.93 C82.83 29.28 81.67 32.22 80 33.08 C78.33 33.93 75 33.93 74 33.08 C73 32.22 74 29.64 74 27.93 C74 26.21 74.5 24.25 74 22.79 C73.5 21.32 72 20.46 71 19.11 C70 17.76 68.67 16.41 68 14.7 C67.33 12.98 67 10.66 67 8.82 C67 6.98 67.5 5.02 68 3.68 C68.5 2.33 70 0.73 70 0.73 Z" fill="rgb(59,83,170)"/></g><text x="60" y="112" text-anchor="middle" font-family="-apple-system, PingFang SC, Microsoft YaHei, sans-serif" font-size="12" fill="#555">空闲</text><g transform="translate(130 26.25)"><path d="M22 4.54 C24.83 3.87 27.5 3.68 31 3.44 C34.5 3.19 40 3.5 43 3.07 C46 2.64 49 0.86 49 0.86 C49 0.86 53 2.7 53 2.7 C53 2.7 51.5 4.36 51 5.27 C50.5 6.19 49.83 7.23 50 8.21 C50.17 9.19 51 10.17 52 11.15 C53 12.13 54.5 12.87 56 14.09 C57.5 15.32 59.5 17.03 61 18.5 C62.5 19.97 63.5 21.57 65 22.91 C66.5 24.26 68 25.79 70 26.59 C72 27.39 74.67 27.2 77 27.69 C79.33 28.18 84 29.53 84 29.53 C84 29.53 84.33 32.1 84 33.94 C83.67 35.78 82.83 38.47 82 40.55 C81.17 42.64 80.17 44.47 79 46.43 C77.83 48.39 76.17 50.48 75 52.31 C73.83 54.15 72.33 55.99 72 57.46 C71.67 58.93 72 60.15 73 61.13 C74 62.11 77 62.54 78 63.34 C79 64.14 79.5 65.18 79 65.91 C78.5 66.65 77.17 67.44 75 67.75 C72.83 68.06 66 67.75 66 67.75 C66 67.75 63.33 66.28 62 65.18 C60.67 64.07 59.42 62.54 58 61.13 C56.58 59.73 55 58.32 53.5 56.72 C52 55.13 50.42 53.29 49 51.58 C47.58 49.86 46.33 48.15 45 46.43 C43.67 44.72 42.5 43 41 41.29 C39.5 39.57 38 37.86 36 36.14 C34 34.43 31.83 32.59 29 31 C26.17 29.41 22.42 27.63 19 26.59 C15.58 25.55 8.5 24.75 8.5 24.75 C8.5 24.75 6.75 27.39 6.5 28.79 C6.25 30.2 6.75 31.73 7 33.2 C7.25 34.67 7.67 36.14 8 37.61 C8.33 39.08 8.5 40.55 9 42.02 C9.5 43.49 10.17 44.96 11 46.43 C11.83 47.9 12.83 49.37 14 50.84 C15.17 52.31 16.5 53.78 18 55.25 C19.5 56.72 21.33 58.5 23 59.66 C24.67 60.83 26.5 61.56 28 62.24 C29.5 62.91 32 63.71 32 63.71 C32 63.71 32.83 60.52 33 58.93 C33.17 57.34 33 54.15 33 54.15 C33 54.15 39 54.15 39 54.15 C39 54.15 42.5 56.85 44 58.19 C45.5 59.54 46.83 61.13 48 62.24 C49.17 63.34 51 64.81 51 64.81 C51 64.81 54.33 65.73 56 66.28 C57.67 66.83 61 68.12 61 68.12 C61 68.12 58 69.34 56 69.95 C54 70.57 51.67 71.36 49 71.79 C46.33 72.22 43.5 72.71 40 72.53 C36.5 72.34 31.67 71.61 28 70.69 C24.33 69.77 20.83 68.61 18 67.01 C15.17 65.42 13.17 63.46 11 61.13 C8.83 58.81 6.58 55.99 5 53.05 C3.42 50.11 2.25 46.8 1.5 43.49 C0.75 40.19 0.42 36.63 0.5 33.2 C0.58 29.77 0.92 26.22 2 22.91 C3.08 19.61 5 15.93 7 13.36 C9 10.79 11.5 8.95 14 7.48 C16.5 6.01 19.17 5.21 22 4.54 Z M52 28.06 C52 28.06 52.83 31 53.5 32.47 C54.17 33.94 55.17 35.41 56 36.88 C56.83 38.35 57.67 40.13 58.5 41.29 C59.33 42.45 60 43.56 61 43.86 C62 44.17 63.5 43.8 64.5 43.13 C65.5 42.45 66.75 40.98 67 39.82 C67.25 38.66 66.83 37.37 66 36.14 C65.17 34.92 63.5 33.51 62 32.47 C60.5 31.43 58.67 30.63 57 29.9 C55.33 29.16 52 28.06 52 28.06 Z M55.2 32.11 C56.42 32.11 57.4 33.09 57.4 34.31 C57.4 35.52 56.42 36.51 55.2 36.51 C53.98 36.51 53 35.52 53 34.31 C53 33.09 53.98 32.11 55.2 32.11 Z M74.14 -0.41 C74.14 -0.41 75.87 1.69 76.72 2.91 C77.56 4.14 78.35 5.73 79.19 6.96 C80.03 8.19 80.81 9.28 81.76 10.28 C82.72 11.28 84.93 12.94 84.93 12.94 C84.93 12.94 87.86 11.86 89.69 11.37 C91.52 10.87 93.72 10.19 95.93 9.99 C98.15 9.8 102.97 10.22 102.97 10.22 C102.97 10.22 101.82 14.88 101.08 16.63 C100.34 18.39 99.67 19.53 98.5 20.73 C97.33 21.93 95.84 22.96 94.04 23.83 C92.24 24.7 89.31 25.16 87.7 25.93 C86.08 26.7 85.66 27.26 84.32 28.44 C82.99 29.61 81.43 32.37 79.66 32.99 C77.9 33.61 74.59 33.16 73.72 32.18 C72.84 31.19 74.18 28.78 74.42 27.08 C74.65 25.38 75.41 23.51 75.11 21.98 C74.82 20.46 73.45 19.41 72.64 17.93 C71.83 16.46 70.69 14.95 70.27 13.16 C69.84 11.37 69.82 9.02 70.07 7.2 C70.32 5.38 71.08 3.5 71.76 2.24 C72.44 0.97 74.14 -0.41 74.14 -0.41 Z M91.04 41.7 C92.09 41.7 92.95 42.56 92.95 43.61 C92.95 44.66 92.09 45.52 91.04 45.52 C89.99 45.52 89.13 44.66 89.13 43.61 C89.13 42.56 89.99 41.7 91.04 41.7 Z M94.31 32.67 C95.48 32.67 96.43 33.62 96.43 34.79 C96.43 35.96 95.48 36.91 94.31 36.91 C93.14 36.91 92.19 35.96 92.19 34.79 C92.19 33.62 93.14 32.67 94.31 32.67 Z M91.25 40.36 C92.23 40.36 93.03 41.16 93.03 42.14 C93.03 43.12 92.23 43.92 91.25 43.92 C90.27 43.92 89.47 43.12 89.47 42.14 C89.47 41.16 90.27 40.36 91.25 40.36 Z" fill="rgb(77,107,254)"/></g><text x="180" y="112" text-anchor="middle" font-family="-apple-system, PingFang SC, Microsoft YaHei, sans-serif" font-size="12" fill="#555">运行中</text><g transform="translate(250 26.25)"><path d="M23.03 4.36 C25.89 3.77 28.56 3.67 32.06 3.53 C35.57 3.4 41.06 3.87 44.07 3.54 C47.08 3.2 50.13 1.52 50.13 1.52 C50.13 1.52 54.07 3.48 54.07 3.48 C54.07 3.48 52.52 5.09 52 5.99 C51.47 6.89 50.77 7.91 50.91 8.9 C51.04 9.88 51.85 10.89 52.81 11.9 C53.78 12.91 55.26 13.69 56.72 14.96 C58.18 16.23 60.13 18 61.58 19.52 C63.04 21.04 63.99 22.66 65.45 24.05 C66.9 25.44 68.36 27.02 70.33 27.88 C72.3 28.74 74.98 28.64 77.29 29.2 C79.61 29.76 84.23 31.25 84.23 31.25 C84.23 31.25 84.49 33.83 84.1 35.66 C83.71 37.48 82.79 40.15 81.89 42.21 C81 44.26 79.94 46.07 78.71 47.99 C77.49 49.92 75.76 51.95 74.53 53.75 C73.31 55.55 71.75 57.34 71.38 58.8 C71 60.25 71.29 61.49 72.26 62.5 C73.23 63.51 76.22 64.03 77.19 64.86 C78.17 65.68 78.63 66.74 78.11 67.46 C77.59 68.18 76.23 68.93 74.06 69.17 C71.88 69.41 65.06 68.9 65.06 68.9 C65.06 68.9 62.44 67.34 61.14 66.2 C59.84 65.06 58.64 63.49 57.27 62.04 C55.9 60.59 54.36 59.13 52.91 57.49 C51.46 55.85 49.93 53.97 48.57 52.21 C47.21 50.45 46.01 48.7 44.73 46.94 C43.45 45.19 42.34 43.44 40.89 41.68 C39.44 39.92 38 38.16 36.05 36.38 C34.1 34.6 32 32.7 29.21 31.02 C26.43 29.34 22.74 27.45 19.35 26.31 C15.97 25.16 8.92 24.15 8.92 24.15 C8.92 24.15 7.09 26.72 6.79 28.12 C6.5 29.52 6.95 31.07 7.16 32.55 C7.36 34.02 7.73 35.51 8.02 36.99 C8.31 38.47 8.43 39.94 8.88 41.42 C9.34 42.91 9.96 44.4 10.75 45.89 C11.53 47.39 12.49 48.89 13.61 50.39 C14.73 51.9 16.02 53.41 17.47 54.93 C18.92 56.44 20.7 58.27 22.33 59.49 C23.96 60.7 25.77 61.49 27.25 62.21 C28.73 62.93 31.2 63.81 31.2 63.81 C31.2 63.81 32.13 60.65 32.35 59.06 C32.57 57.48 32.5 54.29 32.5 54.29 C32.5 54.29 38.49 54.47 38.49 54.47 C38.49 54.47 41.91 57.27 43.37 58.67 C44.82 60.06 46.11 61.69 47.24 62.83 C48.37 63.97 50.16 65.49 50.16 65.49 C50.16 65.49 53.46 66.52 55.11 67.12 C56.76 67.72 60.05 69.11 60.05 69.11 C60.05 69.11 57.02 70.24 55 70.79 C52.98 71.34 50.62 72.07 47.95 72.41 C45.27 72.76 42.42 73.16 38.93 72.87 C35.43 72.58 30.63 71.69 26.99 70.66 C23.35 69.63 19.89 68.36 17.11 66.68 C14.32 65 12.39 62.98 10.29 60.59 C8.2 58.19 6.04 55.31 4.54 52.32 C3.05 49.33 1.99 45.99 1.34 42.66 C0.69 39.33 0.47 35.77 0.66 32.35 C0.85 28.92 1.29 25.38 2.48 22.11 C3.66 18.84 5.69 15.22 7.77 12.71 C9.85 10.2 12.4 8.44 14.95 7.05 C17.49 5.66 20.18 4.94 23.03 4.36 Z M52.29 28.79 C52.29 28.79 53.04 31.76 53.66 33.25 C54.28 34.74 55.23 36.24 56.02 37.73 C56.81 39.23 57.58 41.03 58.38 42.22 C59.18 43.41 59.81 44.53 60.8 44.87 C61.79 45.2 63.3 44.88 64.32 44.24 C65.34 43.6 66.64 42.17 66.92 41.01 C67.21 39.85 66.83 38.56 66.04 37.31 C65.24 36.06 63.62 34.6 62.15 33.51 C60.68 32.42 58.88 31.57 57.23 30.78 C55.59 30 52.29 28.79 52.29 28.79 Z M55.37 32.94 C56.58 32.97 57.54 33.99 57.5 35.2 C57.46 36.42 56.45 37.37 55.23 37.34 C54.02 37.3 53.06 36.28 53.1 35.07 C53.14 33.85 54.15 32.9 55.37 32.94 Z M72.27 1.26 C72.27 1.26 74.14 3.23 75.06 4.4 C75.99 5.57 76.89 7.1 77.81 8.27 C78.73 9.44 79.58 10.48 80.6 11.41 C81.63 12.34 83.94 13.85 83.94 13.85 C83.94 13.85 86.79 12.57 88.58 11.95 C90.38 11.34 92.52 10.5 94.72 10.16 C96.91 9.82 101.75 9.9 101.75 9.9 C101.75 9.9 100.93 14.63 100.31 16.43 C99.68 18.23 99.1 19.42 98.01 20.7 C96.92 21.97 95.51 23.1 93.77 24.09 C92.03 25.08 89.15 25.74 87.59 26.62 C86.02 27.49 85.65 28.08 84.39 29.35 C83.14 30.61 81.78 33.47 80.05 34.21 C78.33 34.95 75.01 34.72 74.07 33.8 C73.13 32.88 74.3 30.38 74.42 28.67 C74.53 26.96 75.17 25.04 74.77 23.54 C74.37 22.03 72.93 21.08 72.02 19.67 C71.12 18.25 69.88 16.82 69.33 15.06 C68.78 13.31 68.61 10.96 68.73 9.13 C68.85 7.29 69.49 5.37 70.08 4.06 C70.67 2.75 72.27 1.26 72.27 1.26 Z M93 31.69 L93 31.69 C94.66 31.69 96 33.03 96 34.69 L96 37.51 C96 39.17 94.66 40.51 93 40.51 L93 40.51 C91.34 40.51 90 39.17 90 37.51 L90 34.69 C90 33.03 91.34 31.69 93 31.69 Z M93 44.12 C94.66 44.12 96 45.47 96 47.12 C96 48.78 94.66 50.12 93 50.12 C91.34 50.12 90 48.78 90 47.12 C90 45.47 91.34 44.12 93 44.12 Z" fill="rgb(245,158,11)"/></g><text x="300" y="112" text-anchor="middle" font-family="-apple-system, PingFang SC, Microsoft YaHei, sans-serif" font-size="12" fill="#555">等你处理</text><g transform="translate(370 26.25)"><path d="M21.14 5.84 C23.96 5.09 26.62 4.84 30.11 4.5 C33.6 4.16 39.11 4.32 42.1 3.82 C45.09 3.31 48.04 1.46 48.04 1.46 C48.04 1.46 52.08 3.19 52.08 3.19 C52.08 3.19 50.63 4.88 50.15 5.81 C49.68 6.74 49.04 7.8 49.23 8.78 C49.42 9.75 50.28 10.71 51.31 11.66 C52.33 12.62 53.85 13.31 55.38 14.5 C56.91 15.68 58.96 17.35 60.5 18.78 C62.03 20.21 63.07 21.77 64.61 23.08 C66.14 24.39 67.68 25.88 69.7 26.62 C71.72 27.37 74.38 27.11 76.73 27.54 C79.08 27.97 83.78 29.2 83.78 29.2 C83.78 29.2 84.18 31.76 83.89 33.6 C83.61 35.45 82.84 38.17 82.07 40.27 C81.29 42.37 80.34 44.24 79.22 46.23 C78.11 48.22 76.49 50.34 75.38 52.21 C74.26 54.08 72.81 55.95 72.51 57.43 C72.22 58.91 72.58 60.12 73.61 61.08 C74.63 62.03 77.64 62.38 78.66 63.15 C79.68 63.92 80.21 64.95 79.73 65.7 C79.25 66.44 77.94 67.28 75.78 67.64 C73.62 68 66.78 67.87 66.78 67.87 C66.78 67.87 64.08 66.47 62.72 65.41 C61.35 64.34 60.07 62.84 58.61 61.47 C57.16 60.1 55.54 58.73 54 57.18 C52.46 55.63 50.83 53.83 49.37 52.15 C47.9 50.48 46.61 48.8 45.23 47.12 C43.85 45.44 42.64 43.75 41.1 42.08 C39.55 40.4 38.01 38.73 35.97 37.06 C33.92 35.4 31.71 33.62 28.83 32.1 C25.96 30.59 22.16 28.91 18.72 27.96 C15.28 27.01 8.18 26.4 8.18 26.4 C8.18 26.4 6.5 29.07 6.28 30.49 C6.07 31.9 6.61 33.42 6.9 34.88 C7.19 36.35 7.64 37.81 8.01 39.27 C8.39 40.73 8.59 42.19 9.13 43.65 C9.67 45.11 10.37 46.56 11.24 48.01 C12.12 49.45 13.15 50.9 14.36 52.34 C15.56 53.77 16.93 55.21 18.47 56.64 C20.01 58.07 21.89 59.8 23.59 60.92 C25.28 62.04 27.13 62.72 28.65 63.36 C30.17 63.99 32.69 64.72 32.69 64.72 C32.69 64.72 33.44 61.52 33.56 59.92 C33.69 58.32 33.44 55.14 33.44 55.14 C33.44 55.14 39.44 54.99 39.44 54.99 C39.44 54.99 43.01 57.59 44.54 58.9 C46.07 60.21 47.45 61.76 48.64 62.83 C49.84 63.91 51.71 65.33 51.71 65.33 C51.71 65.33 55.07 66.16 56.75 66.67 C58.43 67.17 61.79 68.37 61.79 68.37 C61.79 68.37 58.83 69.67 56.84 70.34 C54.86 71 52.55 71.86 49.89 72.36 C47.24 72.86 44.42 73.42 40.92 73.33 C37.41 73.24 32.56 72.63 28.87 71.81 C25.18 70.98 21.65 69.91 18.78 68.4 C15.91 66.88 13.86 64.97 11.63 62.7 C9.4 60.43 7.08 57.67 5.42 54.78 C3.76 51.88 2.51 48.6 1.67 45.32 C0.83 42.03 0.41 38.49 0.4 35.05 C0.39 31.62 0.63 28.06 1.63 24.73 C2.63 21.39 4.45 17.67 6.38 15.05 C8.31 12.42 10.76 10.52 13.22 8.99 C15.68 7.45 18.33 6.58 21.14 5.84 Z M51.75 28.56 C51.75 28.56 52.66 31.48 53.36 32.93 C54.07 34.38 55.11 35.83 55.98 37.28 C56.85 38.72 57.73 40.48 58.59 41.62 C59.46 42.76 60.15 43.85 61.16 44.13 C62.17 44.41 63.66 44 64.64 43.3 C65.62 42.6 66.83 41.1 67.05 39.93 C67.27 38.76 66.82 37.48 65.96 36.28 C65.09 35.08 63.39 33.71 61.86 32.71 C60.33 31.71 58.48 30.96 56.79 30.27 C55.11 29.58 51.75 28.56 51.75 28.56 Z M55.05 32.53 C56.27 32.49 57.28 33.45 57.31 34.67 C57.34 35.88 56.38 36.89 55.17 36.92 C53.95 36.96 52.94 36 52.91 34.78 C52.88 33.57 53.84 32.56 55.05 32.53 Z M66.94 0.72 C66.94 0.72 69.1 2.36 70.21 3.36 C71.31 4.36 72.44 5.73 73.54 6.73 C74.64 7.73 75.65 8.62 76.81 9.37 C77.97 10.12 80.51 11.23 80.51 11.23 C80.51 11.23 83.1 9.5 84.77 8.6 C86.45 7.7 88.43 6.53 90.54 5.83 C92.65 5.13 97.43 4.43 97.43 4.43 C97.43 4.43 97.39 9.23 97.07 11.11 C96.75 12.98 96.37 14.25 95.5 15.69 C94.64 17.13 93.43 18.47 91.87 19.73 C90.32 20.99 87.58 22.11 86.18 23.23 C84.78 24.35 84.51 24.99 83.48 26.45 C82.45 27.9 81.57 30.94 79.99 31.95 C78.41 32.96 75.1 33.28 74.02 32.53 C72.94 31.77 73.69 29.11 73.53 27.4 C73.36 25.7 73.67 23.7 73.03 22.28 C72.39 20.87 70.82 20.16 69.69 18.91 C68.57 17.67 67.11 16.45 66.29 14.81 C65.46 13.17 64.9 10.88 64.73 9.05 C64.55 7.22 64.86 5.23 65.23 3.84 C65.6 2.45 66.94 0.72 66.94 0.72 Z M94.05 37.29 L100.84 44.08 C101.89 45.13 101.89 46.84 100.84 47.9 L100.77 47.97 C99.71 49.02 98 49.02 96.95 47.97 L90.16 41.18 C89.11 40.13 89.11 38.42 90.16 37.36 L90.23 37.29 C91.29 36.24 93 36.24 94.05 37.29 Z M90.16 44.08 L96.95 37.29 C98 36.24 99.71 36.24 100.77 37.29 L100.84 37.36 C101.89 38.42 101.89 40.13 100.84 41.18 L94.05 47.97 C93 49.02 91.29 49.02 90.23 47.97 L90.16 47.9 C89.11 46.84 89.11 45.13 90.16 44.08 Z M60 5.29 C60 5.29 62.21 9.2 62.6 10.5 C62.99 11.8 62.77 12.45 62.34 13.1 C61.91 13.75 60.78 14.4 60 14.4 C59.22 14.4 58.09 13.75 57.66 13.1 C57.23 12.45 57.01 11.8 57.4 10.5 C57.79 9.2 60 5.29 60 5.29 Z" fill="rgb(224,49,49)"/></g><text x="420" y="112" text-anchor="middle" font-family="-apple-system, PingFang SC, Microsoft YaHei, sans-serif" font-size="12" fill="#555">刚失败</text><g transform="translate(490 26.25)"><path d="M24.22 -3.98 C27.09 -4.47 29.76 -4.48 33.27 -4.5 C36.78 -4.51 42.25 -3.85 45.27 -4.08 C48.29 -4.31 51.4 -5.89 51.4 -5.89 C51.4 -5.89 55.27 -3.79 55.27 -3.79 C55.27 -3.79 53.67 -2.24 53.11 -1.35 C52.55 -0.47 51.82 0.52 51.92 1.51 C52.02 2.5 52.79 3.53 53.72 4.58 C54.66 5.62 56.1 6.45 57.52 7.77 C58.94 9.09 60.82 10.94 62.22 12.5 C63.62 14.07 64.52 15.72 65.93 17.16 C67.33 18.61 68.73 20.23 70.67 21.16 C72.62 22.08 75.29 22.07 77.59 22.72 C79.88 23.36 84.45 25.01 84.45 25.01 C84.45 25.01 84.62 27.6 84.16 29.41 C83.71 31.22 82.7 33.85 81.73 35.88 C80.77 37.9 79.65 39.67 78.36 41.55 C77.06 43.43 75.27 45.4 73.98 47.15 C72.7 48.91 71.08 50.65 70.65 52.09 C70.22 53.54 70.47 54.78 71.41 55.82 C72.34 56.87 75.31 57.49 76.25 58.35 C77.2 59.21 77.63 60.28 77.08 60.98 C76.54 61.69 75.15 62.39 72.97 62.56 C70.79 62.72 63.99 61.97 63.99 61.97 C63.99 61.97 61.43 60.33 60.17 59.14 C58.91 57.95 57.76 56.34 56.44 54.84 C55.12 53.35 53.63 51.84 52.24 50.15 C50.85 48.46 49.39 46.52 48.08 44.72 C46.78 42.92 45.65 41.12 44.43 39.32 C43.21 37.53 42.16 35.74 40.77 33.93 C39.39 32.12 38.01 30.31 36.12 28.47 C34.24 26.63 32.2 24.65 29.47 22.88 C26.75 21.1 23.12 19.08 19.78 17.82 C16.44 16.56 9.43 15.3 9.43 15.3 C9.43 15.3 7.51 17.82 7.17 19.2 C6.82 20.59 7.22 22.15 7.38 23.64 C7.53 25.12 7.85 26.62 8.09 28.1 C8.32 29.59 8.39 31.07 8.8 32.57 C9.2 34.07 9.77 35.58 10.5 37.1 C11.24 38.62 12.14 40.15 13.21 41.7 C14.28 43.24 15.51 44.8 16.91 46.36 C18.31 47.93 20.02 49.82 21.61 51.09 C23.2 52.36 24.98 53.21 26.43 53.98 C27.89 54.75 30.33 55.71 30.33 55.71 C30.33 55.71 31.37 52.59 31.64 51.01 C31.91 49.43 31.95 46.24 31.95 46.24 C31.95 46.24 37.94 46.63 37.94 46.63 C37.94 46.63 41.25 49.55 42.66 50.99 C44.07 52.44 45.3 54.11 46.39 55.29 C47.48 56.47 49.21 58.05 49.21 58.05 C49.21 58.05 52.48 59.19 54.11 59.85 C55.74 60.51 58.98 62.01 58.98 62.01 C58.98 62.01 55.9 63.03 53.87 63.51 C51.83 63.99 49.45 64.64 46.76 64.89 C44.07 65.14 41.21 65.45 37.73 65.03 C34.25 64.62 29.48 63.57 25.88 62.42 C22.28 61.26 18.86 59.87 16.14 58.09 C13.42 56.32 11.55 54.23 9.54 51.77 C7.53 49.31 5.47 46.35 4.08 43.31 C2.69 40.27 1.75 36.9 1.21 33.55 C0.68 30.2 0.58 26.63 0.89 23.21 C1.2 19.8 1.76 16.27 3.06 13.04 C4.36 9.81 6.51 6.27 8.67 3.84 C10.84 1.4 13.45 -0.27 16.04 -1.57 C18.63 -2.88 21.35 -3.5 24.22 -3.98 Z M52.62 21.45 C52.62 21.45 53.26 24.44 53.82 25.95 C54.39 27.46 55.3 28.99 56.03 30.51 C56.77 32.03 57.48 33.86 58.24 35.07 C58.99 36.29 59.59 37.43 60.56 37.8 C61.54 38.18 63.06 37.91 64.1 37.3 C65.15 36.69 66.49 35.31 66.82 34.16 C67.14 33.02 66.81 31.71 66.06 30.43 C65.31 29.15 63.74 27.64 62.31 26.5 C60.88 25.36 59.1 24.45 57.49 23.61 C55.87 22.77 52.62 21.45 52.62 21.45 Z M55.54 25.7 C56.76 25.77 57.68 26.82 57.6 28.03 C57.52 29.25 56.47 30.17 55.26 30.09 C54.04 30.01 53.13 28.96 53.21 27.75 C53.29 26.53 54.33 25.62 55.54 25.7 Z M68.31 -4.42 C68.31 -4.42 70.43 -2.73 71.51 -1.7 C72.58 -0.67 73.68 0.73 74.76 1.76 C75.84 2.78 76.82 3.7 77.96 4.48 C79.1 5.26 81.61 6.43 81.61 6.43 C81.61 6.43 84.25 4.77 85.94 3.91 C87.63 3.05 89.64 1.93 91.77 1.29 C93.9 0.65 98.7 0.06 98.7 0.06 C98.7 0.06 98.54 4.86 98.17 6.73 C97.8 8.6 97.39 9.86 96.49 11.27 C95.58 12.69 94.34 14 92.76 15.22 C91.17 16.44 88.41 17.49 86.98 18.57 C85.55 19.66 85.26 20.29 84.19 21.72 C83.12 23.15 82.17 26.16 80.57 27.13 C78.96 28.11 75.64 28.34 74.58 27.56 C73.52 26.77 74.34 24.14 74.22 22.43 C74.1 20.71 74.46 18.72 73.85 17.29 C73.25 15.86 71.7 15.11 70.6 13.84 C69.51 12.57 68.08 11.32 67.3 9.65 C66.51 7.99 66.02 5.69 65.89 3.86 C65.76 2.02 66.12 0.03 66.52 -1.35 C66.92 -2.72 68.31 -4.42 68.31 -4.42 Z M93 19.53 C94.02 23.13 96.33 25.44 99.93 26.46 C96.33 27.48 94.02 29.79 93 33.39 C91.98 29.79 89.67 27.48 86.07 26.46 C89.67 25.44 91.98 23.13 93 19.53 Z M58 3.34 C58.8 6.19 60.63 8.02 63.48 8.82 C60.63 9.62 58.8 11.45 58 14.3 C57.2 11.45 55.37 9.62 52.52 8.82 C55.37 8.02 57.2 6.19 58 3.34 Z M96 46.16 C96.34 47.38 97.13 48.17 98.35 48.51 C97.13 48.85 96.34 49.64 96 50.86 C95.66 49.64 94.87 48.85 93.65 48.51 C94.87 48.17 95.66 47.38 96 46.16 Z M10 16.07 C10.45 17.65 11.46 18.66 13.04 19.11 C11.46 19.56 10.45 20.57 10 22.15 C9.55 20.57 8.54 19.56 6.96 19.11 C8.54 18.66 9.55 17.65 10 16.07 Z" fill="rgb(46,160,90)"/></g><text x="540" y="112" text-anchor="middle" font-family="-apple-system, PingFang SC, Microsoft YaHei, sans-serif" font-size="12" fill="#555">刚完成</text></svg>
@@ -0,0 +1,283 @@
1
+ // macOS menu bar helper for dsh-notify-tray, run with `osascript -l JavaScript`.
2
+ // No compiled binary: JXA's Objective-C bridge drives NSStatusItem directly, so
3
+ // it works on any Mac (Apple Silicon or Intel) without Rosetta or Xcode.
4
+ //
5
+ // argv: <view.json path> <host pid>
6
+ // The host rewrites view.json atomically; this script polls its mtime. Menu
7
+ // clicks are written to stdout as one JSON line each: {"action":"<id>"}.
8
+ //
9
+ // The icon is the whale from lib/whale.js, shipped as bezier paths in
10
+ // icon.json (next to view.json; one frame list per state, plus fps / loop)
11
+ // and rasterized here into template images at 1x and 2x.
12
+ // Template images take the menu bar's own colour (white on dark, black on
13
+ // light), exactly like the system icons next to it, so no tinting happens:
14
+ // the whale's state is told by its shape (spout / "!" / "×"), not its colour.
15
+ ObjC.import('Cocoa')
16
+ ObjC.import('stdlib')
17
+ ObjC.import('signal')
18
+
19
+ /** Icon size in points: the whale is wider than tall, so 22×16 sits like an 18 pt template icon. */
20
+ var ICON_H = 17
21
+ var ICON_W = 23
22
+ /** Timer period: view.json is polled and animation frames advanced at this rate. */
23
+ var TICK_SECONDS = 0.1
24
+
25
+ function run(argv) {
26
+ var viewPath = argv[0]
27
+ var hostPid = Number(argv[1] || 0)
28
+ var app = $.NSApplication.sharedApplication
29
+ app.setActivationPolicy($.NSApplicationActivationPolicyAccessory)
30
+
31
+ var item = $.NSStatusBar.systemStatusBar.statusItemWithLength(-1) // NSVariableStatusItemLength
32
+ var stdout = $.NSFileHandle.fileHandleWithStandardOutput
33
+ var fm = $.NSFileManager.defaultManager
34
+ var lastStamp = ''
35
+ var icon = null // icon.json: { box, bounds, frames: { state: [paths...] }, anim: { state: { fps, loop } } }
36
+ var images = {} // "<state>:<frame>" -> NSImage
37
+ var symbolCache = {}
38
+ var current = { state: 'idle', frame: 0, frames: 1, fps: 1, loop: false, clock: 0 }
39
+
40
+ function send(action) {
41
+ var line = $(JSON.stringify({ action: action }) + '\n').dataUsingEncoding($.NSUTF8StringEncoding)
42
+ stdout.writeData(line)
43
+ }
44
+
45
+ function warn(text) {
46
+ $.NSFileHandle.fileHandleWithStandardError.writeData($(text + '\n').dataUsingEncoding($.NSUTF8StringEncoding))
47
+ }
48
+
49
+ function hostAlive() {
50
+ if (!hostPid) return true
51
+ // kill(pid, 0) succeeds while the host process exists.
52
+ return $.kill(hostPid, 0) === 0
53
+ }
54
+
55
+ function readIcon() {
56
+ var iconPath = viewPath.replace(/[^\/]+$/, 'icon.json')
57
+ var text = $.NSString.stringWithContentsOfFileEncodingError(iconPath, $.NSUTF8StringEncoding, null)
58
+ if (!text || text.isNil()) return null
59
+ try { return JSON.parse(ObjC.unwrap(text)) } catch (e) { return null }
60
+ }
61
+
62
+ function readView() {
63
+ var attrs = fm.attributesOfItemAtPathError(viewPath, null)
64
+ if (!attrs || attrs.isNil()) return null
65
+ var stamp = ObjC.unwrap(attrs.fileModificationDate.description) + ':' + ObjC.unwrap(attrs.fileSize)
66
+ if (stamp === lastStamp) return null
67
+ lastStamp = stamp
68
+ var text = $.NSString.stringWithContentsOfFileEncodingError(viewPath, $.NSUTF8StringEncoding, null)
69
+ if (!text || text.isNil()) return null
70
+ try { return JSON.parse(ObjC.unwrap(text)) } catch (e) { return null }
71
+ }
72
+
73
+ // ---- whale rasterizer --------------------------------------------------------
74
+
75
+ /** Build an NSBezierPath from lib/whale.js commands, fitted into w×h pixels and flipped to AppKit's y-up. */
76
+ function bezier(paths, spec, w, h) {
77
+ var path = $.NSBezierPath.bezierPath
78
+ path.windingRule = $.NSNonZeroWindingRule
79
+ var b = spec.bounds || { x: 0, y: 0, w: spec.box, h: spec.box }
80
+ var s = Math.min(w / b.w, h / b.h)
81
+ var ox = (w - b.w * s) / 2
82
+ var oy = (h - b.h * s) / 2
83
+ var P = function (x, y) { return $.NSMakePoint(ox + (x - b.x) * s, h - oy - (y - b.y) * s) }
84
+ paths.forEach(function (sub) {
85
+ sub.forEach(function (cmd) {
86
+ if (cmd[0] === 'M') path.moveToPoint(P(cmd[1], cmd[2]))
87
+ else if (cmd[0] === 'L') path.lineToPoint(P(cmd[1], cmd[2]))
88
+ else if (cmd[0] === 'C') path.curveToPointControlPoint1ControlPoint2(P(cmd[5], cmd[6]), P(cmd[1], cmd[2]), P(cmd[3], cmd[4]))
89
+ else if (cmd[0] === 'Z') path.closePath
90
+ })
91
+ })
92
+ return path
93
+ }
94
+
95
+ /** One bitmap rep of the whale at `scale` (1 or 2), black on transparent. */
96
+ function rep(paths, spec, scale) {
97
+ var w = ICON_W * scale
98
+ var h = ICON_H * scale
99
+ var bitmap = $.NSBitmapImageRep.alloc.initWithBitmapDataPlanesPixelsWidePixelsHighBitsPerSampleSamplesPerPixelHasAlphaIsPlanarColorSpaceNameBytesPerRowBitsPerPixel(
100
+ $(), w, h, 8, 4, true, false, $.NSCalibratedRGBColorSpace, 0, 0)
101
+ $.NSGraphicsContext.saveGraphicsState
102
+ $.NSGraphicsContext.setCurrentContext($.NSGraphicsContext.graphicsContextWithBitmapImageRep(bitmap))
103
+ // Fresh bitmap memory is not guaranteed to be zeroed: clear it to transparent first.
104
+ $.NSColor.clearColor.set
105
+ $.NSRectFill($.NSMakeRect(0, 0, w, h))
106
+ $.NSColor.blackColor.set
107
+ bezier(paths, spec, w, h).fill
108
+ $.NSGraphicsContext.restoreGraphicsState
109
+ // Set the point size only after drawing (before, the context would scale
110
+ // pixel coordinates by it): 36 px at 18 pt is the retina 2x rep.
111
+ bitmap.size = $.NSMakeSize(ICON_W, ICON_H)
112
+ return bitmap
113
+ }
114
+
115
+ function whaleImage(state, frame) {
116
+ var key = state + ':' + frame
117
+ if (images[key]) return images[key]
118
+ var frames = (icon && icon.frames[state]) || (icon && icon.frames.idle)
119
+ if (!frames) return null
120
+ var paths = frames[frame % frames.length]
121
+ var image = $.NSImage.alloc.initWithSize($.NSMakeSize(ICON_W, ICON_H))
122
+ image.addRepresentation(rep(paths, icon, 1))
123
+ image.addRepresentation(rep(paths, icon, 2))
124
+ image.template = true // the menu bar colours it like every other icon
125
+ images[key] = image
126
+ return image
127
+ }
128
+
129
+ function symbolImage(name) {
130
+ if (!name) return null
131
+ if (symbolCache[name] !== undefined) return symbolCache[name]
132
+ var image = null
133
+ if ($.NSImage.imageWithSystemSymbolNameAccessibilityDescription) {
134
+ var candidate = $.NSImage.imageWithSystemSymbolNameAccessibilityDescription(name, name)
135
+ if (candidate && !candidate.isNil()) image = candidate
136
+ }
137
+ symbolCache[name] = image
138
+ return image
139
+ }
140
+
141
+ function showFrame() {
142
+ var image = whaleImage(current.state, current.frame)
143
+ if (image) item.button.image = image
144
+ }
145
+
146
+ // ---- view -> status item -----------------------------------------------------------
147
+
148
+ /** Row height / text inset of a native menu item, so info rows line up with the clickable ones. */
149
+ var INFO_ROW_H = 22
150
+ var INFO_INSET = 14
151
+ /** Leading SF Symbol of an info row: size and gap to the text. */
152
+ var INFO_ICON = 16
153
+ var INFO_GAP = 6
154
+
155
+ /** The user's accent colour (System Settings › Appearance), dark-mode aware; blue before 10.14. */
156
+ function accentColor() {
157
+ var accent = $.NSColor.controlAccentColor
158
+ return accent && !accent.isNil() ? accent : $.NSColor.systemBlueColor
159
+ }
160
+
161
+ /**
162
+ * Read-only rows (balance, usage, …) are disabled so they never highlight or
163
+ * fire, but AppKit draws a disabled title at a very low alpha, which made
164
+ * them hard to read. A disabled item with a custom view is still inert, yet
165
+ * its view is drawn as-is: the text is a plain NSTextField in labelColor
166
+ * (solid black in light mode, white in dark mode), so it reads as clearly as
167
+ * the actionable items. What tells the two apart is the row's own SF Symbol
168
+ * drawn in front of the text in the accent colour: clickable items carry no
169
+ * icon, info rows always do.
170
+ */
171
+ function infoView(label, iconName) {
172
+ var x = INFO_INSET
173
+ var view = $.NSView.alloc.initWithFrame($.NSMakeRect(0, 0, 200, INFO_ROW_H))
174
+ view.autoresizingMask = $.NSViewWidthSizable // the menu stretches it to its own width
175
+ var image = symbolImage(iconName)
176
+ if (image) {
177
+ var iconView = $.NSImageView.alloc.initWithFrame($.NSMakeRect(x, (INFO_ROW_H - INFO_ICON) / 2, INFO_ICON, INFO_ICON))
178
+ iconView.image = image
179
+ iconView.imageScaling = $.NSImageScaleProportionallyUpOrDown
180
+ iconView.contentTintColor = accentColor()
181
+ view.addSubview(iconView)
182
+ x += INFO_ICON + INFO_GAP
183
+ }
184
+ var field = $.NSTextField.alloc.initWithFrame($.NSMakeRect(x, 0, 200, INFO_ROW_H))
185
+ field.bezeled = false
186
+ field.drawsBackground = false
187
+ field.editable = false
188
+ field.selectable = false
189
+ field.font = $.NSFont.menuFontOfSize(0)
190
+ field.textColor = $.NSColor.labelColor
191
+ field.stringValue = $(label)
192
+ field.sizeToFit
193
+ var size = field.frame.size
194
+ field.frame = $.NSMakeRect(x, (INFO_ROW_H - size.height) / 2, size.width, size.height)
195
+ field.autoresizingMask = $.NSViewWidthSizable
196
+ view.addSubview(field)
197
+ view.frame = $.NSMakeRect(0, 0, x + size.width + INFO_INSET, INFO_ROW_H)
198
+ return view
199
+ }
200
+
201
+ function buildMenu(rows) {
202
+ var menu = $.NSMenu.alloc.init
203
+ menu.autoenablesItems = false
204
+ rows.forEach(function (row) {
205
+ if (row.separator) { menu.addItem($.NSMenuItem.separatorItem); return }
206
+ var mi = $.NSMenuItem.alloc.initWithTitleActionKeyEquivalent($(row.label), 'menuClicked:', '')
207
+ mi.target = target
208
+ mi.representedObject = $(row.id || '')
209
+ mi.enabled = row.enabled !== false
210
+ var image = symbolImage(row.icon)
211
+ if (image) mi.image = image
212
+ var hasChildren = Boolean(row.children && row.children.length)
213
+ if (!mi.enabled && !hasChildren) mi.view = infoView(row.label, row.icon)
214
+ if (hasChildren) {
215
+ mi.action = $()
216
+ mi.enabled = true
217
+ mi.submenu = buildMenu(row.children)
218
+ }
219
+ menu.addItem(mi)
220
+ })
221
+ return menu
222
+ }
223
+
224
+ function apply(view) {
225
+ if (view.quit) { app.terminate(null); return } // the host is taking the icon down
226
+ if (view.icon) { icon = view.icon; images = {} } // legacy: geometry inline
227
+ if (!icon) icon = readIcon()
228
+ var frames = (icon && icon.frames[view.state]) ? icon.frames[view.state].length : 1
229
+ var anim = (icon && icon.anim && icon.anim[view.state]) || { fps: 1, loop: false }
230
+ // Same state again (e.g. the menu changed while running): keep the animation where it is.
231
+ if (view.state !== current.state) current = { state: view.state, frame: 0, frames: frames, fps: anim.fps, loop: anim.loop, clock: 0 }
232
+ var button = item.button
233
+ button.imagePosition = $.NSImageLeft
234
+ showFrame()
235
+ button.title = $(view.title ? ' ' + view.title : '')
236
+ button.toolTip = $(view.tooltip)
237
+ item.menu = buildMenu(view.menu)
238
+ }
239
+
240
+ ObjC.registerSubclass({
241
+ name: 'DshTrayTarget',
242
+ methods: {
243
+ 'menuClicked:': {
244
+ types: ['void', ['id']],
245
+ implementation: function (sender) {
246
+ var id = ObjC.unwrap(sender.representedObject)
247
+ if (!id) return
248
+ if (id === 'hide-tray') { send(id); app.terminate(null); return }
249
+ send(id)
250
+ },
251
+ },
252
+ 'tick:': {
253
+ types: ['void', ['id']],
254
+ implementation: function () {
255
+ try {
256
+ if (!hostAlive()) { app.terminate(null); return }
257
+ var view = readView()
258
+ if (view) apply(view)
259
+ if (current.frames > 1 && (current.loop || current.frame < current.frames - 1)) {
260
+ current.clock += TICK_SECONDS
261
+ var step = 1 / Math.max(1, current.fps)
262
+ if (current.clock >= step) {
263
+ current.clock -= step
264
+ current.frame = current.loop ? (current.frame + 1) % current.frames : Math.min(current.frames - 1, current.frame + 1)
265
+ showFrame()
266
+ }
267
+ }
268
+ } catch (e) {
269
+ // ObjC callbacks swallow exceptions silently; surface them on stderr.
270
+ warn('tray tick failed: ' + e)
271
+ }
272
+ },
273
+ },
274
+ },
275
+ })
276
+ var target = $.DshTrayTarget.alloc.init
277
+
278
+ var first = readView()
279
+ if (first) apply(first)
280
+ else item.button.title = $(' …')
281
+ $.NSTimer.scheduledTimerWithTimeIntervalTargetSelectorUserInfoRepeats(TICK_SECONDS, target, 'tick:', null, true)
282
+ app.run
283
+ }