@foxden-app/foxclaw 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/.env.example +36 -0
  2. package/LICENSE +22 -0
  3. package/README.md +244 -0
  4. package/README_EN.md +244 -0
  5. package/dist/channels/bridge_messaging_router.d.ts +27 -0
  6. package/dist/channels/bridge_messaging_router.js +85 -0
  7. package/dist/channels/telegram/telegram_channel_adapter.d.ts +12 -0
  8. package/dist/channels/telegram/telegram_channel_adapter.js +21 -0
  9. package/dist/channels/telegram/telegram_messaging_port.d.ts +25 -0
  10. package/dist/channels/telegram/telegram_messaging_port.js +51 -0
  11. package/dist/channels/weixin/account_store.d.ts +15 -0
  12. package/dist/channels/weixin/account_store.js +54 -0
  13. package/dist/channels/weixin/ilink/aes_ecb.d.ts +3 -0
  14. package/dist/channels/weixin/ilink/aes_ecb.js +12 -0
  15. package/dist/channels/weixin/ilink/api.d.ts +44 -0
  16. package/dist/channels/weixin/ilink/api.js +187 -0
  17. package/dist/channels/weixin/ilink/cdn_upload.d.ts +11 -0
  18. package/dist/channels/weixin/ilink/cdn_upload.js +60 -0
  19. package/dist/channels/weixin/ilink/cdn_url.d.ts +7 -0
  20. package/dist/channels/weixin/ilink/cdn_url.js +7 -0
  21. package/dist/channels/weixin/ilink/constants.d.ts +7 -0
  22. package/dist/channels/weixin/ilink/constants.js +27 -0
  23. package/dist/channels/weixin/ilink/context.d.ts +13 -0
  24. package/dist/channels/weixin/ilink/context.js +13 -0
  25. package/dist/channels/weixin/ilink/login_qr.d.ts +34 -0
  26. package/dist/channels/weixin/ilink/login_qr.js +233 -0
  27. package/dist/channels/weixin/ilink/media_image.d.ts +11 -0
  28. package/dist/channels/weixin/ilink/media_image.js +44 -0
  29. package/dist/channels/weixin/ilink/mime.d.ts +3 -0
  30. package/dist/channels/weixin/ilink/mime.js +36 -0
  31. package/dist/channels/weixin/ilink/pic_decrypt.d.ts +2 -0
  32. package/dist/channels/weixin/ilink/pic_decrypt.js +56 -0
  33. package/dist/channels/weixin/ilink/random.d.ts +2 -0
  34. package/dist/channels/weixin/ilink/random.js +7 -0
  35. package/dist/channels/weixin/ilink/redact.d.ts +4 -0
  36. package/dist/channels/weixin/ilink/redact.js +34 -0
  37. package/dist/channels/weixin/ilink/runtime_attach.d.ts +3 -0
  38. package/dist/channels/weixin/ilink/runtime_attach.js +13 -0
  39. package/dist/channels/weixin/ilink/send.d.ts +21 -0
  40. package/dist/channels/weixin/ilink/send.js +108 -0
  41. package/dist/channels/weixin/ilink/session_guard.d.ts +6 -0
  42. package/dist/channels/weixin/ilink/session_guard.js +39 -0
  43. package/dist/channels/weixin/ilink/types.d.ts +155 -0
  44. package/dist/channels/weixin/ilink/types.js +10 -0
  45. package/dist/channels/weixin/ilink/upload.d.ts +15 -0
  46. package/dist/channels/weixin/ilink/upload.js +75 -0
  47. package/dist/channels/weixin/sync_buf_store.d.ts +3 -0
  48. package/dist/channels/weixin/sync_buf_store.js +19 -0
  49. package/dist/channels/weixin/weixin_channel_adapter.d.ts +18 -0
  50. package/dist/channels/weixin/weixin_channel_adapter.js +273 -0
  51. package/dist/channels/weixin/weixin_messaging_port.d.ts +29 -0
  52. package/dist/channels/weixin/weixin_messaging_port.js +113 -0
  53. package/dist/codex_app/client.d.ts +176 -0
  54. package/dist/codex_app/client.js +1230 -0
  55. package/dist/codex_app/deeplink.d.ts +7 -0
  56. package/dist/codex_app/deeplink.js +29 -0
  57. package/dist/codex_app/local_usage.d.ts +16 -0
  58. package/dist/codex_app/local_usage.js +123 -0
  59. package/dist/config.d.ts +44 -0
  60. package/dist/config.js +131 -0
  61. package/dist/controller/access.d.ts +11 -0
  62. package/dist/controller/access.js +33 -0
  63. package/dist/controller/activity.d.ts +62 -0
  64. package/dist/controller/activity.js +330 -0
  65. package/dist/controller/commands.d.ts +6 -0
  66. package/dist/controller/commands.js +17 -0
  67. package/dist/controller/controller.d.ts +326 -0
  68. package/dist/controller/controller.js +7503 -0
  69. package/dist/controller/observer.d.ts +16 -0
  70. package/dist/controller/observer.js +98 -0
  71. package/dist/controller/presentation.d.ts +80 -0
  72. package/dist/controller/presentation.js +568 -0
  73. package/dist/controller/service_tier.d.ts +9 -0
  74. package/dist/controller/service_tier.js +32 -0
  75. package/dist/controller/session_observer.d.ts +22 -0
  76. package/dist/controller/session_observer.js +259 -0
  77. package/dist/controller/status.d.ts +10 -0
  78. package/dist/controller/status.js +28 -0
  79. package/dist/core/bridge_scope.d.ts +18 -0
  80. package/dist/core/bridge_scope.js +46 -0
  81. package/dist/core/channel_port.d.ts +15 -0
  82. package/dist/core/channel_port.js +1 -0
  83. package/dist/i18n.d.ts +1108 -0
  84. package/dist/i18n.js +1154 -0
  85. package/dist/lock.d.ts +7 -0
  86. package/dist/lock.js +80 -0
  87. package/dist/logger.d.ts +12 -0
  88. package/dist/logger.js +57 -0
  89. package/dist/main.d.ts +2 -0
  90. package/dist/main.js +236 -0
  91. package/dist/runtime.d.ts +3 -0
  92. package/dist/runtime.js +14 -0
  93. package/dist/store/database.d.ts +79 -0
  94. package/dist/store/database.js +489 -0
  95. package/dist/store/migrate_bridge_scope.d.ts +6 -0
  96. package/dist/store/migrate_bridge_scope.js +59 -0
  97. package/dist/telegram/addressing.d.ts +33 -0
  98. package/dist/telegram/addressing.js +57 -0
  99. package/dist/telegram/api.d.ts +14 -0
  100. package/dist/telegram/api.js +89 -0
  101. package/dist/telegram/gateway.d.ts +76 -0
  102. package/dist/telegram/gateway.js +383 -0
  103. package/dist/telegram/media.d.ts +34 -0
  104. package/dist/telegram/media.js +180 -0
  105. package/dist/telegram/rendering.d.ts +10 -0
  106. package/dist/telegram/rendering.js +21 -0
  107. package/dist/telegram/scope.d.ts +6 -0
  108. package/dist/telegram/scope.js +24 -0
  109. package/dist/telegram/text.d.ts +7 -0
  110. package/dist/telegram/text.js +47 -0
  111. package/dist/types.d.ts +343 -0
  112. package/dist/types.js +1 -0
  113. package/docs/agent-assisted-install.md +84 -0
  114. package/docs/install-for-beginners.md +287 -0
  115. package/docs/troubleshooting.md +239 -0
  116. package/package.json +62 -0
  117. package/scripts/doctor.sh +3 -0
  118. package/scripts/launchd/install.sh +54 -0
  119. package/scripts/status.sh +3 -0
  120. package/scripts/systemd/install.sh +83 -0
  121. package/scripts/systemd/uninstall.sh +15 -0
  122. package/skills/foxclaw/SKILL.md +167 -0
  123. package/skills/foxclaw/agents/openai.yaml +4 -0
  124. package/skills/foxclaw/references/telegram-setup.md +93 -0
  125. package/skills/foxclaw/scripts/bootstrap_host.py +350 -0
  126. package/skills/foxclaw/scripts/bootstrap_remote.py +67 -0
package/.env.example ADDED
@@ -0,0 +1,36 @@
1
+ # Required: one bot token per device
2
+ TG_BOT_TOKEN=<telegram_bot_token>
3
+
4
+ # Required: only this Telegram user can control the bridge
5
+ TG_ALLOWED_USER_ID=<telegram_user_id>
6
+
7
+ # Optional: set this to allow a specific group/supergroup
8
+ TG_ALLOWED_CHAT_ID=
9
+
10
+ # Optional: set this to make one topic the default conversation scope
11
+ # Leave empty to use the whole allowed group as the default scope
12
+ TG_ALLOWED_TOPIC_ID=
13
+
14
+ # Optional Codex Desktop bridge settings
15
+ CODEX_APP_AUTOLAUNCH=true
16
+ CODEX_APP_LAUNCH_CMD=codex app
17
+ # Optional: persist the managed app-server pid/port and collect its stdout/stderr
18
+ CODEX_APP_SERVER_STATE_PATH=
19
+ CODEX_APP_SERVER_LOG_PATH=
20
+ CODEX_APP_SYNC_ON_OPEN=true
21
+ CODEX_APP_SYNC_ON_TURN_COMPLETE=false
22
+ STORE_PATH=
23
+ LOG_LEVEL=info
24
+ DEFAULT_CWD=/absolute/path/to/workspace
25
+ DEFAULT_APPROVAL_POLICY=on-request
26
+ DEFAULT_SANDBOX_MODE=workspace-write
27
+ TELEGRAM_POLL_INTERVAL_MS=1200
28
+ TELEGRAM_PREVIEW_THROTTLE_MS=800
29
+ THREAD_LIST_LIMIT=10
30
+ CODEX_CLI_BIN=/absolute/path/to/codex
31
+
32
+ # Weixin (iLink): run `npm run weixin-login` once, then enable:
33
+ # WX_ENABLED=true
34
+ # Comma-separated iLink user ids allowed to chat (see account JSON linkedIlinkUserId)
35
+ # WX_ALLOWED_ILINK_USER_IDS=
36
+ # Optional: WEIXIN_ACCOUNTS_DIR, WEIXIN_SYNC_BUF_DIR, WEIXIN_MEDIA_DIR, WX_ILINK_ROUTE_TAG
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gan Xing
4
+ Copyright (c) 2026 Foxden App contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ 中文 | [English](./README_EN.md)
2
+
3
+ # FoxClaw
4
+
5
+ FoxClaw 是 Foxden agents 的本地执行爪。
6
+
7
+ 它运行在你自己的电脑上,让受信任的 Telegram 或微信聊天可以控制本机 Codex 环境。你不需要公网服务器:FoxClaw 通过本地 `codex app-server` 与 Codex 通信,把审批留在你的电脑上,并把工作会话发送回手机。
8
+
9
+ ## 从这里开始
10
+
11
+ - 已经有 Codex、OpenClaw、QwenPaw、Hermes、OpenCode 或 Kimi CLI 这类能操作 shell 的 agent?优先使用 [Agent 辅助安装](./docs/agent-assisted-install.md),这是推荐路径。
12
+ - 不熟悉 Node、Telegram 机器人或 Codex CLI?使用 [新手安装指南](./docs/install-for-beginners.md)。
13
+ - 已经熟悉 Git、Node 和 `.env` 文件?可以直接使用下面的快速设置。
14
+ - 遇到问题?查看 [故障排查](./docs/troubleshooting.md)。
15
+
16
+ 如果你希望做到这些,FoxClaw 会很适合:
17
+
18
+ - 在手机上使用 Codex,同时不把自己的电脑暴露到公网
19
+ - 将代码、shell 访问、认证、审批和运行数据都保留在自己的机器上
20
+ - 让一个受信任的 Telegram 用户作为远程操作者
21
+
22
+ 最小安装只需要一个 Telegram bot token、你的 Telegram 数字用户 ID、Node.js 24,以及一份已经登录的 `codex` CLI。首次安装通常需要 10-20 分钟。
23
+
24
+ 30 秒产品示例:FoxClaw 运行后,向你的 Telegram 机器人发送 `List files in DEFAULT_CWD`。FoxClaw 会让本地 Codex 检查你电脑上的那个目录,并把答案发回 Telegram。
25
+
26
+ ## 环境要求
27
+
28
+ - macOS 或 Linux,并且有可用的 `codex` CLI
29
+ - 主机上的 Codex 已完成认证
30
+ - Node.js 24+
31
+ - 来自 `@BotFather` 的 Telegram bot token
32
+ - 你的 Telegram 数字用户 ID
33
+
34
+ ## 快速设置
35
+
36
+ ```bash
37
+ git clone https://github.com/foxden-app/foxclaw.git
38
+ cd foxclaw
39
+ npm install
40
+ cp .env.example .env
41
+ $EDITOR .env
42
+ npm run build
43
+ npm run doctor
44
+ npm run serve
45
+ ```
46
+
47
+ 运行 `doctor` 或 `serve` 前先编辑 `.env`。私聊模式的最小配置:
48
+
49
+ ```dotenv
50
+ TG_BOT_TOKEN=123456:telegram-token
51
+ TG_ALLOWED_USER_ID=123456789
52
+ DEFAULT_CWD=/absolute/path/to/workspace
53
+ DEFAULT_APPROVAL_POLICY=on-request
54
+ DEFAULT_SANDBOX_MODE=workspace-write
55
+ ```
56
+
57
+ FoxClaw 只接受来自 `TG_ALLOWED_USER_ID` 的消息。把机器人放进群组并不会让每个群成员都能使用它。
58
+
59
+ <details>
60
+ <summary>FoxClaw 运行后可以做什么</summary>
61
+
62
+ - 允许一个 Telegram 用户通过私聊、群组和话题控制
63
+ - 可选的微信/iLink 通道,复用同一套桥接核心
64
+ - 使用 `/threads`、`/open`、`/new`、`/where` 和 `/interrupt` 进行稳定的聊天到线程绑定
65
+ - 从手机控制线程生命周期:重命名、归档、取消归档、fork、回滚、compact、review 和 diff
66
+ - 面向单个聊天的设置面板:模型、reasoning effort、Fast service tier、access preset、Agent/Plan 模式和 active-turn 行为
67
+ - Codex 账户控制:`/account`、`/quota`、`/login_device`、`/login_cancel`、`/auth add <name>`,以及带保护的 `/logout confirm`
68
+ - 当某个认证触发用量限制时,在本地 `auth.json_*` 候选之间自动切换 Codex 认证
69
+ - 针对命令、文件变更和细粒度权限审批的内联按钮
70
+ - 针对工具在 turn 中提出的结构化问题显示 MCP elicitation 卡片
71
+ - Skills、MCP、hooks、plugins、apps、feature flags、config、requirements 和 provider diagnostics
72
+ - 使用 SQLite 持久化绑定、offset、审批、待处理输入提示和审计日志
73
+ - 单实例进程锁,避免同一个 bot token 上出现重复 Telegram polling
74
+
75
+ </details>
76
+
77
+ ## 作为服务安装
78
+
79
+ Linux 用户级 systemd:
80
+
81
+ ```bash
82
+ npm run install-systemd
83
+ systemctl --user status foxclaw.service
84
+ journalctl --user -u foxclaw.service -f
85
+ ```
86
+
87
+ macOS launchd:
88
+
89
+ ```bash
90
+ ./scripts/launchd/install.sh
91
+ ```
92
+
93
+ 默认运行时文件存储在 `~/.foxclaw`:
94
+
95
+ - store:`~/.foxclaw/data/bridge.sqlite`
96
+ - bridge log:`~/.foxclaw/logs/service.log`
97
+ - status:`~/.foxclaw/runtime/status.json`
98
+ - app-server state:`~/.foxclaw/runtime/codex-app-server.json`
99
+ - app-server log:`~/.foxclaw/logs/codex-app-server.log`
100
+
101
+ 可以用 `STORE_PATH`、`LOCK_PATH`、`CODEX_APP_SERVER_STATE_PATH` 和 `CODEX_APP_SERVER_LOG_PATH` 覆盖 store、lock 和 app-server 路径。
102
+
103
+ ## 从 telegram-codex-app-bridge 迁移
104
+
105
+ FoxClaw 最初 fork 自 `Gan-Xing/telegram-codex-app-bridge`,并继续以 MIT License 分发。
106
+
107
+ 升级已有本地安装时:
108
+
109
+ ```bash
110
+ systemctl --user disable --now telegram-codex-app-bridge.service 2>/dev/null || true
111
+ test -e ~/.foxclaw || cp -a ~/.telegram-codex-app-bridge ~/.foxclaw
112
+ npm run install-systemd
113
+ ```
114
+
115
+ 如果使用 launchd 安装,先卸载旧 plist(如存在):
116
+
117
+ ```bash
118
+ launchctl unload ~/Library/LaunchAgents/com.ganxing.telegram-codex-app-bridge.plist 2>/dev/null || true
119
+ ./scripts/launchd/install.sh
120
+ ```
121
+
122
+ 旧运行时目录不会被自动读取。如果你想保留现有绑定、缓存线程列表、审批和状态数据,请手动复制一次。
123
+
124
+ ## Telegram 设置
125
+
126
+ 1. 使用 `@BotFather` 创建机器人,并把 token 填入 `TG_BOT_TOKEN`。
127
+ 2. 获取你的 Telegram 数字用户 ID,并填入 `TG_ALLOWED_USER_ID`。
128
+ 3. 使用 `npm run serve` 或服务安装器启动 FoxClaw。
129
+ 4. 打开与机器人的私聊并发送 `/help`。
130
+
131
+ 可选的群组/话题配置:
132
+
133
+ ```dotenv
134
+ TG_ALLOWED_CHAT_ID=-1001234567890
135
+ TG_ALLOWED_TOPIC_ID=42
136
+ ```
137
+
138
+ - 留空 `TG_ALLOWED_CHAT_ID` 表示使用私聊模式。
139
+ - 只设置 `TG_ALLOWED_CHAT_ID` 时,允许一个群组作为默认会话范围。
140
+ - 同时设置 `TG_ALLOWED_CHAT_ID` 和 `TG_ALLOWED_TOPIC_ID` 时,绑定一个话题作为默认范围。
141
+ - 即使配置了群组,`TG_ALLOWED_USER_ID` 仍然可以继续使用私聊。
142
+
143
+ 查找群组和话题 ID:
144
+
145
+ 1. 停止 FoxClaw。
146
+ 2. 在目标群组或话题中发送一条消息。
147
+ 3. 打开 `https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`。
148
+ 4. 读取 `message.chat.id` 作为 `TG_ALLOWED_CHAT_ID`。
149
+ 5. 读取 `message.message_thread_id` 作为 `TG_ALLOWED_TOPIC_ID`。
150
+
151
+ 如果 FoxClaw 还在运行,它可能会在你检查前消费掉该 update。
152
+
153
+ ## Telegram 群组检查清单
154
+
155
+ 对于群组或超级群里的自然语言消息:
156
+
157
+ 1. 将机器人加入目标群组。
158
+ 2. 在 `@BotFather` 中禁用机器人的 `privacy mode`。
159
+ 3. 将机器人提升为该群管理员。
160
+ 4. 如果是在加入群组后才修改隐私模式,请移除并重新加入机器人。
161
+
162
+ 即使 privacy mode 阻止普通消息,`/status@botname` 这样的显式命令也可能正常工作,所以请用一条普通消息验证群组设置。
163
+
164
+ ## Codex App-Server 生命周期
165
+
166
+ 默认配置:
167
+
168
+ ```dotenv
169
+ CODEX_APP_AUTOLAUNCH=true
170
+ CODEX_APP_LAUNCH_CMD=codex app
171
+ CODEX_APP_SERVER_STATE_PATH=
172
+ CODEX_APP_SERVER_LOG_PATH=
173
+ CODEX_APP_SYNC_ON_OPEN=true
174
+ CODEX_APP_SYNC_ON_TURN_COMPLETE=false
175
+ ```
176
+
177
+ FoxClaw 会把 `codex app-server` 启动为一个由 bridge 管理的 detached 进程,并记录它的 pid 和端口。重启时,如果记录的 app-server 进程仍然存活,FoxClaw 会重新连接;否则会启动一个新进程。`/auth_reload` 和认证切换会重启托管的 app-server,从而重新加载当前 `auth.json`。
178
+
179
+ 正常安装不需要固定的 Codex app-server 端口。
180
+
181
+ ## 命令
182
+
183
+ - `/help`
184
+ - `/setup` 打开统一偏好设置面板
185
+ - `/fast <on|off|toggle>`
186
+ - `/active <steer|queue>`
187
+ - `/status`、`/account`、`/quota`
188
+ - `/quota_nudge <credits|usage_limit> confirm`
189
+ - `/login_device`、`/login_cancel [id]`、`/logout confirm`
190
+ - `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
191
+ - `/threads [query]`、`/threads archived`、`/open <n>`
192
+ - `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
193
+ - `/history [limit]`、`/files <query>`、`/remote`
194
+ - `/new [cwd]`
195
+ - `/steer <message>`、`/takeover <message>`、`/queue <message>`
196
+ - `/review [base <branch>|commit <sha>|custom <instructions>]`
197
+ - `/diff`、`/fork [name]`、`/undo [n]`、`/rollback [n]`
198
+ - `/rename <name>`、`/compact`、`/archive`、`/unarchive <n>`
199
+ - `/skills [query]`、`/skill <name>`、`/skill_enable <name>`、`/skill_disable <name>`
200
+ - `/loaded`、`/hooks`、`/plugins [query]`、`/apps [reload]`、`/features`、`/config`、`/requirements`、`/provider`
201
+ - `/mcp`、`/mcp_reload`、`/mcp_login <server>`、`/mcp_resource <server> <uri>`
202
+ - `/models`、`/model`、`/effort`、`/permissions`、`/access`、`/mode`、`/plan` 和 `/agent`
203
+ - `/reveal`、`/where`、`/interrupt`
204
+
205
+ 普通文本会发送到当前线程;如果当前没有绑定线程,则创建一个新线程。
206
+
207
+ ## 微信/iLink
208
+
209
+ 微信支持是可选的,默认关闭:
210
+
211
+ ```dotenv
212
+ WX_ENABLED=true
213
+ WX_ALLOWED_ILINK_USER_IDS=
214
+ ```
215
+
216
+ 构建后运行一次二维码登录助手:
217
+
218
+ ```bash
219
+ npm run weixin-login
220
+ ```
221
+
222
+ 微信运行时文件默认存储在 `~/.foxclaw/weixin`。
223
+
224
+ ## Codex Skill
225
+
226
+ 本仓库内置一个 Codex skill:[`skills/foxclaw`](./skills/foxclaw)。当你想让 Codex 在本机或另一台 Mac 上通过 SSH bootstrap FoxClaw、写入 `.env`、构建、运行 doctor、安装 launchd,并指导首次消息验证时,可以使用它。
227
+
228
+ ## 故障排查
229
+
230
+ `doctor` 失败、Telegram 没有回复、服务日志、重启行为和迁移问题,请查看 [故障排查](./docs/troubleshooting.md)。
231
+
232
+ ## 运维命令
233
+
234
+ ```bash
235
+ npm run build
236
+ npm run doctor
237
+ npm run status
238
+ npm run install-systemd
239
+ npm run uninstall-systemd
240
+ ```
241
+
242
+ ## 贡献
243
+
244
+ 欢迎在 `https://github.com/foxden-app/foxclaw` 提交 issue 和 PR。
package/README_EN.md ADDED
@@ -0,0 +1,244 @@
1
+ [中文](./README.md) | English
2
+
3
+ # FoxClaw
4
+
5
+ FoxClaw is the local execution claw for Foxden agents.
6
+
7
+ It runs on your own computer and lets a trusted Telegram or Weixin chat control your local Codex environment. You do not need a public server: FoxClaw talks to Codex over local `codex app-server`, keeps approvals on your machine, and sends the working conversation back to your phone.
8
+
9
+ ## Start Here
10
+
11
+ - Already have a shell-capable agent such as Codex, OpenClaw, QwenPaw, Hermes, OpenCode, or Kimi CLI? Use the [Agent-Assisted Install](./docs/agent-assisted-install.md) first. This is the recommended path.
12
+ - New to Node, Telegram bots, or Codex CLI? Use the [Beginner Install Guide](./docs/install-for-beginners.md).
13
+ - Already comfortable with Git, Node, and `.env` files? Use the quick setup below.
14
+ - Something failed? Check [Troubleshooting](./docs/troubleshooting.md).
15
+
16
+ FoxClaw is a good fit if you want to:
17
+
18
+ - use Codex from your phone without exposing your computer to the public internet
19
+ - keep code, shell access, auth, approvals, and runtime data on your own machine
20
+ - use one trusted Telegram user as the remote operator
21
+
22
+ The minimum install needs only a Telegram bot token, your numeric Telegram user id, Node.js 24, and a logged-in `codex` CLI. A first install usually takes 10-20 minutes.
23
+
24
+ 30-second product example: after FoxClaw is running, send `List files in DEFAULT_CWD` to your Telegram bot. FoxClaw asks local Codex to inspect that folder on your computer and sends the answer back to Telegram.
25
+
26
+ ## Requirements
27
+
28
+ - macOS or Linux with a working `codex` CLI
29
+ - Codex authenticated on the host machine
30
+ - Node.js 24+
31
+ - A Telegram bot token from `@BotFather`
32
+ - Your Telegram numeric user id
33
+
34
+ ## Quick Setup
35
+
36
+ ```bash
37
+ git clone https://github.com/foxden-app/foxclaw.git
38
+ cd foxclaw
39
+ npm install
40
+ cp .env.example .env
41
+ $EDITOR .env
42
+ npm run build
43
+ npm run doctor
44
+ npm run serve
45
+ ```
46
+
47
+ Edit `.env` before running `doctor` or `serve`. Minimum private-chat config:
48
+
49
+ ```dotenv
50
+ TG_BOT_TOKEN=123456:telegram-token
51
+ TG_ALLOWED_USER_ID=123456789
52
+ DEFAULT_CWD=/absolute/path/to/workspace
53
+ DEFAULT_APPROVAL_POLICY=on-request
54
+ DEFAULT_SANDBOX_MODE=workspace-write
55
+ ```
56
+
57
+ FoxClaw accepts messages only from `TG_ALLOWED_USER_ID`. Putting the bot in a group does not make it available to every group member.
58
+
59
+ <details>
60
+ <summary>What FoxClaw can do after it is running</summary>
61
+
62
+ - Telegram private chat, group, and topic control for one allowed Telegram user
63
+ - Optional Weixin/iLink channel for the same bridge core
64
+ - Sticky chat-to-thread binding with `/threads`, `/open`, `/new`, `/where`, and `/interrupt`
65
+ - Thread lifecycle controls from mobile: rename, archive, unarchive, fork, rollback, compact, review, and diff
66
+ - Chat-scoped setup panel for model, reasoning effort, Fast service tier, access preset, Agent/Plan mode, and active-turn behavior
67
+ - Codex account controls with `/account`, `/quota`, `/login_device`, `/login_cancel`, `/auth add <name>`, and guarded `/logout confirm`
68
+ - Automatic local Codex auth rotation across `auth.json_*` candidates when a usage-limit auth fails
69
+ - Inline approval buttons for command, file-change, and granular permission approvals
70
+ - MCP elicitation cards for structured questions raised by tools during a turn
71
+ - Skills, MCP, hooks, plugins, apps, feature flags, config, requirements, and provider diagnostics
72
+ - SQLite persistence for bindings, offsets, approvals, pending input prompts, and audit logs
73
+ - Single-instance process lock to prevent duplicate Telegram polling on the same bot token
74
+
75
+ </details>
76
+
77
+ ## Installing As A Service
78
+
79
+ Linux user systemd:
80
+
81
+ ```bash
82
+ npm run install-systemd
83
+ systemctl --user status foxclaw.service
84
+ journalctl --user -u foxclaw.service -f
85
+ ```
86
+
87
+ macOS launchd:
88
+
89
+ ```bash
90
+ ./scripts/launchd/install.sh
91
+ ```
92
+
93
+ Default runtime files are stored under `~/.foxclaw`:
94
+
95
+ - store: `~/.foxclaw/data/bridge.sqlite`
96
+ - bridge log: `~/.foxclaw/logs/service.log`
97
+ - status: `~/.foxclaw/runtime/status.json`
98
+ - app-server state: `~/.foxclaw/runtime/codex-app-server.json`
99
+ - app-server log: `~/.foxclaw/logs/codex-app-server.log`
100
+
101
+ Override the store, lock, and app-server paths with `STORE_PATH`, `LOCK_PATH`, `CODEX_APP_SERVER_STATE_PATH`, and `CODEX_APP_SERVER_LOG_PATH`.
102
+
103
+ ## Migrating From telegram-codex-app-bridge
104
+
105
+ FoxClaw was originally forked from `Gan-Xing/telegram-codex-app-bridge` and remains distributed under the MIT License.
106
+
107
+ When upgrading an existing local install:
108
+
109
+ ```bash
110
+ systemctl --user disable --now telegram-codex-app-bridge.service 2>/dev/null || true
111
+ test -e ~/.foxclaw || cp -a ~/.telegram-codex-app-bridge ~/.foxclaw
112
+ npm run install-systemd
113
+ ```
114
+
115
+ For launchd installs, unload the old plist if present:
116
+
117
+ ```bash
118
+ launchctl unload ~/Library/LaunchAgents/com.ganxing.telegram-codex-app-bridge.plist 2>/dev/null || true
119
+ ./scripts/launchd/install.sh
120
+ ```
121
+
122
+ The old runtime directory is not read automatically. Copy it once if you want to keep existing bindings, cached thread lists, approvals, and status data.
123
+
124
+ ## Telegram Setup
125
+
126
+ 1. Create a bot with `@BotFather` and copy the token into `TG_BOT_TOKEN`.
127
+ 2. Get your Telegram numeric user id and place it into `TG_ALLOWED_USER_ID`.
128
+ 3. Start FoxClaw with `npm run serve` or the service installer.
129
+ 4. Open a private chat with the bot and send `/help`.
130
+
131
+ Optional group/topic config:
132
+
133
+ ```dotenv
134
+ TG_ALLOWED_CHAT_ID=-1001234567890
135
+ TG_ALLOWED_TOPIC_ID=42
136
+ ```
137
+
138
+ - Leave `TG_ALLOWED_CHAT_ID` empty for private-chat mode.
139
+ - Set `TG_ALLOWED_CHAT_ID` only to allow one group as the default conversation scope.
140
+ - Set both `TG_ALLOWED_CHAT_ID` and `TG_ALLOWED_TOPIC_ID` to bind one topic as the default scope.
141
+ - Private chat remains available for `TG_ALLOWED_USER_ID` even when a group is configured.
142
+
143
+ To discover group and topic ids:
144
+
145
+ 1. Stop FoxClaw.
146
+ 2. Send a message in the target group or topic.
147
+ 3. Open `https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`.
148
+ 4. Read `message.chat.id` as `TG_ALLOWED_CHAT_ID`.
149
+ 5. Read `message.message_thread_id` as `TG_ALLOWED_TOPIC_ID`.
150
+
151
+ If FoxClaw is still running, it may consume the update before you inspect it.
152
+
153
+ ## Telegram Group Checklist
154
+
155
+ For natural-language messages in a group or supergroup:
156
+
157
+ 1. Add the bot to the target group.
158
+ 2. Disable the bot's `privacy mode` in `@BotFather`.
159
+ 3. Promote the bot to administrator in that group.
160
+ 4. If privacy mode was changed after adding the bot, remove and re-add the bot.
161
+
162
+ Explicit commands such as `/status@botname` can work even when privacy mode blocks normal messages, so use a plain message to verify group setup.
163
+
164
+ ## Codex App-Server Lifecycle
165
+
166
+ By default:
167
+
168
+ ```dotenv
169
+ CODEX_APP_AUTOLAUNCH=true
170
+ CODEX_APP_LAUNCH_CMD=codex app
171
+ CODEX_APP_SERVER_STATE_PATH=
172
+ CODEX_APP_SERVER_LOG_PATH=
173
+ CODEX_APP_SYNC_ON_OPEN=true
174
+ CODEX_APP_SYNC_ON_TURN_COMPLETE=false
175
+ ```
176
+
177
+ FoxClaw starts `codex app-server` as a detached, bridge-managed process and records its pid and port. On restart, it reconnects to the recorded app-server if that process is still alive; otherwise it starts a new one. `/auth_reload` and auth switching restart the managed app-server so the current `auth.json` is reloaded.
178
+
179
+ No static Codex app-server port is required in normal installs.
180
+
181
+ ## Commands
182
+
183
+ - `/help`
184
+ - `/setup` opens the unified preference panel
185
+ - `/fast <on|off|toggle>`
186
+ - `/active <steer|queue>`
187
+ - `/status`, `/account`, `/quota`
188
+ - `/quota_nudge <credits|usage_limit> confirm`
189
+ - `/login_device`, `/login_cancel [id]`, `/logout confirm`
190
+ - `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
191
+ - `/threads [query]`, `/threads archived`, `/open <n>`
192
+ - `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
193
+ - `/history [limit]`, `/files <query>`, `/remote`
194
+ - `/new [cwd]`
195
+ - `/steer <message>`, `/takeover <message>`, `/queue <message>`
196
+ - `/review [base <branch>|commit <sha>|custom <instructions>]`
197
+ - `/diff`, `/fork [name]`, `/undo [n]`, `/rollback [n]`
198
+ - `/rename <name>`, `/compact`, `/archive`, `/unarchive <n>`
199
+ - `/skills [query]`, `/skill <name>`, `/skill_enable <name>`, `/skill_disable <name>`
200
+ - `/loaded`, `/hooks`, `/plugins [query]`, `/apps [reload]`, `/features`, `/config`, `/requirements`, `/provider`
201
+ - `/mcp`, `/mcp_reload`, `/mcp_login <server>`, `/mcp_resource <server> <uri>`
202
+ - `/models`, `/model`, `/effort`, `/permissions`, `/access`, `/mode`, `/plan`, and `/agent`
203
+ - `/reveal`, `/where`, `/interrupt`
204
+
205
+ Plain text sends to the current thread, or creates a new one if none is bound.
206
+
207
+ ## Weixin/iLink
208
+
209
+ Weixin support is optional and disabled by default:
210
+
211
+ ```dotenv
212
+ WX_ENABLED=true
213
+ WX_ALLOWED_ILINK_USER_IDS=
214
+ ```
215
+
216
+ Run the QR login helper once after building:
217
+
218
+ ```bash
219
+ npm run weixin-login
220
+ ```
221
+
222
+ Weixin runtime files default to `~/.foxclaw/weixin`.
223
+
224
+ ## Codex Skill
225
+
226
+ This repo ships a Codex skill at [`skills/foxclaw`](./skills/foxclaw). Use it when you want Codex to bootstrap FoxClaw locally or on another Mac over SSH, write `.env`, build, run doctor, install launchd, and guide first-message validation.
227
+
228
+ ## Troubleshooting
229
+
230
+ See [Troubleshooting](./docs/troubleshooting.md) for `doctor` failures, Telegram no-reply cases, service logs, reboot behavior, and migration issues.
231
+
232
+ ## Operations
233
+
234
+ ```bash
235
+ npm run build
236
+ npm run doctor
237
+ npm run status
238
+ npm run install-systemd
239
+ npm run uninstall-systemd
240
+ ```
241
+
242
+ ## Contributing
243
+
244
+ Issues and PRs are welcome at `https://github.com/foxden-app/foxclaw`.
@@ -0,0 +1,27 @@
1
+ import type { TelegramRemoteFile } from '../telegram/api.js';
2
+ import type { InlineKeyboard, TelegramMessagingPort } from './telegram/telegram_messaging_port.js';
3
+ import type { WeixinMessagingPort } from './weixin/weixin_messaging_port.js';
4
+ /**
5
+ * Routes outbound calls by `scopeId` prefix: {@link BRIDGE_SCOPE_WEIXIN_PREFIX} vs Telegram.
6
+ * Telegram-only surfaces (callbacks, Bot API files) always use the Telegram port.
7
+ */
8
+ export declare class BridgeMessagingRouter {
9
+ private readonly telegram;
10
+ private readonly weixin;
11
+ constructor(telegram: TelegramMessagingPort, weixin: WeixinMessagingPort | null);
12
+ get hasWeixinTransport(): boolean;
13
+ private isWeixinScope;
14
+ canSendToScope(scopeId: string): boolean;
15
+ private requireWeixinTransport;
16
+ sendPlain(scopeId: string, text: string, keyboard?: InlineKeyboard): Promise<number>;
17
+ sendHtml(scopeId: string, text: string, keyboard?: InlineKeyboard): Promise<number>;
18
+ editPlain(scopeId: string, messageId: number, text: string, keyboard?: InlineKeyboard): Promise<void>;
19
+ editHtml(scopeId: string, messageId: number, text: string, keyboard?: InlineKeyboard): Promise<void>;
20
+ deleteMessage(scopeId: string, messageId: number): Promise<void>;
21
+ sendTypingInScope(scopeId: string): Promise<void>;
22
+ clearInlineKeyboard(scopeId: string, messageId: number): Promise<void>;
23
+ sendDraft(scopeId: string, draftId: number, text: string): Promise<void>;
24
+ answerCallback(callbackQueryId: string, text: string): Promise<void>;
25
+ getFile(fileId: string): Promise<TelegramRemoteFile>;
26
+ downloadResolvedFile(remoteFilePath: string, destinationPath: string): Promise<number>;
27
+ }
@@ -0,0 +1,85 @@
1
+ import { BRIDGE_SCOPE_WEIXIN_PREFIX } from '../core/bridge_scope.js';
2
+ /**
3
+ * Routes outbound calls by `scopeId` prefix: {@link BRIDGE_SCOPE_WEIXIN_PREFIX} vs Telegram.
4
+ * Telegram-only surfaces (callbacks, Bot API files) always use the Telegram port.
5
+ */
6
+ export class BridgeMessagingRouter {
7
+ telegram;
8
+ weixin;
9
+ constructor(telegram, weixin) {
10
+ this.telegram = telegram;
11
+ this.weixin = weixin;
12
+ }
13
+ get hasWeixinTransport() {
14
+ return this.weixin !== null;
15
+ }
16
+ isWeixinScope(scopeId) {
17
+ return scopeId.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX);
18
+ }
19
+ canSendToScope(scopeId) {
20
+ return !this.isWeixinScope(scopeId) || this.weixin !== null;
21
+ }
22
+ requireWeixinTransport(scopeId) {
23
+ if (!this.weixin) {
24
+ throw new Error(`Weixin channel is disabled for scope ${scopeId}`);
25
+ }
26
+ return this.weixin;
27
+ }
28
+ sendPlain(scopeId, text, keyboard) {
29
+ if (this.isWeixinScope(scopeId)) {
30
+ return this.requireWeixinTransport(scopeId).sendPlain(scopeId, text, keyboard);
31
+ }
32
+ return this.telegram.sendPlain(scopeId, text, keyboard);
33
+ }
34
+ sendHtml(scopeId, text, keyboard) {
35
+ if (this.isWeixinScope(scopeId)) {
36
+ return this.requireWeixinTransport(scopeId).sendHtml(scopeId, text, keyboard);
37
+ }
38
+ return this.telegram.sendHtml(scopeId, text, keyboard);
39
+ }
40
+ editPlain(scopeId, messageId, text, keyboard) {
41
+ if (this.isWeixinScope(scopeId)) {
42
+ return this.requireWeixinTransport(scopeId).editPlain(scopeId, messageId, text, keyboard);
43
+ }
44
+ return this.telegram.editPlain(scopeId, messageId, text, keyboard);
45
+ }
46
+ editHtml(scopeId, messageId, text, keyboard) {
47
+ if (this.isWeixinScope(scopeId)) {
48
+ return this.requireWeixinTransport(scopeId).editHtml(scopeId, messageId, text, keyboard);
49
+ }
50
+ return this.telegram.editHtml(scopeId, messageId, text, keyboard);
51
+ }
52
+ deleteMessage(scopeId, messageId) {
53
+ if (this.isWeixinScope(scopeId)) {
54
+ return this.requireWeixinTransport(scopeId).deleteMessage(scopeId, messageId);
55
+ }
56
+ return this.telegram.deleteMessage(scopeId, messageId);
57
+ }
58
+ sendTypingInScope(scopeId) {
59
+ if (this.isWeixinScope(scopeId)) {
60
+ return this.requireWeixinTransport(scopeId).sendTypingInScope(scopeId);
61
+ }
62
+ return this.telegram.sendTypingInScope(scopeId);
63
+ }
64
+ clearInlineKeyboard(scopeId, messageId) {
65
+ if (this.isWeixinScope(scopeId)) {
66
+ return this.requireWeixinTransport(scopeId).clearInlineKeyboard(scopeId, messageId);
67
+ }
68
+ return this.telegram.clearInlineKeyboard(scopeId, messageId);
69
+ }
70
+ sendDraft(scopeId, draftId, text) {
71
+ if (this.isWeixinScope(scopeId)) {
72
+ return this.requireWeixinTransport(scopeId).sendDraft(scopeId, draftId, text);
73
+ }
74
+ return this.telegram.sendDraft(scopeId, draftId, text);
75
+ }
76
+ answerCallback(callbackQueryId, text) {
77
+ return this.telegram.answerCallback(callbackQueryId, text);
78
+ }
79
+ getFile(fileId) {
80
+ return this.telegram.getFile(fileId);
81
+ }
82
+ downloadResolvedFile(remoteFilePath, destinationPath) {
83
+ return this.telegram.downloadResolvedFile(remoteFilePath, destinationPath);
84
+ }
85
+ }
@@ -0,0 +1,12 @@
1
+ import type { BridgeSessionCore } from '../../controller/controller.js';
2
+ /**
3
+ * Telegram channel: inbound subscription + transport startup ordering for {@link BridgeSessionCore}.
4
+ * Additional channels (e.g. Weixin) can compose the same core with their own adapters.
5
+ */
6
+ export declare class TelegramChannelAdapter {
7
+ private readonly core;
8
+ constructor(core: BridgeSessionCore);
9
+ start(): Promise<void>;
10
+ stop(): Promise<void>;
11
+ get sessionCore(): BridgeSessionCore;
12
+ }