diff --git a/PRODUCT_CAPABILITIES.zh-CN.md b/PRODUCT_CAPABILITIES.zh-CN.md
index 9535e22..f0b80cf 100644
--- a/PRODUCT_CAPABILITIES.zh-CN.md
+++ b/PRODUCT_CAPABILITIES.zh-CN.md
@@ -28,10 +28,10 @@
| 能力域 | 能力 | 用户结果 | 当前状态 |
|---|---|---|---|
-| 分享入口 | 在微信转发菜单增加十二个入口 | 无需打开微信流主窗口即可发起场景 | 稳定 |
+| 分享入口 | 在微信转发菜单增加十三个入口 | 无需打开微信流主窗口即可发起场景 | 稳定 |
| 分批收集 | 连续接收多批原始 ZIP,最后统一选择目标和场景 | 浮条首尾参考、记录页恢复、冻结交付和失败重试 | 开发版已实现,微信宿主分享链路待实测 |
| 文件接入 | 接收微信导出的 ZIP 和文件型附件 | 原始文件和文件名进入本地批次 | 稳定 |
-| 目标转发 | 发给 Codex、Claude、豆包、千问办公、WorkBuddy、WeSight 或自定义应用 | 激活目标 App 并自动粘贴 | 稳定,依赖辅助功能权限 |
+| 目标转发 | 发给 Codex、Claude、豆包、千问办公、WorkBuddy、WeSight、DeepSeek Harness、Qoder CN 或自定义应用 | 激活目标 App 并自动粘贴 | 稳定,依赖辅助功能权限 |
| Obsidian | 生成 Markdown 笔记并保存原始归档 | 聊天记录进入本地知识库 | 稳定 |
| 剪贴板 | 文件写入剪贴板 | 用户自行在目标位置粘贴 | 稳定 |
| 记录 | 历史批次、状态、重新发送和清理 | 可以确认文件是否送达 | 稳定 |
@@ -178,7 +178,7 @@
### 8.2 网络
- 除软件更新外不联网。
-- 十二个 Share Extension 均在沙盒中运行。
+- 十三个 Share Extension 均在沙盒中运行。
- 扩展没有网络权限。
### 8.3 系统权限
@@ -219,7 +219,7 @@
| 能力 | 主要实现 |
|---|---|
-| 十二个分享入口 | `macos/Scripts/share-slots.sh` |
+| 十三个分享入口 | `macos/Scripts/share-slots.sh` |
| Share Extension 接入 | `macos/Sources/WeChatBridgeShare/ShareViewController.swift` |
| 附件复制与命名 | `macos/Sources/WeChatBridgeShare/AttachmentImporter.swift` |
| 目标应用定义 | `macos/Sources/WeChatBridgeCore/ShareAction.swift` |
diff --git a/README.md b/README.md
index 857093c..304f107 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ flowchart LR
| 能力 | 使用体验 |
| --- | --- |
-| 十二个原生入口 | 在微信转发菜单直接选择目标,无需打开微信流主窗口 |
+| 十三个原生入口 | 在微信转发菜单直接选择目标,无需打开微信流主窗口 |
| AI Agent 转发 | 激活目标 App,附加场景指令并自动粘贴聊天归档 |
| Obsidian 沉淀 | 生成 Markdown 笔记,保存原始 ZIP,并按聊天名组织内容 |
| 自定义目标 | 添加任意 macOS 应用,终端类应用可只接收文件路径 |
@@ -59,6 +59,7 @@ flowchart LR
| 发给 WorkBuddy | 激活 WorkBuddy 并粘贴聊天归档 |
| 发给 WeSight | 激活 WeSight 并粘贴聊天归档 |
| 发给 DeepSeek Harness | 激活 DeepSeek Harness 并粘贴聊天归档 |
+| 发给 Qoder CN | 激活 Qoder CN 并粘贴聊天归档 |
| 沉淀到 Obsidian | 创建 Markdown 笔记并保存原始附件,通知可打开笔记 |
| 沉淀到文件夹 | 写入选定目录中的 Markdown 与附件,通知可在访达中定位 |
| 分批收集到微信流 | 连续分享多批原始 ZIP,收齐后一次选择目标和场景 |
@@ -85,7 +86,7 @@ flowchart LR
 |
- | 十二个入口随时开关,未安装的应用直接标注 |
+ 十三个入口随时开关,未安装的应用直接标注 |
场景保存提示词与适用 Agent,转发时挑一个 |
@@ -179,7 +180,7 @@ shared/ # 两端共用的资产与契约
site/ # Sparkle 更新源与版本说明
```
-项目使用 Swift Package Manager 管理 macOS 源码和 Sparkle 依赖。`macos/Scripts/make-app.sh` 会把主程序与十二个 Share Extension 组装成完整的 `.app`。Windows 侧见 `windows/AGENTS.md` 与 `windows/docs/README.zh-CN.md`。
+项目使用 Swift Package Manager 管理 macOS 源码和 Sparkle 依赖。`macos/Scripts/make-app.sh` 会把主程序与十三个 Share Extension 组装成完整的 `.app`。Windows 侧见 `windows/AGENTS.md` 与 `windows/docs/README.zh-CN.md`。
## 开发与验证
diff --git a/README_EN.md b/README_EN.md
index 7854dba..a0aace6 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -39,7 +39,7 @@ flowchart LR
| Capability | Experience |
| --- | --- |
-| Twelve native entries | Pick a destination in WeChat without opening the main app |
+| Thirteen native entries | Pick a destination in WeChat without opening the main app |
| AI agent hand-off | Activate the target app, attach a scene prompt, and paste the archive |
| Obsidian archiving | Create a Markdown note, retain the original ZIP, and organize it by chat name |
| Custom destinations | Add any macOS app; terminal-style apps can receive file paths only |
@@ -59,6 +59,7 @@ flowchart LR
| Send to WorkBuddy | Activate WorkBuddy and paste the conversation archive |
| Send to WeSight | Activate WeSight and paste the conversation archive |
| Send to DeepSeek Harness | Activate DeepSeek Harness and paste the conversation archive |
+| Send to Qoder CN | Activate Qoder CN and paste the conversation archive |
| Save to Obsidian | Create a Markdown note and preserve the original attachment |
| Save to folder | Write Markdown and attachments to the selected folder; reveal files from the notification |
| Collect in WeChatBridge | Receive multiple original ZIPs, then choose a target and scene once |
@@ -83,7 +84,7 @@ Pick a destination straight from WeChat's “Forward to other apps” menu, with
 |
- | Toggle any of the twelve entries; missing apps are flagged |
+ Toggle any of the thirteen entries; missing apps are flagged |
Scenes store prompts and their agents, chosen at forward time |
@@ -156,7 +157,7 @@ shared/ # Agent logos, design assets, screenshots, and skills
site/ # Sparkle update feed and release notes
```
-Swift Package Manager owns the source layout and the Sparkle dependency. `macos/Scripts/make-app.sh` assembles the host executable and twelve Share Extensions into a complete `.app` bundle.
+Swift Package Manager owns the source layout and the Sparkle dependency. `macos/Scripts/make-app.sh` assembles the host executable and thirteen Share Extensions into a complete `.app` bundle.
## Development checks
diff --git a/USAGE.zh-CN.md b/USAGE.zh-CN.md
index dc7219a..1f5d014 100644
--- a/USAGE.zh-CN.md
+++ b/USAGE.zh-CN.md
@@ -19,7 +19,7 @@
## 转发入口
-十二个入口覆盖主流 Agent、笔记软件和剪贴板,随时可以用开关收窄或放开。
+十三个入口覆盖主流 Agent、笔记软件和剪贴板,随时可以用开关收窄或放开。

@@ -29,7 +29,7 @@

-- 发给 Codex / Claude / 豆包 / 千问办公 / WorkBuddy / WeSight:激活目标应用并粘贴聊天归档。
+- 发给 Codex / Claude / 豆包 / 千问办公 / WorkBuddy / WeSight / DeepSeek Harness / Qoder CN:激活目标应用并粘贴聊天归档。
- 沉淀到 Obsidian:生成 Markdown 笔记,同时保存微信导出的原始压缩包;通过通知打开笔记。
- 沉淀到文件夹:将 Markdown、原始 ZIP 和附件保存到选定目录,默认位置为下载目录下的「微信流」文件夹。
- 复制到剪贴板:只写文件,不自动粘贴,由你自己决定贴到哪里。
diff --git a/USAGE_EN.md b/USAGE_EN.md
index c75cac7..d776ae5 100644
--- a/USAGE_EN.md
+++ b/USAGE_EN.md
@@ -19,7 +19,7 @@ These entries come from WeChatBridge and sit in the same menu as AirDrop, Messag
## Destinations
-Twelve entries cover mainstream agents, note-taking apps, and the clipboard. You can narrow or widen the list at any time.
+Thirteen entries cover mainstream agents, note-taking apps, and the clipboard. You can narrow or widen the list at any time.

@@ -29,7 +29,7 @@ Supported agents:

-- Send to Codex / Claude / Doubao / QwenWork / WorkBuddy / WeSight: activate the target app and paste the archive.
+- Send to Codex / Claude / Doubao / QwenWork / WorkBuddy / WeSight / DeepSeek Harness / Qoder CN: activate the target app and paste the archive.
- Save to Obsidian: create a Markdown note and keep the original WeChat export.
- Copy to Clipboard: write the files without pasting, so you decide where they land.
- Send to Custom: add any macOS app; terminal-style apps can receive file paths only.
diff --git a/macos/Resources/Localizations/en.lproj/Localizable.strings b/macos/Resources/Localizations/en.lproj/Localizable.strings
index 02b1b15..21e47a6 100644
--- a/macos/Resources/Localizations/en.lproj/Localizable.strings
+++ b/macos/Resources/Localizations/en.lproj/Localizable.strings
@@ -32,6 +32,7 @@
"发给豆包" = "Send to Doubao";
"发给 WeSight" = "Send to WeSight";
"发给 DeepSeek Harness" = "Send to DeepSeek Harness";
+"发给 Qoder CN" = "Send to Qoder CN";
"沉淀到 Obsidian" = "Save to Obsidian";
"沉淀到文件夹" = "Save to Folder";
"添加应用…" = "Add an App…";
@@ -131,6 +132,7 @@
"激活豆包并直接粘贴到输入框。" = "Brings Doubao forward and pastes straight into its input.";
"激活 WeSight 并直接粘贴到输入框。" = "Brings WeSight forward and pastes straight into its input.";
"激活 DeepSeek Harness 并直接粘贴到输入框。" = "Brings DeepSeek Harness forward and pastes straight into its input.";
+"激活 Qoder CN 并直接粘贴到输入框。" = "Brings Qoder CN forward and pastes straight into its input.";
"把聊天记录转成 Markdown,写入选定的 Obsidian 知识库。" = "Turns the chat archive into Markdown and writes it into the selected Obsidian vault.";
"把聊天记录转成 Markdown 和附件,写入选定的文件夹。" = "Turns the chat into Markdown and attachments in the folder you choose.";
"只放进剪贴板,去哪儿按 ⌘V 由你决定。" = "Puts them on the clipboard and leaves the ⌘V to you.";
diff --git a/macos/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/macos/Resources/Localizations/zh-Hans.lproj/Localizable.strings
index c05c775..81e3356 100644
--- a/macos/Resources/Localizations/zh-Hans.lproj/Localizable.strings
+++ b/macos/Resources/Localizations/zh-Hans.lproj/Localizable.strings
@@ -32,6 +32,7 @@
"发给豆包" = "发给豆包";
"发给 WeSight" = "发给 WeSight";
"发给 DeepSeek Harness" = "发给 DeepSeek Harness";
+"发给 Qoder CN" = "发给 Qoder CN";
"沉淀到 Obsidian" = "沉淀到 Obsidian";
"沉淀到文件夹" = "沉淀到文件夹";
"添加应用…" = "添加应用…";
@@ -131,6 +132,7 @@
"激活豆包并直接粘贴到输入框。" = "激活豆包并直接粘贴到输入框。";
"激活 WeSight 并直接粘贴到输入框。" = "激活 WeSight 并直接粘贴到输入框。";
"激活 DeepSeek Harness 并直接粘贴到输入框。" = "激活 DeepSeek Harness 并直接粘贴到输入框。";
+"激活 Qoder CN 并直接粘贴到输入框。" = "激活 Qoder CN 并直接粘贴到输入框。";
"把聊天记录转成 Markdown,写入选定的 Obsidian 知识库。" = "把聊天记录转成 Markdown,写入选定的 Obsidian 知识库。";
"把聊天记录转成 Markdown 和附件,写入选定的文件夹。" = "把聊天记录转成 Markdown 和附件,写入选定的文件夹。";
"只放进剪贴板,去哪儿按 ⌘V 由你决定。" = "只放进剪贴板,去哪儿按 ⌘V 由你决定。";
diff --git a/macos/Resources/ShareLocalizations/QoderCN/en.lproj/InfoPlist.strings b/macos/Resources/ShareLocalizations/QoderCN/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000..424ab0f
--- /dev/null
+++ b/macos/Resources/ShareLocalizations/QoderCN/en.lproj/InfoPlist.strings
@@ -0,0 +1 @@
+"CFBundleDisplayName" = "Send to Qoder CN";
diff --git a/macos/Resources/ShareLocalizations/QoderCN/zh-Hans.lproj/InfoPlist.strings b/macos/Resources/ShareLocalizations/QoderCN/zh-Hans.lproj/InfoPlist.strings
new file mode 100644
index 0000000..b6c0ab9
--- /dev/null
+++ b/macos/Resources/ShareLocalizations/QoderCN/zh-Hans.lproj/InfoPlist.strings
@@ -0,0 +1 @@
+"CFBundleDisplayName" = "发给 Qoder CN";
diff --git a/macos/Scripts/check-release-config.sh b/macos/Scripts/check-release-config.sh
index 9bd49e6..62d32f4 100755
--- a/macos/Scripts/check-release-config.sh
+++ b/macos/Scripts/check-release-config.sh
@@ -23,8 +23,8 @@ plutil -lint "$APP_INFO" "$SHARE_INFO" "$APP_ENTITLEMENTS" "$SHARE_ENTITLEMENTS"
plutil -lint "$ROOT"/Resources/Localizations/*/*.strings "$ROOT"/Resources/ShareLocalizations/*/*/*.strings >/dev/null
python3 -m json.tool "$SHARED/Skills/catalog.json" >/dev/null
-if [ "${#SHARE_SLOTS[@]}" -ne 12 ]; then
- echo "expected 12 Share-menu entries, found ${#SHARE_SLOTS[@]}" >&2
+if [ "${#SHARE_SLOTS[@]}" -ne 13 ]; then
+ echo "expected 13 Share-menu entries, found ${#SHARE_SLOTS[@]}" >&2
exit 1
fi
diff --git a/macos/Scripts/share-slots.sh b/macos/Scripts/share-slots.sh
index ead305f..35c5381 100644
--- a/macos/Scripts/share-slots.sh
+++ b/macos/Scripts/share-slots.sh
@@ -6,7 +6,7 @@
# same pkd election System Settings → General → Login Items & Extensions →
# Sharing does.
#
-# All twelve run the same executable and tell themselves apart by DKShareAction,
+# All of them run the same executable and tell themselves apart by DKShareAction,
# so adding an entry costs a row here and a pair of InfoPlist.strings — not a
# second copy of the import code.
#
@@ -25,6 +25,7 @@ SHARE_SLOTS=(
"WorkBuddy|WeChatBridgeShareWorkBuddy|ShareWorkBuddy|workBuddy|发给 WorkBuddy"
"WeSight|WeChatBridgeShareWeSight|ShareWeSight|weSight|发给 WeSight"
"DeepSeekHarness|WeChatBridgeShareDeepSeekHarness|ShareDeepSeekHarness|deepSeekHarness|发给 DeepSeek Harness"
+ "QoderCN|WeChatBridgeShareQoderCN|ShareQoderCN|qoderCN|发给 Qoder CN"
"Obsidian|WeChatBridgeShareObsidian|ShareObsidian|obsidian|沉淀到 Obsidian"
"Collection|WeChatBridgeShareCollection|ShareCollection|collect|分批收集到微信流"
"Folder|WeChatBridgeShareFolder|ShareFolder|folder|沉淀到文件夹"
@@ -42,6 +43,7 @@ share_logo_name() {
QwenWork) printf '%s' "02-qwen.png" ;;
WorkBuddy) printf '%s' "06-workbuddy.png" ;;
WeSight) printf '%s' "07-wesight.png" ;;
+ QoderCN) printf '%s' "08-qodercn.png" ;;
Obsidian) printf '%s' "05-obsidian.png" ;;
esac
}
diff --git a/macos/Sources/WeChatBridgeApp/ActionRunner.swift b/macos/Sources/WeChatBridgeApp/ActionRunner.swift
index 525bdb2..0a59c33 100644
--- a/macos/Sources/WeChatBridgeApp/ActionRunner.swift
+++ b/macos/Sources/WeChatBridgeApp/ActionRunner.swift
@@ -95,7 +95,7 @@ final class ActionRunner {
// Nothing is shown: the share sheet said 「已复制到剪贴板」 a moment
// ago and is still on screen. A second capsule saying it again is
// WeChatBridge talking over the system.
- case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .obsidian, .folder, .custom:
+ case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN, .obsidian, .folder, .custom:
// Shares and WeChat captures share the same clipboard queue.
//
// Read here rather than where the panel opens. This forward may wait
diff --git a/macos/Sources/WeChatBridgeApp/AppModel.swift b/macos/Sources/WeChatBridgeApp/AppModel.swift
index c981d16..a03d22a 100644
--- a/macos/Sources/WeChatBridgeApp/AppModel.swift
+++ b/macos/Sources/WeChatBridgeApp/AppModel.swift
@@ -220,7 +220,7 @@ final class AppModel: ObservableObject {
// Already copied, by the extension, and already recorded as
// such by `BatchState.initial`. Nothing left to do.
return false
- case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .obsidian, .folder, .custom:
+ case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN, .obsidian, .folder, .custom:
// A forward whose one-shot request is already gone: the file was
// consumed by a run that then died before it could act, or the
// extension never managed to write it. Either way nothing can be
diff --git a/macos/Sources/WeChatBridgeApp/ForwardTargets.swift b/macos/Sources/WeChatBridgeApp/ForwardTargets.swift
index 8c1a9a4..51f29bd 100644
--- a/macos/Sources/WeChatBridgeApp/ForwardTargets.swift
+++ b/macos/Sources/WeChatBridgeApp/ForwardTargets.swift
@@ -158,6 +158,7 @@ final class ForwardTargets: ObservableObject {
ForwardDestination(action: .workBuddy, target: nil),
ForwardDestination(action: .weSight, target: nil),
ForwardDestination(action: .deepSeekHarness, target: nil),
+ ForwardDestination(action: .qoderCN, target: nil),
ForwardDestination(action: .obsidian, target: nil),
] + targets.map { ForwardDestination(action: .custom, target: $0) }
}
diff --git a/macos/Sources/WeChatBridgeApp/ShareEntryProbe.swift b/macos/Sources/WeChatBridgeApp/ShareEntryProbe.swift
index f1f47b5..f269b54 100644
--- a/macos/Sources/WeChatBridgeApp/ShareEntryProbe.swift
+++ b/macos/Sources/WeChatBridgeApp/ShareEntryProbe.swift
@@ -142,7 +142,7 @@ final class ShareEntryProbe: ObservableObject {
///
/// `run` blocks on a `DispatchSemaphore`, and a blocked cooperative thread
/// is gone until it unblocks — the runtime cannot reclaim it. `refresh`
- /// probes nine entries in a row, so one cold `pkd` could park a pool thread
+ /// probes every entry in a row, so one cold `pkd` could park a pool thread
/// for 15 s, and flipping several switches quickly parks more. On a
/// four-core Mac that is most of the pool, and the `Task.sleep` loop
/// `AutoPaste` uses to wait for the target app to come frontmost has a
diff --git a/macos/Sources/WeChatBridgeApp/Views/Settings/ShareEntryList.swift b/macos/Sources/WeChatBridgeApp/Views/Settings/ShareEntryList.swift
index 129a097..94f0a92 100644
--- a/macos/Sources/WeChatBridgeApp/Views/Settings/ShareEntryList.swift
+++ b/macos/Sources/WeChatBridgeApp/Views/Settings/ShareEntryList.swift
@@ -172,6 +172,7 @@ struct ShareEntryList: View {
case .qwen: file = "02-qwen.png"
case .workBuddy: file = "06-workbuddy.png"
case .weSight: file = "07-wesight.png"
+ case .qoderCN: file = "08-qodercn.png"
case .obsidian: file = "05-obsidian.png"
case .deepSeekHarness, .collect, .folder, .clipboard, .custom: return nil
}
@@ -187,7 +188,7 @@ struct ShareEntryList: View {
/// table of symbols would be a second answer to "which icon is this entry".
static func symbol(for action: ShareAction) -> String {
switch action {
- case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness: "paperplane"
+ case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN: "paperplane"
case .obsidian: "book.closed"
case .collect: "tray.and.arrow.down"
case .folder: "folder"
@@ -199,7 +200,7 @@ struct ShareEntryList: View {
private func detail(for action: ShareAction) -> String {
if compactDetails {
switch action {
- case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness:
+ case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN:
guard let bundleIdentifier = action.targetBundleIdentifier else {
return L10n.text("未安装")
}
@@ -231,6 +232,7 @@ struct ShareEntryList: View {
case .workBuddy: return L10n.text("激活 WorkBuddy 并直接粘贴到输入框。")
case .weSight: return L10n.text("激活 WeSight 并直接粘贴到输入框。")
case .deepSeekHarness: return L10n.text("激活 DeepSeek Harness 并直接粘贴到输入框。")
+ case .qoderCN: return L10n.text("激活 Qoder CN 并直接粘贴到输入框。")
case .obsidian: return L10n.text("把聊天记录转成 Markdown,写入选定的 Obsidian 知识库。")
case .collect: return L10n.text("多次分享,收齐后一起发送")
case .folder: return L10n.text("把聊天记录转成 Markdown 和附件,写入选定的文件夹。")
@@ -242,7 +244,7 @@ struct ShareEntryList: View {
private func detailIsWarning(for action: ShareAction) -> Bool {
guard compactDetails else { return false }
switch action {
- case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness:
+ case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN:
return action.targetBundleIdentifier.map {
!InstalledApp.lookup($0).isInstalled
} ?? true
diff --git a/macos/Sources/WeChatBridgeCore/BatchState.swift b/macos/Sources/WeChatBridgeCore/BatchState.swift
index 08d5057..22709aa 100644
--- a/macos/Sources/WeChatBridgeCore/BatchState.swift
+++ b/macos/Sources/WeChatBridgeCore/BatchState.swift
@@ -134,7 +134,7 @@ public struct BatchState: Codable, Sendable, Hashable {
action: action,
targetName: targetName
)
- case .collect, .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .obsidian, .folder, .custom:
+ case .collect, .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN, .obsidian, .folder, .custom:
return BatchState(outcome: nil, action: action, targetName: targetName)
}
}
diff --git a/macos/Sources/WeChatBridgeCore/ShareAction.swift b/macos/Sources/WeChatBridgeCore/ShareAction.swift
index 98fc009..f058e7a 100644
--- a/macos/Sources/WeChatBridgeCore/ShareAction.swift
+++ b/macos/Sources/WeChatBridgeCore/ShareAction.swift
@@ -23,6 +23,8 @@ public enum ShareAction: String, Codable, Sendable, CaseIterable {
case weSight
/// Paste into DeepSeek Harness.
case deepSeekHarness
+ /// Paste into Qoder CN.
+ case qoderCN
/// Write a Markdown note and its source archive into the configured vault.
case obsidian
/// Write a Markdown note and its attachments into the configured folder,
@@ -83,6 +85,7 @@ public enum ShareAction: String, Codable, Sendable, CaseIterable {
case .workBuddy: return "com.tencent.workbuddy.mac"
case .weSight: return "ai.wesight.app"
case .deepSeekHarness: return "com.deepseek.dsh"
+ case .qoderCN: return "com.qodercn.app"
case .obsidian: return "md.obsidian"
// `.custom` has no fixed destination and no destination in its intent
// either. `ActionRunner` resolves one from the user's own list, or takes
@@ -102,7 +105,7 @@ public enum ShareAction: String, Codable, Sendable, CaseIterable {
public var needsIntent: Bool {
switch self {
case .collect, .clipboard: return false
- case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .obsidian, .folder, .custom: return true
+ case .codex, .claude, .doubao, .qwen, .workBuddy, .weSight, .deepSeekHarness, .qoderCN, .obsidian, .folder, .custom: return true
}
}
@@ -118,6 +121,7 @@ public enum ShareAction: String, Codable, Sendable, CaseIterable {
case .workBuddy: return "WorkBuddy"
case .weSight: return L10n.text("WeSight")
case .deepSeekHarness: return "DeepSeek Harness"
+ case .qoderCN: return "Qoder CN"
case .obsidian: return L10n.text("Obsidian")
case .collect: return L10n.text("分批收集")
case .folder: return L10n.text("文件夹")
@@ -141,6 +145,7 @@ public enum ShareAction: String, Codable, Sendable, CaseIterable {
case .workBuddy: return L10n.text("发给 WorkBuddy")
case .weSight: return L10n.text("发给 WeSight")
case .deepSeekHarness: return L10n.text("发给 DeepSeek Harness")
+ case .qoderCN: return L10n.text("发给 Qoder CN")
case .obsidian: return L10n.text("沉淀到 Obsidian")
case .collect: return L10n.text("分批收集到微信流")
case .folder: return L10n.text("沉淀到文件夹")
@@ -162,6 +167,7 @@ public enum ShareAction: String, Codable, Sendable, CaseIterable {
case .workBuddy: return "ShareWorkBuddy"
case .weSight: return "ShareWeSight"
case .deepSeekHarness: return "ShareDeepSeekHarness"
+ case .qoderCN: return "ShareQoderCN"
case .obsidian: return "ShareObsidian"
case .collect: return "ShareCollection"
case .folder: return "ShareFolder"
diff --git a/macos/Sources/WeChatBridgeCore/ShareFailure.swift b/macos/Sources/WeChatBridgeCore/ShareFailure.swift
index fad1892..8d1359e 100644
--- a/macos/Sources/WeChatBridgeCore/ShareFailure.swift
+++ b/macos/Sources/WeChatBridgeCore/ShareFailure.swift
@@ -17,7 +17,7 @@ public struct ShareFailure: Codable, Sendable, Hashable {
public let at: Date
/// Which Share-menu entry the user picked. Not shown anywhere yet — the
/// message already names what went wrong — but a field report is worth
- /// little without knowing which of the nine entries produced it.
+ /// little without knowing which entry produced it.
public let action: ShareAction
public let message: String
diff --git a/macos/Tests/WeChatBridgeCoreTests/ShareActionTests.swift b/macos/Tests/WeChatBridgeCoreTests/ShareActionTests.swift
index 7d23f4d..39f161e 100644
--- a/macos/Tests/WeChatBridgeCoreTests/ShareActionTests.swift
+++ b/macos/Tests/WeChatBridgeCoreTests/ShareActionTests.swift
@@ -16,7 +16,7 @@ final class ShareActionTests: XCTestCase {
}
func testOnlyForwardingActionsNameATargetApp() {
- XCTAssertEqual(ShareAction.allCases.count, 12)
+ XCTAssertEqual(ShareAction.allCases.count, 13)
XCTAssertEqual(ShareAction.codex.targetBundleIdentifier, "com.openai.codex")
XCTAssertEqual(ShareAction.claude.targetBundleIdentifier, "com.anthropic.claudefordesktop")
XCTAssertEqual(ShareAction.doubao.targetBundleIdentifier, "com.bot.pc.doubao")
@@ -24,6 +24,7 @@ final class ShareActionTests: XCTestCase {
XCTAssertEqual(ShareAction.workBuddy.targetBundleIdentifier, "com.tencent.workbuddy.mac")
XCTAssertEqual(ShareAction.weSight.targetBundleIdentifier, "ai.wesight.app")
XCTAssertEqual(ShareAction.deepSeekHarness.targetBundleIdentifier, "com.deepseek.dsh")
+ XCTAssertEqual(ShareAction.qoderCN.targetBundleIdentifier, "com.qodercn.app")
XCTAssertEqual(ShareAction.obsidian.targetBundleIdentifier, "md.obsidian")
XCTAssertNil(ShareAction.clipboard.targetBundleIdentifier)
// 「沉淀到文件夹」 delivers to a path, not an app.
@@ -51,6 +52,18 @@ final class ShareActionTests: XCTestCase {
XCTAssertEqual(try BatchManifest.decoder().decode(BatchManifest.self, from: manifestData).action, .folder)
}
+ /// The raw value is the wire contract: `Scripts/share-slots.sh` stamps it
+ /// into the appex's `DKShareAction`, and `ShareAction.declared` reads it
+ /// back. Renaming the case without the slot row breaks the entry silently,
+ /// so both halves are pinned together here.
+ func testQoderCNActionRoundTripsThroughCodable() throws {
+ let data = try JSONEncoder().encode(ShareAction.qoderCN)
+ XCTAssertEqual(String(data: data, encoding: .utf8), "\"qoderCN\"")
+ XCTAssertEqual(try JSONDecoder().decode(ShareAction.self, from: data), .qoderCN)
+ XCTAssertEqual(ShareAction.qoderCN.bundleIdentifierSuffix, "ShareQoderCN")
+ XCTAssertTrue(ShareAction.qoderCN.needsIntent)
+ }
+
func testAnIntentIsReadableExactlyOnce() throws {
let batchID = try commitBatch(intent: BatchIntent(action: .codex, requestedAt: Date()))
@@ -84,7 +97,7 @@ final class ShareActionTests: XCTestCase {
XCTAssertFalse(ShareAction.clipboard.needsIntent)
for action in [
ShareAction.codex, .claude, .doubao, .qwen, .workBuddy,
- .weSight, .deepSeekHarness, .obsidian, .folder, .custom,
+ .weSight, .deepSeekHarness, .qoderCN, .obsidian, .folder, .custom,
] {
XCTAssertTrue(action.needsIntent, "\(action)")
}
diff --git a/shared/AppLogos/08-qodercn.png b/shared/AppLogos/08-qodercn.png
new file mode 100644
index 0000000..1fbf1ef
Binary files /dev/null and b/shared/AppLogos/08-qodercn.png differ
diff --git a/shared/AppLogos/README.md b/shared/AppLogos/README.md
index a9c0936..2452cb1 100644
--- a/shared/AppLogos/README.md
+++ b/shared/AppLogos/README.md
@@ -9,6 +9,7 @@ Official 1024 x 1024 App Store artwork, ordered for the forwarding flow:
5. `05-obsidian.png` - [Obsidian](https://apps.apple.com/cn/app/id1557175442)
6. `06-workbuddy.png` - [WorkBuddy](https://apps.apple.com/cn/app/id6761374913)
7. `07-wesight.png` - WeSight
+8. `08-qodercn.png` - Qoder CN (icon extracted from the local `/Applications/Qoder CN.app` bundle; no App Store listing)
Doubao, Qwen, Obsidian, and WorkBuddy use the China App Store listing.
Claude and ChatGPT have no China App Store listing, so their official