mvibe 0.1.0__tar.gz
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.
- mvibe-0.1.0/LICENSE +21 -0
- mvibe-0.1.0/PKG-INFO +226 -0
- mvibe-0.1.0/README.md +198 -0
- mvibe-0.1.0/commands/mvibe-off.md +10 -0
- mvibe-0.1.0/commands/mvibe-on.md +9 -0
- mvibe-0.1.0/commands/mvibe-status.md +7 -0
- mvibe-0.1.0/mvibe/__init__.py +8 -0
- mvibe-0.1.0/mvibe/_tls.py +17 -0
- mvibe-0.1.0/mvibe/bridge.py +242 -0
- mvibe-0.1.0/mvibe/cli.py +197 -0
- mvibe-0.1.0/mvibe/config.py +161 -0
- mvibe-0.1.0/mvibe/ilink/__init__.py +5 -0
- mvibe-0.1.0/mvibe/ilink/wechat_api.py +414 -0
- mvibe-0.1.0/mvibe/ilink/wechat_auth.py +407 -0
- mvibe-0.1.0/mvibe/paths.py +124 -0
- mvibe-0.1.0/mvibe/prompt_detect.py +242 -0
- mvibe-0.1.0/mvibe/prompt_rules.json +38 -0
- mvibe-0.1.0/mvibe/tailer.py +100 -0
- mvibe-0.1.0/mvibe/wechat_in.py +94 -0
- mvibe-0.1.0/mvibe/wechat_login.py +87 -0
- mvibe-0.1.0/mvibe/wechat_out.py +65 -0
- mvibe-0.1.0/mvibe/wrapper.py +220 -0
- mvibe-0.1.0/pyproject.toml +48 -0
- mvibe-0.1.0/scripts/smoke.sh +74 -0
- mvibe-0.1.0/uv.lock +649 -0
mvibe-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yong Wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mvibe-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mvibe
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mirror a local Claude Code TUI to a remote chat (WeChat). Local stays identical; remote takes over I/O via a flag file.
|
|
5
|
+
Project-URL: Homepage, https://github.com/wvalianty/mvibe
|
|
6
|
+
Project-URL: Repository, https://github.com/wvalianty/mvibe
|
|
7
|
+
Project-URL: Issues, https://github.com/wvalianty/mvibe/issues
|
|
8
|
+
Author-email: Yong Wang <yong.wang@cobo.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude,claude-code,mirror,pty,remote,tui,wechat
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Topic :: Terminals
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: aiohttp>=3.9
|
|
25
|
+
Requires-Dist: pyte>=0.8.2
|
|
26
|
+
Requires-Dist: segno>=1.6
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# mvibe
|
|
30
|
+
|
|
31
|
+
把本地 **Claude Code** TUI 镜像到远程聊天(微信)。本地终端与直接运行 `claude`
|
|
32
|
+
逐字节一致;两个开关(输入路由 `flag` + 输出/入站总闸 `gate`)决定远程接管程度。
|
|
33
|
+
**全程只有一个 `claude` 进程**——切换只是 I/O 路由,不重启、不交接会话。
|
|
34
|
+
|
|
35
|
+
## 工作原理
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌──────────── mvibe run(持 PTY master)────────────┐
|
|
39
|
+
终端 ────┤ stdin → PTY (local 路由;一敲键即夺回) │
|
|
40
|
+
←───┤ PTY → stdout (始终;远程驱动时也能围观) │──► claude TUI
|
|
41
|
+
│ inject FIFO → PTY(remote 路由时) │ (单进程)
|
|
42
|
+
└───────────────────────────────────────────────────┘ │ 写入
|
|
43
|
+
.jsonl ◄─────────┘
|
|
44
|
+
微信 ──long-poll 入站──► inject FIFO + flag=remote │
|
|
45
|
+
◄── 推送(iLink) ──── tail transcript .jsonl(assistant text)◄┘
|
|
46
|
+
[mvibe bridge](出站受 remote gate 控制,无网络监听)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **本地保持真 TUI**(富渲染、slash 命令、plan mode):PTY 透明透传,即
|
|
50
|
+
`script(1)` / `asciinema` 那套。
|
|
51
|
+
- **远程输出 ≠ 裸 ANSI**:从会话 transcript(`~/.claude/projects/<cwd>/<id>.jsonl`)
|
|
52
|
+
读,而非镜像 PTY 字节——TUI 的 ANSI 流在聊天框里是乱码。
|
|
53
|
+
- **远程输入**:当作键盘注入 PTY,驱动真 TUI。
|
|
54
|
+
|
|
55
|
+
### 两个开关,别搞混
|
|
56
|
+
|
|
57
|
+
- **`flag`(`~/.mvibe/active`,local|remote)= 输入路由**:谁的键盘进 claude。
|
|
58
|
+
入站消息置 `remote`;**本地敲任意键立即翻回 `local` 并夺回控制**(human 优先,
|
|
59
|
+
永不锁死)。这是输入端互斥,杜绝两人同敲。
|
|
60
|
+
- **`gate`(remote on/off)= 输出镜像 + 入站总闸**:决定是否把 claude 回复推手机、
|
|
61
|
+
是否接受手机消息。`/mvibe-on` 开、`/mvibe-off` 关。
|
|
62
|
+
- 二者解耦:所以你本地夺回打字(flag=local)时,回复仍会推手机(gate 仍 on);
|
|
63
|
+
要彻底断开手机,`/mvibe-off`。
|
|
64
|
+
|
|
65
|
+
## 安装
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv tool install --editable /path/to/mvibe # 把 mvibe 装上全局 PATH
|
|
69
|
+
cp /path/to/mvibe/commands/mvibe-*.md ~/.claude/commands/ # 装会话内 slash 开关
|
|
70
|
+
mvibe login # 一次性:扫码绑定微信 bot
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
依赖仅 `aiohttp` + `segno`(PyPI),**零 avibe 依赖**。
|
|
74
|
+
|
|
75
|
+
## 用法(单终端,推荐)
|
|
76
|
+
|
|
77
|
+
进到想用 claude 的目录,直接 `mvibe`——**裸 `mvibe` 等价于在当前目录 `mvibe up`**:
|
|
78
|
+
同一终端前台跑 claude TUI,后台跑 bridge(镜像输出 + 微信入站轮询)。桥日志写
|
|
79
|
+
`~/.mvibe/bridge.log`,不污染界面。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cd /path/to/project
|
|
83
|
+
mvibe # = mvibe up,cwd 即当前目录
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
等价写法:`mvibe up`(同上)、`mvibe up --cwd /other/dir`(指定目录)。
|
|
87
|
+
|
|
88
|
+
**传 claude 参数**:`--` 前是 mvibe 参数,后是 claude 参数(整条当命令运行):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mvibe -- claude --resume 873fd8af-... # 续接指定会话
|
|
92
|
+
mvibe -- claude -c # 续接最近会话
|
|
93
|
+
mvibe up --cwd /proj -- claude --model opus
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`--` 后写程序名即可换后端(如 `mvibe -- codex ...`)。注意:显式 `-- <cmd>` 时
|
|
97
|
+
mvibe 不再自动加 claude 默认参数(`--yolo` 等被忽略,全由你控制)。
|
|
98
|
+
|
|
99
|
+
手机端:先给 bot 发一条消息建立会话,之后你发的内容驱动这个 Claude TUI,
|
|
100
|
+
Claude 的回复以聊天消息返回。退出 claude(`/exit`)即整体结束。
|
|
101
|
+
|
|
102
|
+
### 会话内开关(Claude slash 命令)
|
|
103
|
+
|
|
104
|
+
把 `commands/mvibe-*.md` 拷到 `~/.claude/commands/`,即可在任意会话里:
|
|
105
|
+
|
|
106
|
+
| 命令 | 作用 |
|
|
107
|
+
|------|------|
|
|
108
|
+
| `/mvibe-on` | 开启远程接管(微信驱动会话) |
|
|
109
|
+
| `/mvibe-off` | 关闭远程接管(忽略微信,保持本地) |
|
|
110
|
+
| `/mvibe-status` | 查看 remote 开关 / 路由 flag / transcript |
|
|
111
|
+
|
|
112
|
+
`mvibe up` 默认 remote=on(`--no-remote` 可改)。
|
|
113
|
+
|
|
114
|
+
**夺回控制**:远程接管时本地键盘被让出,但**只要你在本地敲任意键,立即夺回
|
|
115
|
+
本地控制**(human 在场优先)——所以你永远不会被锁死,可随时打字。想彻底不让
|
|
116
|
+
手机再抢,敲 `/mvibe-off`(关闭 gate)。下次手机消息会再次接管,除非 gate 关着。
|
|
117
|
+
|
|
118
|
+
### 两终端模式(可选)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
mvibe run --cwd . # 终端1:claude TUI(必须先起)
|
|
122
|
+
mvibe bridge --cwd . # 终端2:镜像 + 入站
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 直接命令
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
mvibe remote on|off|status # 远程接管开关
|
|
129
|
+
mvibe flag remote|local # 仅切路由
|
|
130
|
+
mvibe send "继续" # 注入一行到当前会话
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
无网络监听:微信入站是 long-poll(出站连接),本地控制全走 CLI + 文件。
|
|
134
|
+
|
|
135
|
+
## 微信:独立 bot
|
|
136
|
+
|
|
137
|
+
mvibe 端到端自持微信 bot——**零 avibe 依赖**、无需 service、无需公网 webhook:
|
|
138
|
+
|
|
139
|
+
- **绑定**(`mvibe login`,`wechat_login.py`):跑官方 iLink 扫码流程,把
|
|
140
|
+
`bot_token` / `base_url` / `user_id` 写入 `~/.mvibe/config.json`。
|
|
141
|
+
- **入站**(`wechat_in.py`):iLink `getUpdates` 长轮询,无需 webhook/公网。
|
|
142
|
+
每条消息产出 `(text, user_id)`,置 flag 为 `remote` 并把文本注入 PTY。同步游标
|
|
143
|
+
与每用户 `context_token` 持久化在 `~/.mvibe/state/`。
|
|
144
|
+
- **出站**(`wechat_out.py`):tailer 读到的 assistant 文本经 `send_message`
|
|
145
|
+
推手机,用记住的 `context_token` 寻址。**只在 remote gate 开启时推送**(跟 gate
|
|
146
|
+
走,不跟 flag),所以本地按键夺回输入不会吞掉回复。失败写 `~/.mvibe/bridge.log`
|
|
147
|
+
(如 `errcode -14` 会话过期,需手机再发条消息刷新)。
|
|
148
|
+
|
|
149
|
+
iLink 协议代码 vendored 在 `mvibe/ilink/`(`wechat_api.py` / `wechat_auth.py`,
|
|
150
|
+
源自 avibe,仅依赖 `aiohttp` + stdlib)。mvibe 不在运行时引用任何 avibe 安装。
|
|
151
|
+
|
|
152
|
+
> 一个 bot_token 上不要同时跑两个 `getUpdates` 轮询(会争同步游标)。mvibe 有自己
|
|
153
|
+
> 的登录/bot,与任何 avibe 实例互不干扰。
|
|
154
|
+
|
|
155
|
+
## 安全
|
|
156
|
+
|
|
157
|
+
远程聊天面拥有**完整 agent 权限**(Claude 可跑 bash/工具)。务必收紧入口:
|
|
158
|
+
|
|
159
|
+
- **入站鉴权(默认安全)**:`mvibe login` 会写入 `wechat.user_id`,此后只有该
|
|
160
|
+
用户能驱动会话;其余被丢弃。多人放行用 `wechat.allowed_users: [...]`。若两者
|
|
161
|
+
皆空,则放行所有人并打印告警——别在这种状态下长期运行。
|
|
162
|
+
- **无网络监听**:不开任何端口,没有可被本机其他进程访问的注入入口;入站只来自
|
|
163
|
+
已鉴权的微信 bot。
|
|
164
|
+
- **凭证文件权限**:`~/.mvibe` 为 `0700`,`config.json` / `context_tokens` /
|
|
165
|
+
`sync_buf` 为 `0600`(仅本人可读)。
|
|
166
|
+
- **注入消毒**:远程文本注入 PTY 前剥除 C0 控制符(含 ESC),防终端转义/控制键
|
|
167
|
+
注入;制表与换行保留。
|
|
168
|
+
- **TLS**:若存在 `~/tmp/cacert.pem`,会经 `SSL_CERT_FILE` 启用该 CA bundle
|
|
169
|
+
(Cloudflare WARP 拦截环境所需)——这会扩大信任根,按需保留。
|
|
170
|
+
|
|
171
|
+
## 交互确认转发(抓屏)
|
|
172
|
+
|
|
173
|
+
claude 的确认框(「是否允许 Web Search?1/2/3」「(y/n)」)是 **TUI 界面元素**,不进
|
|
174
|
+
transcript。mvibe 用**抓屏**把它转发到手机:
|
|
175
|
+
|
|
176
|
+
- `mvibe up` 用 pyte 终端模拟器维护「渲染后的屏幕」,喂入 PTY 输出(`prompt_detect.py`)。
|
|
177
|
+
- 屏幕稳定(停 ~300ms)且匹配「确认框形态」(编号选项 + 光标 ❯,或 `(y/n)`)时,
|
|
178
|
+
把框文本发手机:`⚠️ 需要确认:… 回复数字 或 yes/no`。
|
|
179
|
+
- 手机回 `1`/`2`/`yes`/`no` → 映射成按键注入 TUI(`yes`→含 "Yes" 那项,`no`→"No"/取消)。
|
|
180
|
+
- **remote gate 开启时就转发**(与出站镜像同规则,不看 flag);手机或本地终端都能答。
|
|
181
|
+
久不回则框留着。不想被转发就 `/mvibe-off`。
|
|
182
|
+
|
|
183
|
+
**后端无关**:机制(pyte 渲染 + 按键注入)跟谁是后端无关;只有「确认框形态」识别规则
|
|
184
|
+
按后端配(现内置 claude 形态)。换 codex 等只需在 `prompt_detect.py` 加一条规则。
|
|
185
|
+
|
|
186
|
+
限制 & 选项:
|
|
187
|
+
|
|
188
|
+
- 仅 `mvibe up`(单进程)生效——PTY 字节只有 wrapper 进程有;两终端 `bridge` 看不到。
|
|
189
|
+
- 完全不想要确认(自动跑):`mvibe up --yolo`(给 claude 加 `--dangerously-skip-permissions`)。
|
|
190
|
+
- 检测靠 UI 形态匹配,claude 改版可能要调 `prompt_detect.py` 的规则。
|
|
191
|
+
- 远程能让 claude 跑任何工具——**靠入站 allowlist 限制是谁**(见安全)。
|
|
192
|
+
|
|
193
|
+
## 注意事项
|
|
194
|
+
|
|
195
|
+
- iLink 仅允许在手机最近一次给 bot 发消息后的窗口内推送(`context_token`);
|
|
196
|
+
长时间空闲后推送可能报 errcode -14。
|
|
197
|
+
- 键盘注入以 CR 提交;多行文本可能提前断行提交。
|
|
198
|
+
- 两端必须用**相同 cwd**(transcript 目录按 cwd 编码)。
|
|
199
|
+
|
|
200
|
+
## 项目结构
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
mvibe/
|
|
204
|
+
cli.py 命令行(argparse):默认/up/run/bridge/login/remote/send/flag/status
|
|
205
|
+
wrapper.py PTY mux 核心:本地透传 + flag 路由 + 本地按键夺回 + 注入消毒
|
|
206
|
+
bridge.py 后台:transcript→微信镜像 + 微信入站 poll(无网络监听)
|
|
207
|
+
tailer.py follow transcript .jsonl,抽 assistant text
|
|
208
|
+
prompt_detect.py pyte 抓屏:检测确认框 → 回调(确认转发用)
|
|
209
|
+
wechat_in.py iLink getUpdates 长轮询入站
|
|
210
|
+
wechat_out.py iLink send_message 出站
|
|
211
|
+
wechat_login.py QR 扫码绑定
|
|
212
|
+
config.py ~/.mvibe 凭证/tokens/sync_buf(0600)
|
|
213
|
+
paths.py 路径布局 + flag/gate + cwd→transcript 编码
|
|
214
|
+
_tls.py 可选 CA bundle
|
|
215
|
+
ilink/ vendored iLink 协议(wechat_api.py / wechat_auth.py,仅 aiohttp)
|
|
216
|
+
commands/ Claude slash 命令模板(/mvibe-on /off /status)
|
|
217
|
+
scripts/smoke.sh 本地自测(无微信、隔离 HOME)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## 自测
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
bash scripts/smoke.sh # 9 项:注入/flag 互斥/消毒/HTTP/token/权限/tailer
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
不碰真 `~/.mvibe`(用隔离 `MVIBE_HOME`),无网络无微信,安全可随时跑。
|
mvibe-0.1.0/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# mvibe
|
|
2
|
+
|
|
3
|
+
把本地 **Claude Code** TUI 镜像到远程聊天(微信)。本地终端与直接运行 `claude`
|
|
4
|
+
逐字节一致;两个开关(输入路由 `flag` + 输出/入站总闸 `gate`)决定远程接管程度。
|
|
5
|
+
**全程只有一个 `claude` 进程**——切换只是 I/O 路由,不重启、不交接会话。
|
|
6
|
+
|
|
7
|
+
## 工作原理
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌──────────── mvibe run(持 PTY master)────────────┐
|
|
11
|
+
终端 ────┤ stdin → PTY (local 路由;一敲键即夺回) │
|
|
12
|
+
←───┤ PTY → stdout (始终;远程驱动时也能围观) │──► claude TUI
|
|
13
|
+
│ inject FIFO → PTY(remote 路由时) │ (单进程)
|
|
14
|
+
└───────────────────────────────────────────────────┘ │ 写入
|
|
15
|
+
.jsonl ◄─────────┘
|
|
16
|
+
微信 ──long-poll 入站──► inject FIFO + flag=remote │
|
|
17
|
+
◄── 推送(iLink) ──── tail transcript .jsonl(assistant text)◄┘
|
|
18
|
+
[mvibe bridge](出站受 remote gate 控制,无网络监听)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **本地保持真 TUI**(富渲染、slash 命令、plan mode):PTY 透明透传,即
|
|
22
|
+
`script(1)` / `asciinema` 那套。
|
|
23
|
+
- **远程输出 ≠ 裸 ANSI**:从会话 transcript(`~/.claude/projects/<cwd>/<id>.jsonl`)
|
|
24
|
+
读,而非镜像 PTY 字节——TUI 的 ANSI 流在聊天框里是乱码。
|
|
25
|
+
- **远程输入**:当作键盘注入 PTY,驱动真 TUI。
|
|
26
|
+
|
|
27
|
+
### 两个开关,别搞混
|
|
28
|
+
|
|
29
|
+
- **`flag`(`~/.mvibe/active`,local|remote)= 输入路由**:谁的键盘进 claude。
|
|
30
|
+
入站消息置 `remote`;**本地敲任意键立即翻回 `local` 并夺回控制**(human 优先,
|
|
31
|
+
永不锁死)。这是输入端互斥,杜绝两人同敲。
|
|
32
|
+
- **`gate`(remote on/off)= 输出镜像 + 入站总闸**:决定是否把 claude 回复推手机、
|
|
33
|
+
是否接受手机消息。`/mvibe-on` 开、`/mvibe-off` 关。
|
|
34
|
+
- 二者解耦:所以你本地夺回打字(flag=local)时,回复仍会推手机(gate 仍 on);
|
|
35
|
+
要彻底断开手机,`/mvibe-off`。
|
|
36
|
+
|
|
37
|
+
## 安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv tool install --editable /path/to/mvibe # 把 mvibe 装上全局 PATH
|
|
41
|
+
cp /path/to/mvibe/commands/mvibe-*.md ~/.claude/commands/ # 装会话内 slash 开关
|
|
42
|
+
mvibe login # 一次性:扫码绑定微信 bot
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
依赖仅 `aiohttp` + `segno`(PyPI),**零 avibe 依赖**。
|
|
46
|
+
|
|
47
|
+
## 用法(单终端,推荐)
|
|
48
|
+
|
|
49
|
+
进到想用 claude 的目录,直接 `mvibe`——**裸 `mvibe` 等价于在当前目录 `mvibe up`**:
|
|
50
|
+
同一终端前台跑 claude TUI,后台跑 bridge(镜像输出 + 微信入站轮询)。桥日志写
|
|
51
|
+
`~/.mvibe/bridge.log`,不污染界面。
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd /path/to/project
|
|
55
|
+
mvibe # = mvibe up,cwd 即当前目录
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
等价写法:`mvibe up`(同上)、`mvibe up --cwd /other/dir`(指定目录)。
|
|
59
|
+
|
|
60
|
+
**传 claude 参数**:`--` 前是 mvibe 参数,后是 claude 参数(整条当命令运行):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
mvibe -- claude --resume 873fd8af-... # 续接指定会话
|
|
64
|
+
mvibe -- claude -c # 续接最近会话
|
|
65
|
+
mvibe up --cwd /proj -- claude --model opus
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--` 后写程序名即可换后端(如 `mvibe -- codex ...`)。注意:显式 `-- <cmd>` 时
|
|
69
|
+
mvibe 不再自动加 claude 默认参数(`--yolo` 等被忽略,全由你控制)。
|
|
70
|
+
|
|
71
|
+
手机端:先给 bot 发一条消息建立会话,之后你发的内容驱动这个 Claude TUI,
|
|
72
|
+
Claude 的回复以聊天消息返回。退出 claude(`/exit`)即整体结束。
|
|
73
|
+
|
|
74
|
+
### 会话内开关(Claude slash 命令)
|
|
75
|
+
|
|
76
|
+
把 `commands/mvibe-*.md` 拷到 `~/.claude/commands/`,即可在任意会话里:
|
|
77
|
+
|
|
78
|
+
| 命令 | 作用 |
|
|
79
|
+
|------|------|
|
|
80
|
+
| `/mvibe-on` | 开启远程接管(微信驱动会话) |
|
|
81
|
+
| `/mvibe-off` | 关闭远程接管(忽略微信,保持本地) |
|
|
82
|
+
| `/mvibe-status` | 查看 remote 开关 / 路由 flag / transcript |
|
|
83
|
+
|
|
84
|
+
`mvibe up` 默认 remote=on(`--no-remote` 可改)。
|
|
85
|
+
|
|
86
|
+
**夺回控制**:远程接管时本地键盘被让出,但**只要你在本地敲任意键,立即夺回
|
|
87
|
+
本地控制**(human 在场优先)——所以你永远不会被锁死,可随时打字。想彻底不让
|
|
88
|
+
手机再抢,敲 `/mvibe-off`(关闭 gate)。下次手机消息会再次接管,除非 gate 关着。
|
|
89
|
+
|
|
90
|
+
### 两终端模式(可选)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
mvibe run --cwd . # 终端1:claude TUI(必须先起)
|
|
94
|
+
mvibe bridge --cwd . # 终端2:镜像 + 入站
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 直接命令
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
mvibe remote on|off|status # 远程接管开关
|
|
101
|
+
mvibe flag remote|local # 仅切路由
|
|
102
|
+
mvibe send "继续" # 注入一行到当前会话
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
无网络监听:微信入站是 long-poll(出站连接),本地控制全走 CLI + 文件。
|
|
106
|
+
|
|
107
|
+
## 微信:独立 bot
|
|
108
|
+
|
|
109
|
+
mvibe 端到端自持微信 bot——**零 avibe 依赖**、无需 service、无需公网 webhook:
|
|
110
|
+
|
|
111
|
+
- **绑定**(`mvibe login`,`wechat_login.py`):跑官方 iLink 扫码流程,把
|
|
112
|
+
`bot_token` / `base_url` / `user_id` 写入 `~/.mvibe/config.json`。
|
|
113
|
+
- **入站**(`wechat_in.py`):iLink `getUpdates` 长轮询,无需 webhook/公网。
|
|
114
|
+
每条消息产出 `(text, user_id)`,置 flag 为 `remote` 并把文本注入 PTY。同步游标
|
|
115
|
+
与每用户 `context_token` 持久化在 `~/.mvibe/state/`。
|
|
116
|
+
- **出站**(`wechat_out.py`):tailer 读到的 assistant 文本经 `send_message`
|
|
117
|
+
推手机,用记住的 `context_token` 寻址。**只在 remote gate 开启时推送**(跟 gate
|
|
118
|
+
走,不跟 flag),所以本地按键夺回输入不会吞掉回复。失败写 `~/.mvibe/bridge.log`
|
|
119
|
+
(如 `errcode -14` 会话过期,需手机再发条消息刷新)。
|
|
120
|
+
|
|
121
|
+
iLink 协议代码 vendored 在 `mvibe/ilink/`(`wechat_api.py` / `wechat_auth.py`,
|
|
122
|
+
源自 avibe,仅依赖 `aiohttp` + stdlib)。mvibe 不在运行时引用任何 avibe 安装。
|
|
123
|
+
|
|
124
|
+
> 一个 bot_token 上不要同时跑两个 `getUpdates` 轮询(会争同步游标)。mvibe 有自己
|
|
125
|
+
> 的登录/bot,与任何 avibe 实例互不干扰。
|
|
126
|
+
|
|
127
|
+
## 安全
|
|
128
|
+
|
|
129
|
+
远程聊天面拥有**完整 agent 权限**(Claude 可跑 bash/工具)。务必收紧入口:
|
|
130
|
+
|
|
131
|
+
- **入站鉴权(默认安全)**:`mvibe login` 会写入 `wechat.user_id`,此后只有该
|
|
132
|
+
用户能驱动会话;其余被丢弃。多人放行用 `wechat.allowed_users: [...]`。若两者
|
|
133
|
+
皆空,则放行所有人并打印告警——别在这种状态下长期运行。
|
|
134
|
+
- **无网络监听**:不开任何端口,没有可被本机其他进程访问的注入入口;入站只来自
|
|
135
|
+
已鉴权的微信 bot。
|
|
136
|
+
- **凭证文件权限**:`~/.mvibe` 为 `0700`,`config.json` / `context_tokens` /
|
|
137
|
+
`sync_buf` 为 `0600`(仅本人可读)。
|
|
138
|
+
- **注入消毒**:远程文本注入 PTY 前剥除 C0 控制符(含 ESC),防终端转义/控制键
|
|
139
|
+
注入;制表与换行保留。
|
|
140
|
+
- **TLS**:若存在 `~/tmp/cacert.pem`,会经 `SSL_CERT_FILE` 启用该 CA bundle
|
|
141
|
+
(Cloudflare WARP 拦截环境所需)——这会扩大信任根,按需保留。
|
|
142
|
+
|
|
143
|
+
## 交互确认转发(抓屏)
|
|
144
|
+
|
|
145
|
+
claude 的确认框(「是否允许 Web Search?1/2/3」「(y/n)」)是 **TUI 界面元素**,不进
|
|
146
|
+
transcript。mvibe 用**抓屏**把它转发到手机:
|
|
147
|
+
|
|
148
|
+
- `mvibe up` 用 pyte 终端模拟器维护「渲染后的屏幕」,喂入 PTY 输出(`prompt_detect.py`)。
|
|
149
|
+
- 屏幕稳定(停 ~300ms)且匹配「确认框形态」(编号选项 + 光标 ❯,或 `(y/n)`)时,
|
|
150
|
+
把框文本发手机:`⚠️ 需要确认:… 回复数字 或 yes/no`。
|
|
151
|
+
- 手机回 `1`/`2`/`yes`/`no` → 映射成按键注入 TUI(`yes`→含 "Yes" 那项,`no`→"No"/取消)。
|
|
152
|
+
- **remote gate 开启时就转发**(与出站镜像同规则,不看 flag);手机或本地终端都能答。
|
|
153
|
+
久不回则框留着。不想被转发就 `/mvibe-off`。
|
|
154
|
+
|
|
155
|
+
**后端无关**:机制(pyte 渲染 + 按键注入)跟谁是后端无关;只有「确认框形态」识别规则
|
|
156
|
+
按后端配(现内置 claude 形态)。换 codex 等只需在 `prompt_detect.py` 加一条规则。
|
|
157
|
+
|
|
158
|
+
限制 & 选项:
|
|
159
|
+
|
|
160
|
+
- 仅 `mvibe up`(单进程)生效——PTY 字节只有 wrapper 进程有;两终端 `bridge` 看不到。
|
|
161
|
+
- 完全不想要确认(自动跑):`mvibe up --yolo`(给 claude 加 `--dangerously-skip-permissions`)。
|
|
162
|
+
- 检测靠 UI 形态匹配,claude 改版可能要调 `prompt_detect.py` 的规则。
|
|
163
|
+
- 远程能让 claude 跑任何工具——**靠入站 allowlist 限制是谁**(见安全)。
|
|
164
|
+
|
|
165
|
+
## 注意事项
|
|
166
|
+
|
|
167
|
+
- iLink 仅允许在手机最近一次给 bot 发消息后的窗口内推送(`context_token`);
|
|
168
|
+
长时间空闲后推送可能报 errcode -14。
|
|
169
|
+
- 键盘注入以 CR 提交;多行文本可能提前断行提交。
|
|
170
|
+
- 两端必须用**相同 cwd**(transcript 目录按 cwd 编码)。
|
|
171
|
+
|
|
172
|
+
## 项目结构
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
mvibe/
|
|
176
|
+
cli.py 命令行(argparse):默认/up/run/bridge/login/remote/send/flag/status
|
|
177
|
+
wrapper.py PTY mux 核心:本地透传 + flag 路由 + 本地按键夺回 + 注入消毒
|
|
178
|
+
bridge.py 后台:transcript→微信镜像 + 微信入站 poll(无网络监听)
|
|
179
|
+
tailer.py follow transcript .jsonl,抽 assistant text
|
|
180
|
+
prompt_detect.py pyte 抓屏:检测确认框 → 回调(确认转发用)
|
|
181
|
+
wechat_in.py iLink getUpdates 长轮询入站
|
|
182
|
+
wechat_out.py iLink send_message 出站
|
|
183
|
+
wechat_login.py QR 扫码绑定
|
|
184
|
+
config.py ~/.mvibe 凭证/tokens/sync_buf(0600)
|
|
185
|
+
paths.py 路径布局 + flag/gate + cwd→transcript 编码
|
|
186
|
+
_tls.py 可选 CA bundle
|
|
187
|
+
ilink/ vendored iLink 协议(wechat_api.py / wechat_auth.py,仅 aiohttp)
|
|
188
|
+
commands/ Claude slash 命令模板(/mvibe-on /off /status)
|
|
189
|
+
scripts/smoke.sh 本地自测(无微信、隔离 HOME)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 自测
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
bash scripts/smoke.sh # 9 项:注入/flag 互斥/消毒/HTTP/token/权限/tailer
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
不碰真 `~/.mvibe`(用隔离 `MVIBE_HOME`),无网络无微信,安全可随时跑。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Turn OFF mvibe remote takeover; keep control local
|
|
3
|
+
disable-model-invocation: true
|
|
4
|
+
allowed-tools: Bash(mvibe remote off)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
!`mvibe remote off`
|
|
8
|
+
|
|
9
|
+
mvibe remote takeover is now **OFF** — incoming WeChat messages are ignored; the
|
|
10
|
+
terminal keeps control.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""mvibe — mirror a local Claude Code TUI to a remote chat surface.
|
|
2
|
+
|
|
3
|
+
Single live `claude` process. A PTY wrapper keeps the local terminal identical
|
|
4
|
+
to running `claude` directly, while a flag file toggles whether remote (WeChat)
|
|
5
|
+
input is injected and remote output is mirrored. No process restart on switch.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""TLS CA setup for environments that intercept HTTPS (e.g. Cloudflare WARP).
|
|
2
|
+
|
|
3
|
+
If ~/tmp/cacert.pem exists, point SSL at it. Optional; no-op otherwise.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
_CA_BUNDLE = Path.home() / "tmp/cacert.pem"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def setup_tls_ca() -> None:
|
|
15
|
+
if _CA_BUNDLE.is_file():
|
|
16
|
+
os.environ.setdefault("SSL_CERT_FILE", str(_CA_BUNDLE))
|
|
17
|
+
os.environ.setdefault("REQUESTS_CA_BUNDLE", str(_CA_BUNDLE))
|