@unlinearity/cli-wechat-bridge 1.2.0 → 1.2.2
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 +25 -10
- package/bin/wechat-check-update.mjs +0 -0
- package/bin/wechat-claude.mjs +0 -0
- package/bin/wechat-codex.mjs +0 -0
- package/bin/wechat-daemon.mjs +0 -0
- package/bin/wechat-opencode.mjs +0 -0
- package/bin/wechat-pi.mjs +0 -0
- package/bin/wechat-setup.mjs +0 -0
- package/bin/wecom-check-update.mjs +0 -0
- package/bin/wecom-claude.mjs +0 -0
- package/bin/wecom-codex.mjs +0 -0
- package/bin/wecom-daemon.mjs +0 -0
- package/bin/wecom-opencode.mjs +0 -0
- package/bin/wecom-pi.mjs +0 -0
- package/bin/wecom-setup.mjs +0 -0
- package/dist/bridge/adapter-control.js +19 -12
- package/dist/bridge/bridge-adapters.claude.js +219 -115
- package/dist/bridge/bridge-adapters.codex.js +295 -90
- package/dist/bridge/bridge-adapters.core.js +84 -7
- package/dist/bridge/bridge-adapters.opencode.js +344 -74
- package/dist/bridge/bridge-adapters.pi.js +23 -18
- package/dist/bridge/bridge-adapters.shared.js +9 -9
- package/dist/bridge/bridge-session-resume.js +13 -6
- package/dist/bridge/bridge-utils.js +94 -144
- package/dist/bridge/claude-hooks.js +60 -3
- package/dist/bridge/claude-native-controls.js +32 -13
- package/dist/bridge/codex-visible-thread-proxy.js +140 -0
- package/dist/bridge/language-control.js +33 -0
- package/dist/bridge/native-terminal-control.js +38 -10
- package/dist/bridge/opencode-v2-compat.js +458 -0
- package/dist/bridge/wechat-bridge.js +57 -42
- package/dist/channels/channel-driver-factory.js +44 -0
- package/dist/channels/local/local-channel-port.js +40 -0
- package/dist/channels/local/local-driver.js +217 -0
- package/dist/channels/wechat/wechat-driver.js +11 -2
- package/dist/channels/wecom/wecom-driver.js +8 -1
- package/dist/companion/local-companion.js +27 -7
- package/dist/companion/opencode-v2-tui-bridge-plugin.js +93 -0
- package/dist/companion/pi-tui-bridge-extension.js +10 -4
- package/dist/core/bridge-control.js +3 -0
- package/dist/core/bridge-defer.js +3 -2
- package/dist/core/bridge-event-forwarder.js +3 -0
- package/dist/core/channel-types.js +5 -1
- package/dist/daemon/wechat-daemon.js +202 -124
- package/dist/i18n/index.js +25 -11
- package/dist/i18n/messages-en.js +244 -1
- package/dist/i18n/messages-zh.js +244 -1
- package/dist/runtime/legacy-adapter-runtime.js +7 -4
- package/dist/wechat/channel-config.js +3 -2
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
<p align='center'><img src='docs/images/logo.png' width=90%></p>
|
|
4
4
|
|
|
5
|
-
<p align="center"><img alt="Typing SVG" src="https://readme-typing-svg.
|
|
5
|
+
<p align="center"><img alt="Typing SVG" src="https://readme-typing-svg.demolab.com?font=JetBrains+Mono&weight=600&duration=4000&pause=500&color=06C763¢er=true&vCenter=true&width=660&lines=CLI+WeChat+Bridge+%E5%BE%AE%E4%BF%A1%E6%A1%A5%E6%8E%A5;%E7%9C%9F%E6%AD%A3%E7%9A%84%E2%80%9C%E7%BB%88%E7%AB%AF-%E5%BE%AE%E4%BF%A1%E2%80%9D%E5%8E%9F%E7%94%9F%E5%8F%8C%E5%90%91%E4%BA%A4%E4%BA%92"></p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://github.com/UNLINEARITY/CLI-WeChat-Bridge"><img alt="GitHub stars" src="https://img.shields.io/github/stars/UNLINEARITY/CLI-WeChat-Bridge?label=Stars&
|
|
9
|
-
<a href="https://www.npmjs.com/package/cli-wechat-bridge"><img alt="npm version" src="https://img.shields.io/npm/v/cli-wechat-bridge?label=npm&
|
|
10
|
-
<a href="https://www.npmjs.com/package/cli-wechat-bridge"><img alt="npm downloads" src="https://img.shields.io/npm/dt/cli-wechat-bridge?label=Downloads&
|
|
11
|
-
<a href="https://www.npmjs.com/package/@unlinearity/cli-wechat-bridge"><img alt="scoped npm downloads" src="https://img.shields.io/npm/dt/@unlinearity/cli-wechat-bridge?label=Scoped%20downloads&
|
|
8
|
+
<a href="https://github.com/UNLINEARITY/CLI-WeChat-Bridge"><img alt="GitHub stars" src="https://img.shields.io/github/stars/UNLINEARITY/CLI-WeChat-Bridge?label=Stars&style=for-the-badge&logo=github&color=0891b2&labelColor=1c1917"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/cli-wechat-bridge"><img alt="npm version" src="https://img.shields.io/npm/v/cli-wechat-bridge?label=npm&style=for-the-badge&logo=npm&color=cb3837&labelColor=1c1917"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/cli-wechat-bridge"><img alt="npm downloads" src="https://img.shields.io/npm/dt/cli-wechat-bridge?label=Downloads&style=for-the-badge&logo=npm&color=16a34a&labelColor=1c1917"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@unlinearity/cli-wechat-bridge"><img alt="scoped npm downloads" src="https://img.shields.io/npm/dt/@unlinearity/cli-wechat-bridge?label=Scoped%20downloads&style=for-the-badge&logo=npm&color=15803d&labelColor=1c1917"></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/cli-wechat-bridge">
|
|
13
13
|
<img alt="License" src="https://img.shields.io/badge/License-AGPL--3.0-7c3aed?style=for-the-badge&labelColor=1c1917">
|
|
14
14
|
</a>
|
|
15
15
|
<a href="https://github.com/UNLINEARITY/CLI-WeChat-Bridge/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/UNLINEARITY/CLI-WeChat-Bridge/ci.yml?branch=main&label=CI&style=for-the-badge&logo=githubactions&logoColor=white&labelColor=1c1917"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://atomgit.com/UNLINEARITY/CLI-WeChat-Bridge"><img alt="AtomGit G-Star" src="https://atomgit.com/UNLINEARITY/CLI-WeChat-Bridge/star/new_badge.svg"></a><br>
|
|
20
|
+
此项目也在 AtomGit 托管
|
|
21
|
+
</p>
|
|
22
|
+
|
|
18
23
|
**命令行工具的微信与企业微信桥接**:本项目将微信或企业微信消息桥接到本地运行的 [`Codex`](https://github.com/openai/codex)、[`Claude Code`](https://code.claude.com/docs/en/overview)、[`OpenCode`](https://github.com/anomalyco/opencode) 和 [`Pi`](https://github.com/earendil-works/pi),同时把本地输出、审批请求与运行状态同步回对应通道。
|
|
19
24
|
|
|
20
25
|
项目围绕本地工作流设计,重点是保留**本地原生终端体验**:你仍然在本地使用原生 CLI 和高级启动参数,微信或企业微信负责远程输入、结果回流与状态同步。
|
|
@@ -53,10 +58,11 @@
|
|
|
53
58
|
|
|
54
59
|
- [Node.js](https://nodejs.org/en/download) `>= 22.13.0`(建议直接安装官网 LTS 版本)
|
|
55
60
|
- 已安装以下任意一种本地 CLI,并尽量保持最新版本:
|
|
56
|
-
- [Codex](https://github.com/openai/codex)
|
|
61
|
+
- [Codex](https://github.com/openai/codex)(已验证 `0.149.x`–`0.161.x`)
|
|
57
62
|
- [Claude Code](https://code.claude.com/docs/en/overview)
|
|
58
|
-
- [OpenCode](https://github.com/anomalyco/opencode)
|
|
59
|
-
- [Pi](https://github.com/earendil-works/pi)
|
|
63
|
+
- [OpenCode](https://github.com/anomalyco/opencode)(同时支持 `1.18.x` 与 `2.0.x`)
|
|
64
|
+
- [Pi](https://github.com/earendil-works/pi)(通过原生 TUI extension 接入;需要本机可执行 `pi` 命令)
|
|
65
|
+
- Bridge 仅调用安装在 `PATH` 中、可直接执行的独立 CLI;不会发现或调用 ChatGPT.app 等桌面应用内部捆绑的私有可执行文件。
|
|
60
66
|
- 已准备一个远程通道:个人微信使用 `wechat-setup`,企业微信使用 `wecom-setup`
|
|
61
67
|
|
|
62
68
|
### 2. 安装与更新
|
|
@@ -253,7 +259,7 @@ wechat-daemon --adapter claude --profile work
|
|
|
253
259
|
| `opencode` | 已接入 | `wechat-opencode` / `wecom-opencode` 自动启动或复用 OpenCode;支持本地 session 跟随及远程通道 `/new` / `/new-session` |
|
|
254
260
|
| `pi` | 已接入 | `wechat-pi` / `wecom-pi` 启动用户原生 Pi TUI,并通过本地 extension 让远程通道接管同一 session,支持最终回复、停止、新建和恢复 session |
|
|
255
261
|
|
|
256
|
-
Pi 按全权限本地代理运行:bridge 不增加工具审批层,并传入 `--approve`
|
|
262
|
+
Pi 按全权限本地代理运行:bridge 不增加工具审批层,并传入 `--approve` 信任当前项目的配置、扩展与其他本地文件;该参数不是逐项工具审批开关;读写文件和执行命令均使用启动 `wechat-pi` 的本地用户权限。原生 TUI 的主题、快捷键、模型选择和 extension UI 都会保留。`wechat-pi` 本身就是被微信接管的 Pi TUI;不要再启动第二个 Pi 进程同时写入同一个 session 文件。Pi `0.85.0` 及更新版本要求 Node.js `>=22.19.0`,低于该版本时 `wechat-pi` / `wecom-pi` 会在启动前明确提示。
|
|
257
263
|
|
|
258
264
|
### Codex 示例
|
|
259
265
|
|
|
@@ -346,6 +352,7 @@ OpenCode 模式下,微信和企业微信侧都支持 `/new` 或 `/new-session`
|
|
|
346
352
|
| `/pi [prompt]` | daemon | 切换到 Pi;可选 prompt 在切换成功后转发 |
|
|
347
353
|
| `/all <prompt>` | daemon | 将同一段提示词同时下发给全部已启动的 CLI;未启动的列为跳过,任一 CLI 忙碌时整条取消 |
|
|
348
354
|
| `/status` | 直接启动、daemon | 查看 bridge、daemon、适配器和工作区状态 |
|
|
355
|
+
| `/lang`、`/lang zh`、`/lang en` | 直接启动、daemon | 查看或切换中文 / English 桥接提示;立即生效并保存偏好,`/language` 为别名 |
|
|
349
356
|
| `/model`、`/model <编号>` | Codex、Claude Code、OpenCode、Pi | 列出模型并切换当前会话的模型;编号列表 5 分钟内有效 |
|
|
350
357
|
| `/plan`、`/plan on`、`/plan off` | Codex、Claude Code、OpenCode | 开启计划模式,或恢复进入前的模式 / primary agent |
|
|
351
358
|
| `/stop` | 直接启动、daemon | 中断当前任务 |
|
|
@@ -356,14 +363,22 @@ OpenCode 模式下,微信和企业微信侧都支持 `/new` 或 `/new-session`
|
|
|
356
363
|
| `/deny`、`/no` | 直接启动、daemon | 有待审批时拒绝当前权限请求 |
|
|
357
364
|
| `confirm`、`yes` | 有待审批时 | `/confirm` 的文本别名 |
|
|
358
365
|
| `deny`、`no` | 有待审批时 | `/deny` 的文本别名 |
|
|
359
|
-
| `/answer <key>=<value>` | 有待回答问题时 | 提交 Codex、OpenCode
|
|
366
|
+
| `/answer <key>=<value>` | 有待回答问题时 | 提交 Codex、Claude、OpenCode 的结构化回答 |
|
|
360
367
|
| `/daemon-stop` | daemon | 停止当前常驻 daemon |
|
|
361
368
|
| `/pair <code>` | 企业微信首次配置 | 在 `wecom-setup` 提示后完成操作者配对 |
|
|
362
369
|
|
|
370
|
+
语言切换及启动环境变量的优先级见 [界面语言](docs/configuration.md#界面语言)。
|
|
371
|
+
|
|
372
|
+
Claude 的 `AskUserQuestion` 会转发题目、选项说明和文字预览。单题可回复 `/answer 1` 或自定义文本;多题可回复 `/answer 1=Yes;2=2;3=1,2;4=1`,多选值以逗号分隔。答案一次提交全部题目。hook 超时或断开连接后,远程问题会清除,并提示在本地终端作答或使用 `/stop`。升级后需重启 daemon 和可见 Claude 窗口。
|
|
373
|
+
|
|
363
374
|
Claude Code 的模型、计划模式控制需要可见 CLI 处于空闲原生提示符,且没有待审批、本地草稿或打开的弹窗。操作会短暂打开原生选择器并等待确认;控制期间操作本地键盘会取消远程控制。Claude Code 使用选择器的 `s`(仅当前会话)切换模型,需要 CLI 支持此选项;`/plan off` 会恢复进入计划模式前的 permission mode,没有记录时恢复 `default`。
|
|
364
375
|
|
|
365
376
|
OpenCode 的控制不依赖可见 TUI 的输入框、菜单或当前面板。桥接通过 OpenCode server 的会话 API 查询已连接模型、提交模型与 primary agent,并回读 session 确认结果;微信和企业微信消息直接通过 SDK 提交,同时显式携带已生效的 agent、model 和 variant。OpenCode 的 `/plan` 选择 `plan` primary agent;缺少该 agent 时会提示错误,`/plan off` 没有历史记录时恢复 `build` 或首个非 plan primary agent。
|
|
366
377
|
|
|
378
|
+
Codex、Claude Code 和 OpenCode 的审批按到达顺序逐项处理:每次 `/confirm` 或 `/deny` 只处理当前显示的一项,再提示下一项;本地终端已处理的请求会同步移除。子代理的权限请求仍由所属父任务接收,审批不会切换当前会话或改变回复目标。升级后请重启已打开的 CLI 窗口,旧 companion 仍可显示状态,但需要重启才能远程审批。
|
|
379
|
+
|
|
380
|
+
Claude Code 默认在 bridge 生成的启动设置中添加 `WebFetch(domain:*)` 权限规则,允许常规联网请求直接通过;现有拒绝规则和沙箱限制仍生效。设置 `CLI_BRIDGE_STRICT_APPROVAL=1` 可关闭此自动放行;仅有本地网络弹窗、没有 `PermissionRequest` Hook 的请求需要在终端处理。
|
|
381
|
+
|
|
367
382
|
Pi 的 `/model` 通过已连接原生 TUI extension 读取当前 scoped model 列表;没有 scoped models 时使用 model registry 中可用的模型,并通过 `pi.setModel()` 切换。Pi 的 `/plan` 仍由可选扩展定义,不作为 bridge 的通用控制命令。
|
|
368
383
|
|
|
369
384
|
模型列表绑定当前会话、CLI 进程和操作者;切换会话或 daemon 活动 CLI 后请重新发送 `/model`。
|
|
File without changes
|
package/bin/wechat-claude.mjs
CHANGED
|
File without changes
|
package/bin/wechat-codex.mjs
CHANGED
|
File without changes
|
package/bin/wechat-daemon.mjs
CHANGED
|
File without changes
|
package/bin/wechat-opencode.mjs
CHANGED
|
File without changes
|
package/bin/wechat-pi.mjs
CHANGED
|
File without changes
|
package/bin/wechat-setup.mjs
CHANGED
|
File without changes
|
|
File without changes
|
package/bin/wecom-claude.mjs
CHANGED
|
File without changes
|
package/bin/wecom-codex.mjs
CHANGED
|
File without changes
|
package/bin/wecom-daemon.mjs
CHANGED
|
File without changes
|
package/bin/wecom-opencode.mjs
CHANGED
|
File without changes
|
package/bin/wecom-pi.mjs
CHANGED
|
File without changes
|
package/bin/wecom-setup.mjs
CHANGED
|
File without changes
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
|
|
3
|
+
// Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
|
|
4
|
+
// This file is part of CLI-WeChat-Bridge. Modifications and derivative works
|
|
5
|
+
// must be released under AGPL-3.0-or-later with full source code; see
|
|
6
|
+
// LICENSE.txt. Network services built on it must offer source to users.
|
|
7
|
+
import { t } from "../i18n/index.js";
|
|
1
8
|
const snapshots = new WeakMap();
|
|
2
9
|
export const MODEL_SNAPSHOT_TTL_MS = 5 * 60_000;
|
|
3
10
|
export function invalidateModelSnapshot(adapter) {
|
|
@@ -17,40 +24,40 @@ export async function handleAdapterControl(adapter, senderId, command) {
|
|
|
17
24
|
try {
|
|
18
25
|
if (command.type === "plan") {
|
|
19
26
|
if (!adapter.setPlanMode)
|
|
20
|
-
throw new Error(
|
|
27
|
+
throw new Error(t("control.plan.unavailable", { name }));
|
|
21
28
|
const enabled = await adapter.setPlanMode(command.enabled);
|
|
22
29
|
return enabled
|
|
23
|
-
?
|
|
24
|
-
:
|
|
30
|
+
? t("control.plan.enabled", { name })
|
|
31
|
+
: t("control.plan.disabled", { name });
|
|
25
32
|
}
|
|
26
33
|
if (!adapter.listModels || !adapter.selectModel)
|
|
27
|
-
throw new Error(
|
|
34
|
+
throw new Error(t("control.model.unavailable", { name }));
|
|
28
35
|
const owner = snapshotOwner(adapter, senderId);
|
|
29
36
|
if (!command.target) {
|
|
30
37
|
snapshots.delete(adapter);
|
|
31
38
|
const models = await adapter.listModels();
|
|
32
39
|
if (snapshotOwner(adapter, senderId) !== owner)
|
|
33
|
-
throw new Error("
|
|
40
|
+
throw new Error(t("control.model.sessionChanged"));
|
|
34
41
|
if (!models.length)
|
|
35
|
-
return
|
|
42
|
+
return t("control.model.empty", { name });
|
|
36
43
|
snapshots.set(adapter, { owner, createdAtMs: Date.now(), models });
|
|
37
44
|
return [
|
|
38
|
-
|
|
39
|
-
...models.map((model, index) => `${index + 1}. ${model.displayName}${model.isCurrent ? "
|
|
40
|
-
"", "
|
|
45
|
+
t("control.model.header", { name }), "",
|
|
46
|
+
...models.map((model, index) => `${index + 1}. ${model.displayName}${model.isCurrent ? t("control.model.current") : ""}`),
|
|
47
|
+
"", t("control.model.instructions"),
|
|
41
48
|
].join("\n");
|
|
42
49
|
}
|
|
43
50
|
const snapshot = snapshots.get(adapter);
|
|
44
51
|
if (!snapshot || snapshot.owner !== owner || Date.now() - snapshot.createdAtMs > MODEL_SNAPSHOT_TTL_MS) {
|
|
45
|
-
throw new Error("
|
|
52
|
+
throw new Error(t("control.model.expired"));
|
|
46
53
|
}
|
|
47
54
|
const index = Number(command.target);
|
|
48
55
|
if (!/^\d+$/.test(command.target) || !Number.isSafeInteger(index) || index < 1 || index > snapshot.models.length) {
|
|
49
|
-
throw new Error("
|
|
56
|
+
throw new Error(t("control.model.range"));
|
|
50
57
|
}
|
|
51
58
|
const model = await adapter.selectModel(snapshot.models[index - 1].id);
|
|
52
59
|
snapshots.delete(adapter);
|
|
53
|
-
return
|
|
60
|
+
return t("control.model.changed", { name, model: model.displayName });
|
|
54
61
|
}
|
|
55
62
|
catch (error) {
|
|
56
63
|
return error instanceof Error ? error.message : String(error);
|