@foxden-app/foxclaw 0.2.5 → 0.2.7
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 +59 -21
- package/README_EN.md +73 -33
- package/dist/controller/controller.js +1 -1
- package/dist/controller/presentation.js +25 -15
- package/dist/i18n.d.ts +58 -58
- package/dist/i18n.js +58 -58
- package/docs/zh/agent-assisted-install.md +83 -0
- package/docs/zh/foxclaw-skill.md +24 -0
- package/docs/zh/install-for-beginners.md +295 -0
- package/docs/zh/troubleshooting.md +222 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
中文 | [English](./README_EN.md)
|
|
2
2
|
|
|
3
|
-
# FoxClaw
|
|
3
|
+
# 🦊 FoxClaw · 狸爪
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**狸爪:一个更适配编程需求的移动端 Codex 控制器。**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex,把移动端变成一个真正能用的 web coding 入口。Telegram 或微信负责交互,`codex app-server` 负责本地执行;你发任务、看进度、批审批、切线程,Codex 在电脑上继续写代码。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
这适合你离开办公桌去吃饭、在旅途中、在跑步机上,或者陪小孩逛公园的时候继续工作。人可以离开电脑,Codex 不必停;它会把关键进展、错误、审批请求和最终结果同步回手机。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
不需要公网服务器。FoxClaw 跑在你自己的电脑上,代码、shell、认证、审批和运行数据都留在本机,只把你允许的操作入口暴露给受信任的聊天账号。
|
|
12
|
+
|
|
13
|
+
## 为什么是 FoxClaw
|
|
14
|
+
|
|
15
|
+
**为什么选 Codex 作为底层引擎?**
|
|
16
|
+
|
|
17
|
+
1. **开源且接口完整** — Codex 是 OpenAI 开源的 CLI agent,并提供 `codex app-server`。FoxClaw 不是模拟终端输入,而是通过更完整的接口读取线程、切换模型、处理审批和恢复会话。
|
|
18
|
+
2. **当前编码体验强** — FoxClaw 直接读取你本机 Codex 可用的模型列表;如果你的 Codex 环境已经有 GPT-5.5,就可以在手机端选择和使用它。对高强度编程工作流来说,Codex/GPT-5.5 的体感已经是很多人优先选择它的原因。
|
|
19
|
+
3. **多账号额度切换** — 免费额度、一个月 Plus/Team 试用、多账号小额度都可以纳入本地 `auth.json_*` 候选。5 小时限制一到,FoxClaw 自动切换下一个可用账号、重启 app-server 并重试刚才的请求。
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
**适合这些场景:**
|
|
17
22
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
23
|
+
- 🍜 离开办公桌去吃饭,Codex 继续编码,有审批需求时手机上点一下
|
|
24
|
+
- 🚶 通勤路上、旅途中,不用打开电脑也能下发任务、查看进展、继续调试
|
|
25
|
+
- 🏃 在跑步机上、陪小孩逛公园,持续监控 Codex 的编码进程
|
|
26
|
+
- 🔒 代码、shell、认证、审批、运行数据全部留在本机,不暴露到公网
|
|
27
|
+
- 👤 只允许一个受信任的 Telegram 用户远程操作
|
|
28
|
+
|
|
29
|
+
## 从这里开始
|
|
30
|
+
|
|
31
|
+
- 手头有 Codex、OpenClaw、QwenPaw、Hermes、OpenCode、Kimi CLI 之类能跑 shell 的 agent?推荐走 [Agent 辅助安装](./docs/zh/agent-assisted-install.md)。
|
|
32
|
+
- 对 Node、Telegram 机器人、Codex CLI 不太熟?看 [新手安装指南](./docs/zh/install-for-beginners.md)。
|
|
33
|
+
- Git、Node、`.env` 都玩得转?直接往下看快速设置。
|
|
34
|
+
- 卡住了?看 [故障排查](./docs/zh/troubleshooting.md)。
|
|
21
35
|
|
|
22
36
|
最低要求:一个 Telegram bot token、你的 Telegram 数字用户 ID、Node.js 24、一份已登录的 `codex` CLI。首次安装大约 10–20 分钟。
|
|
23
37
|
|
|
@@ -70,21 +84,45 @@ FoxClaw 只响应 `TG_ALLOWED_USER_ID` 的消息——把机器人拉进群不
|
|
|
70
84
|
<details>
|
|
71
85
|
<summary>FoxClaw 能做什么</summary>
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
**核心能力:**
|
|
88
|
+
- 通过 Telegram 私聊、群组、话题控制本地 Codex
|
|
74
89
|
- 可选微信/iLink 通道,复用同一套桥接核心
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
- 每个聊天独立的设置面板:模型、reasoning effort、Fast tier、access preset、Agent/Plan 模式、active-turn 行为
|
|
78
|
-
- Codex 账户管理:`/account`、`/quota`、`/login_device`、`/login_cancel`、`/auth add <name>`、`/logout confirm`
|
|
79
|
-
- 触发用量限制时自动在本地 `auth.json_*` 之间切换认证
|
|
80
|
-
- 命令、文件变更、细粒度权限审批的内联按钮
|
|
90
|
+
- 手机上完整管理 Codex 线程生命周期:创建、重命名、归档、fork、回滚、compact、review、diff
|
|
91
|
+
- 命令、文件变更、细粒度权限审批的内联按钮——手机上一键审批
|
|
81
92
|
- MCP elicitation 卡片——工具在 turn 中提出结构化问题时展示
|
|
93
|
+
|
|
94
|
+
**多账号管理:**
|
|
95
|
+
- Codex 账户管理:`/account`、`/quota`、`/login_device`、`/auth add <name>`
|
|
96
|
+
- 触发用量限制时自动在本地 `auth.json_*` 之间切换认证——5 小时限制到了自动换号
|
|
97
|
+
- `/auth` 面板查看、启用、禁用、切换候选账号
|
|
98
|
+
|
|
99
|
+
**线程与会话:**
|
|
100
|
+
- `/threads`、`/open`、`/new`、`/where`、`/interrupt`——稳定的聊天-线程绑定
|
|
101
|
+
- 每个聊天独立的设置面板:模型、reasoning effort、Fast tier、access preset、Agent/Plan 模式
|
|
82
102
|
- Skills、MCP、hooks、plugins、apps、feature flags、config、requirements、provider diagnostics
|
|
103
|
+
|
|
104
|
+
**可靠性:**
|
|
83
105
|
- SQLite 持久化:绑定、offset、审批、待处理提示、审计日志
|
|
84
106
|
- 单实例进程锁,防止同一 bot token 重复 polling
|
|
85
107
|
|
|
86
108
|
</details>
|
|
87
109
|
|
|
110
|
+
## 多账号切换
|
|
111
|
+
|
|
112
|
+
FoxClaw 的一大特色是自动多账号切换。当一个账号的 5 小时用量限制触发时,FoxClaw 会自动切换到下一个可用账号继续工作。
|
|
113
|
+
|
|
114
|
+
设置方式:
|
|
115
|
+
|
|
116
|
+
1. 在 Codex 的 auth 目录(通常是 `~/.codex/`)中放置多个认证文件,命名为 `auth.json_personal`、`auth.json_team` 等。
|
|
117
|
+
2. 用 `/auth add <name>` 通过 Telegram 直接添加新账号。
|
|
118
|
+
3. 用 `/auth` 查看所有候选账号状态。
|
|
119
|
+
4. 用 `/auth enable <n>` / `/auth disable <n>` 控制哪些账号参与自动轮换。
|
|
120
|
+
|
|
121
|
+
当 Codex 报告用量限制错误时,FoxClaw 会自动:
|
|
122
|
+
- 切换到下一个未失败的候选账号
|
|
123
|
+
- 重启 app-server 加载新认证
|
|
124
|
+
- 用新账号重试刚才失败的请求
|
|
125
|
+
|
|
88
126
|
## 服务与调试
|
|
89
127
|
|
|
90
128
|
推荐方式:
|
|
@@ -241,11 +279,11 @@ foxclaw weixin-login
|
|
|
241
279
|
|
|
242
280
|
## Codex Skill
|
|
243
281
|
|
|
244
|
-
仓库自带一个 Codex skill
|
|
282
|
+
仓库自带一个 Codex skill。用法看 [FoxClaw Skill 中文说明](./docs/zh/foxclaw-skill.md)。它可以让 Codex 通过 SSH 在本机或远程 Mac 上 bootstrap FoxClaw——写 `.env`、构建、跑 doctor、装 launchd、引导首次消息验证,一条龙。
|
|
245
283
|
|
|
246
284
|
## 故障排查
|
|
247
285
|
|
|
248
|
-
`doctor` 报错、Telegram 没回复、服务日志看不懂、重启行为异常、迁移出问题——都看 [故障排查](./docs/troubleshooting.md)。
|
|
286
|
+
`doctor` 报错、Telegram 没回复、服务日志看不懂、重启行为异常、迁移出问题——都看 [故障排查](./docs/zh/troubleshooting.md)。
|
|
249
287
|
|
|
250
288
|
## 运维命令
|
|
251
289
|
|
package/README_EN.md
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
[中文](./README.md) | English
|
|
2
2
|
|
|
3
|
-
# FoxClaw
|
|
3
|
+
# 🦊 FoxClaw
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**A mobile Codex controller built for real programming workflows.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
FoxClaw turns your phone into a practical web coding cockpit for your local Codex. Telegram or Weixin handles the chat interface, `codex app-server` handles local execution, and you can send tasks, inspect progress, approve actions, switch threads, and keep working without opening a laptop.
|
|
8
|
+
|
|
9
|
+
It is built for the moments when you leave your desk for lunch, commute, travel, use a treadmill, or take the kids to the park. You can step away from the keyboard while Codex keeps coding and sends progress, errors, approval requests, and final results back to your phone.
|
|
10
|
+
|
|
11
|
+
No public server required. FoxClaw runs on your own computer, talks to `codex app-server` locally, and keeps code, shell access, auth, approvals, and runtime data on that machine.
|
|
12
|
+
|
|
13
|
+
## Why FoxClaw
|
|
14
|
+
|
|
15
|
+
**Why Codex as the underlying engine?**
|
|
16
|
+
|
|
17
|
+
1. **Open source with complete APIs** — Codex is OpenAI's open-source CLI agent and ships `codex app-server`. FoxClaw does not scrape a terminal; it uses the app-server interface to read threads, switch models, handle approvals, and resume sessions.
|
|
18
|
+
2. **Strong current coding experience** — FoxClaw reads the model list from your local Codex app-server. If your Codex environment has GPT-5.5 available, you can select and use it from your phone. For many heavy coding workflows, Codex/GPT-5.5 is already the reason to choose this stack.
|
|
19
|
+
3. **Multi-account quota rotation** — Free quota, trial Plus/Team accounts, and small account-specific allowances can all live as local `auth.json_*` candidates. When one account hits a 5-hour usage limit, FoxClaw switches to the next available account, restarts app-server, and retries the failed request.
|
|
20
|
+
|
|
21
|
+
**Built for these scenarios:**
|
|
22
|
+
|
|
23
|
+
- 🍜 Leave your desk for lunch — Codex keeps coding, tap to approve when needed
|
|
24
|
+
- 🚶 Commute or travel — dispatch tasks, inspect progress, and continue debugging without a laptop
|
|
25
|
+
- 🏃 Use a treadmill or spend time at the park — monitor Codex's coding process from your phone
|
|
26
|
+
- 🔒 Code, shell, auth, approvals, and runtime data stay on your machine — nothing exposed to the public internet
|
|
27
|
+
- 👤 Only one trusted Telegram user can operate the bot
|
|
8
28
|
|
|
9
29
|
## Start Here
|
|
10
30
|
|
|
@@ -13,15 +33,9 @@ It runs on your own computer and lets a trusted Telegram or Weixin chat control
|
|
|
13
33
|
- Already comfortable with Git, Node, and `.env` files? Use the quick setup below.
|
|
14
34
|
- Something failed? Check [Troubleshooting](./docs/troubleshooting.md).
|
|
15
35
|
|
|
16
|
-
|
|
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.
|
|
36
|
+
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
37
|
|
|
24
|
-
30-second
|
|
38
|
+
**30-second demo**: 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
39
|
|
|
26
40
|
## Requirements
|
|
27
41
|
|
|
@@ -31,7 +45,7 @@ The minimum install needs only a Telegram bot token, your numeric Telegram user
|
|
|
31
45
|
- A Telegram bot token from `@BotFather`
|
|
32
46
|
- Your Telegram numeric user id
|
|
33
47
|
|
|
34
|
-
##
|
|
48
|
+
## Quick Setup
|
|
35
49
|
|
|
36
50
|
```bash
|
|
37
51
|
npm install -g @foxden-app/foxclaw
|
|
@@ -41,7 +55,7 @@ foxclaw doctor
|
|
|
41
55
|
foxclaw start
|
|
42
56
|
```
|
|
43
57
|
|
|
44
|
-
pnpm users
|
|
58
|
+
pnpm users:
|
|
45
59
|
|
|
46
60
|
```bash
|
|
47
61
|
pnpm add -g @foxden-app/foxclaw
|
|
@@ -63,28 +77,52 @@ DEFAULT_SANDBOX_MODE=workspace-write
|
|
|
63
77
|
|
|
64
78
|
The default config file is `~/.foxclaw/.env`. Set `FOXCLAW_ENV=/path/to/.env` if you want to keep it somewhere else.
|
|
65
79
|
|
|
66
|
-
`foxclaw start` runs checks and installs or restarts the background service. It is idempotent
|
|
80
|
+
`foxclaw start` runs checks and installs or restarts the background service. It is idempotent — run it again after upgrading.
|
|
67
81
|
|
|
68
82
|
FoxClaw accepts messages only from `TG_ALLOWED_USER_ID`. Putting the bot in a group does not make it available to every group member.
|
|
69
83
|
|
|
70
84
|
<details>
|
|
71
85
|
<summary>What FoxClaw can do after it is running</summary>
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
- Codex account controls with `/account`, `/quota`, `/login_device`, `/login_cancel`, `/auth add <name>`, and guarded `/logout confirm`
|
|
79
|
-
- Automatic local Codex auth rotation across `auth.json_*` candidates when a usage-limit auth fails
|
|
80
|
-
- Inline approval buttons for command, file-change, and granular permission approvals
|
|
87
|
+
**Core capabilities:**
|
|
88
|
+
- Telegram private chat, group, and topic control for your local Codex
|
|
89
|
+
- Optional Weixin/iLink channel sharing the same bridge core
|
|
90
|
+
- Full thread lifecycle management from mobile: create, rename, archive, fork, rollback, compact, review, diff
|
|
91
|
+
- Inline approval buttons for commands, file changes, and granular permissions — one tap to approve
|
|
81
92
|
- MCP elicitation cards for structured questions raised by tools during a turn
|
|
93
|
+
|
|
94
|
+
**Multi-account management:**
|
|
95
|
+
- Codex account controls: `/account`, `/quota`, `/login_device`, `/auth add <name>`
|
|
96
|
+
- Automatic auth rotation across local `auth.json_*` files when a usage limit is hit — seamless account switching
|
|
97
|
+
- `/auth` panel to view, enable, disable, and switch between candidate accounts
|
|
98
|
+
|
|
99
|
+
**Threads and sessions:**
|
|
100
|
+
- `/threads`, `/open`, `/new`, `/where`, `/interrupt` — sticky chat-to-thread binding
|
|
101
|
+
- Chat-scoped setup panel for model, reasoning effort, Fast tier, access preset, Agent/Plan mode
|
|
82
102
|
- Skills, MCP, hooks, plugins, apps, feature flags, config, requirements, and provider diagnostics
|
|
103
|
+
|
|
104
|
+
**Reliability:**
|
|
83
105
|
- SQLite persistence for bindings, offsets, approvals, pending input prompts, and audit logs
|
|
84
106
|
- Single-instance process lock to prevent duplicate Telegram polling on the same bot token
|
|
85
107
|
|
|
86
108
|
</details>
|
|
87
109
|
|
|
110
|
+
## Multi-Account Rotation
|
|
111
|
+
|
|
112
|
+
A key FoxClaw feature is automatic multi-account switching. When one account's 5-hour usage limit is triggered, FoxClaw automatically switches to the next available account and continues working.
|
|
113
|
+
|
|
114
|
+
Setup:
|
|
115
|
+
|
|
116
|
+
1. Place multiple auth files in the Codex auth directory (usually `~/.codex/`), named like `auth.json_personal`, `auth.json_team`, etc.
|
|
117
|
+
2. Use `/auth add <name>` to add new accounts directly from Telegram.
|
|
118
|
+
3. Use `/auth` to view all candidate account statuses.
|
|
119
|
+
4. Use `/auth enable <n>` / `/auth disable <n>` to control which accounts participate in auto-rotation.
|
|
120
|
+
|
|
121
|
+
When Codex reports a usage-limit error, FoxClaw automatically:
|
|
122
|
+
- Switches to the next non-failed candidate account
|
|
123
|
+
- Restarts app-server to load the new auth
|
|
124
|
+
- Retries the failed request with the new account
|
|
125
|
+
|
|
88
126
|
## Service And Debugging
|
|
89
127
|
|
|
90
128
|
Recommended:
|
|
@@ -108,13 +146,15 @@ foxclaw serve
|
|
|
108
146
|
|
|
109
147
|
Default runtime files are stored under `~/.foxclaw`:
|
|
110
148
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
149
|
+
| Purpose | Path |
|
|
150
|
+
|---------|------|
|
|
151
|
+
| Database | `~/.foxclaw/data/bridge.sqlite` |
|
|
152
|
+
| Bridge log | `~/.foxclaw/logs/service.log` |
|
|
153
|
+
| Status | `~/.foxclaw/runtime/status.json` |
|
|
154
|
+
| App-server state | `~/.foxclaw/runtime/codex-app-server.json` |
|
|
155
|
+
| App-server log | `~/.foxclaw/logs/codex-app-server.log` |
|
|
116
156
|
|
|
117
|
-
Override
|
|
157
|
+
Override with `STORE_PATH`, `LOCK_PATH`, `CODEX_APP_SERVER_STATE_PATH`, and `CODEX_APP_SERVER_LOG_PATH`.
|
|
118
158
|
|
|
119
159
|
## Migrating From telegram-codex-app-bridge
|
|
120
160
|
|
|
@@ -156,7 +196,7 @@ TG_ALLOWED_TOPIC_ID=42
|
|
|
156
196
|
- Set both `TG_ALLOWED_CHAT_ID` and `TG_ALLOWED_TOPIC_ID` to bind one topic as the default scope.
|
|
157
197
|
- Private chat remains available for `TG_ALLOWED_USER_ID` even when a group is configured.
|
|
158
198
|
|
|
159
|
-
|
|
199
|
+
**How to find group and topic IDs:**
|
|
160
200
|
|
|
161
201
|
1. Stop FoxClaw.
|
|
162
202
|
2. Send a message in the target group or topic.
|
|
@@ -197,7 +237,7 @@ No static Codex app-server port is required in normal installs.
|
|
|
197
237
|
## Commands
|
|
198
238
|
|
|
199
239
|
- `/help`
|
|
200
|
-
- `/setup`
|
|
240
|
+
- `/setup` — unified preference panel
|
|
201
241
|
- `/fast <on|off|toggle>`
|
|
202
242
|
- `/active <steer|queue>`
|
|
203
243
|
- `/status`, `/account`, `/quota`
|
|
@@ -215,7 +255,7 @@ No static Codex app-server port is required in normal installs.
|
|
|
215
255
|
- `/skills [query]`, `/skill <name>`, `/skill_enable <name>`, `/skill_disable <name>`
|
|
216
256
|
- `/loaded`, `/hooks`, `/plugins [query]`, `/apps [reload]`, `/features`, `/config`, `/requirements`, `/provider`
|
|
217
257
|
- `/mcp`, `/mcp_reload`, `/mcp_login <server>`, `/mcp_resource <server> <uri>`
|
|
218
|
-
- `/models`, `/model`, `/effort`, `/permissions`, `/access`, `/mode`, `/plan`,
|
|
258
|
+
- `/models`, `/model`, `/effort`, `/permissions`, `/access`, `/mode`, `/plan`, `/agent`
|
|
219
259
|
- `/reveal`, `/where`, `/interrupt`
|
|
220
260
|
|
|
221
261
|
Plain text sends to the current thread, or creates a new one if none is bound.
|
|
@@ -239,7 +279,7 @@ Weixin runtime files default to `~/.foxclaw/weixin`.
|
|
|
239
279
|
|
|
240
280
|
## Codex Skill
|
|
241
281
|
|
|
242
|
-
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
|
|
282
|
+
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.
|
|
243
283
|
|
|
244
284
|
## Troubleshooting
|
|
245
285
|
|
|
@@ -256,4 +296,4 @@ foxclaw uninstall-systemd
|
|
|
256
296
|
|
|
257
297
|
## Contributing
|
|
258
298
|
|
|
259
|
-
Issues and PRs are welcome at
|
|
299
|
+
Issues and PRs are welcome at [GitHub](https://github.com/foxden-app/foxclaw).
|
|
@@ -6980,7 +6980,7 @@ function renderAuthListMessage(locale, state, includeWeixinCopyPaste = false) {
|
|
|
6980
6980
|
function authChoiceKeyboard(locale, record) {
|
|
6981
6981
|
const rows = record.candidates.map((candidate, index) => [
|
|
6982
6982
|
{
|
|
6983
|
-
text: clipButtonText(`${candidate.isCurrent ? '
|
|
6983
|
+
text: clipButtonText(`${candidate.isCurrent ? '✅ ' : '🔐 '}${candidate.name}${candidate.disabled ? ' · off' : ''}`),
|
|
6984
6984
|
callback_data: `auth:${record.localId}:${index}`,
|
|
6985
6985
|
},
|
|
6986
6986
|
{
|
|
@@ -38,7 +38,7 @@ export function buildThreadsKeyboard(locale, threads) {
|
|
|
38
38
|
return threads.flatMap((thread, index) => {
|
|
39
39
|
const ordinal = typeof thread.index === 'number' ? thread.index : index + 1;
|
|
40
40
|
const openRow = [{
|
|
41
|
-
text:
|
|
41
|
+
text: `🧵 ${ordinal}. ${truncate(compactWhitespace(thread.name || thread.preview || t(locale, 'empty')), 28)}`,
|
|
42
42
|
callback_data: `thread:open:${thread.threadId}`,
|
|
43
43
|
}];
|
|
44
44
|
const actionRow = thread.archived
|
|
@@ -110,15 +110,15 @@ export function buildAccessSettingsKeyboard(locale, access) {
|
|
|
110
110
|
const currentPreset = access.preset;
|
|
111
111
|
const buttons = [
|
|
112
112
|
{
|
|
113
|
-
text:
|
|
113
|
+
text: selectedButtonText(currentPreset === 'read-only', accessPresetButtonLabel(locale, 'read-only')),
|
|
114
114
|
callback_data: 'settings:access:read-only',
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
|
-
text:
|
|
117
|
+
text: selectedButtonText(currentPreset === 'default', accessPresetButtonLabel(locale, 'default')),
|
|
118
118
|
callback_data: 'settings:access:default',
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
|
-
text:
|
|
121
|
+
text: selectedButtonText(currentPreset === 'full-access', accessPresetButtonLabel(locale, 'full-access')),
|
|
122
122
|
callback_data: 'settings:access:full-access',
|
|
123
123
|
},
|
|
124
124
|
];
|
|
@@ -256,7 +256,7 @@ export function buildSetupPanelKeyboard(locale, ctx) {
|
|
|
256
256
|
callback_data: 'setup:model:default',
|
|
257
257
|
},
|
|
258
258
|
...ctx.models.map((model) => ({
|
|
259
|
-
text:
|
|
259
|
+
text: selectedButtonText(currentModel === model.model, `🤖 ${truncate(model.model, 14)}`),
|
|
260
260
|
callback_data: `setup:model:${encodeURIComponent(model.model)}`,
|
|
261
261
|
})),
|
|
262
262
|
], 2));
|
|
@@ -266,7 +266,7 @@ export function buildSetupPanelKeyboard(locale, ctx) {
|
|
|
266
266
|
callback_data: 'setup:effort:default',
|
|
267
267
|
},
|
|
268
268
|
...efforts.map((effort) => ({
|
|
269
|
-
text:
|
|
269
|
+
text: selectedButtonText(ctx.settings?.reasoningEffort === effort, `🧠 ${effort}`),
|
|
270
270
|
callback_data: `setup:effort:${effort}`,
|
|
271
271
|
})),
|
|
272
272
|
], 3));
|
|
@@ -287,35 +287,35 @@ export function buildSetupPanelKeyboard(locale, ctx) {
|
|
|
287
287
|
}
|
|
288
288
|
rows.push([
|
|
289
289
|
{
|
|
290
|
-
text:
|
|
290
|
+
text: selectedButtonText(ctx.access.preset === 'read-only', accessPresetButtonLabel(locale, 'read-only')),
|
|
291
291
|
callback_data: 'setup:access:read-only',
|
|
292
292
|
},
|
|
293
293
|
{
|
|
294
|
-
text:
|
|
294
|
+
text: selectedButtonText(ctx.access.preset === 'default', accessPresetButtonLabel(locale, 'default')),
|
|
295
295
|
callback_data: 'setup:access:default',
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
|
-
text:
|
|
298
|
+
text: selectedButtonText(ctx.access.preset === 'full-access', accessPresetButtonLabel(locale, 'full-access')),
|
|
299
299
|
callback_data: 'setup:access:full-access',
|
|
300
300
|
},
|
|
301
301
|
]);
|
|
302
302
|
rows.push([
|
|
303
303
|
{
|
|
304
|
-
text:
|
|
304
|
+
text: selectedButtonText(currentMode === 'default', `🤖 ${t(locale, 'collaboration_mode_default')}`),
|
|
305
305
|
callback_data: 'setup:mode:default',
|
|
306
306
|
},
|
|
307
307
|
{
|
|
308
|
-
text:
|
|
308
|
+
text: selectedButtonText(currentMode === 'plan', `📝 ${t(locale, 'collaboration_mode_plan')}`),
|
|
309
309
|
callback_data: 'setup:mode:plan',
|
|
310
310
|
},
|
|
311
311
|
]);
|
|
312
312
|
rows.push([
|
|
313
313
|
{
|
|
314
|
-
text:
|
|
314
|
+
text: selectedButtonText(activeTurnMessageMode === 'steer', `🎯 ${t(locale, 'active_turn_message_mode_steer')}`),
|
|
315
315
|
callback_data: 'setup:active:steer',
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
|
-
text:
|
|
318
|
+
text: selectedButtonText(activeTurnMessageMode === 'queue', `📥 ${t(locale, 'active_turn_message_mode_queue')}`),
|
|
319
319
|
callback_data: 'setup:active:queue',
|
|
320
320
|
},
|
|
321
321
|
]);
|
|
@@ -382,6 +382,16 @@ function formatFastSetupLabel(locale, supported, enabled, tierName) {
|
|
|
382
382
|
function resolveCollaborationMode(mode) {
|
|
383
383
|
return mode === 'plan' ? 'plan' : 'default';
|
|
384
384
|
}
|
|
385
|
+
function selectedButtonText(selected, label) {
|
|
386
|
+
return `${selected ? '• ' : ''}${label}`;
|
|
387
|
+
}
|
|
388
|
+
function accessPresetButtonLabel(locale, preset) {
|
|
389
|
+
if (preset === 'read-only')
|
|
390
|
+
return `👁️ ${formatAccessPresetLabel(locale, preset)}`;
|
|
391
|
+
if (preset === 'full-access')
|
|
392
|
+
return `🔓 ${formatAccessPresetLabel(locale, preset)}`;
|
|
393
|
+
return `🛡️ ${formatAccessPresetLabel(locale, preset)}`;
|
|
394
|
+
}
|
|
385
395
|
export function formatModelSettingsMessage(locale, models, settings) {
|
|
386
396
|
const selectedModel = resolveCurrentModel(models, settings?.model ?? null);
|
|
387
397
|
const selectedModelLabel = settings?.model ?? t(locale, 'server_default');
|
|
@@ -417,7 +427,7 @@ export function buildModelSettingsKeyboard(locale, models, settings) {
|
|
|
417
427
|
callback_data: 'settings:model:default',
|
|
418
428
|
},
|
|
419
429
|
...models.map((model) => ({
|
|
420
|
-
text:
|
|
430
|
+
text: selectedButtonText(currentModel === model.model, `🤖 ${truncate(model.model, 14)}`),
|
|
421
431
|
callback_data: `settings:model:${encodeURIComponent(model.model)}`,
|
|
422
432
|
})),
|
|
423
433
|
];
|
|
@@ -427,7 +437,7 @@ export function buildModelSettingsKeyboard(locale, models, settings) {
|
|
|
427
437
|
callback_data: 'settings:effort:default',
|
|
428
438
|
},
|
|
429
439
|
...efforts.map((effort) => ({
|
|
430
|
-
text:
|
|
440
|
+
text: selectedButtonText(settings?.reasoningEffort === effort, `🧠 ${effort}`),
|
|
431
441
|
callback_data: `settings:effort:${effort}`,
|
|
432
442
|
})),
|
|
433
443
|
];
|
package/dist/i18n.d.ts
CHANGED
|
@@ -148,10 +148,10 @@ declare const MESSAGES: {
|
|
|
148
148
|
readonly auth_add_cancelled: "New auth login cancelled. Restored previous auth.";
|
|
149
149
|
readonly auth_add_reverted: "Restored previous auth.";
|
|
150
150
|
readonly auth_add_missing_file: "Login completed, but the new auth file was not created: {value}";
|
|
151
|
-
readonly button_login_device: "Login
|
|
152
|
-
readonly button_auth_reload: "Reload auth";
|
|
153
|
-
readonly button_auth_enable: "Enable";
|
|
154
|
-
readonly button_auth_disable: "Disable";
|
|
151
|
+
readonly button_login_device: "🔑 Login";
|
|
152
|
+
readonly button_auth_reload: "🔄 Reload auth";
|
|
153
|
+
readonly button_auth_enable: "✅ Enable";
|
|
154
|
+
readonly button_auth_disable: "⏸️ Disable";
|
|
155
155
|
readonly another_turn_running: "Another turn is already running. Use /interrupt, /takeover, /queue, or wait.";
|
|
156
156
|
readonly working: "Working...";
|
|
157
157
|
readonly usage_open: "Usage: /open <n>";
|
|
@@ -295,16 +295,16 @@ declare const MESSAGES: {
|
|
|
295
295
|
readonly approval_decision_accept: "allow";
|
|
296
296
|
readonly approval_decision_session: "allow for session";
|
|
297
297
|
readonly approval_decision_deny: "deny";
|
|
298
|
-
readonly button_allow: "Allow";
|
|
299
|
-
readonly button_allow_session: "
|
|
300
|
-
readonly button_deny: "Deny";
|
|
301
|
-
readonly button_interrupt: "Interrupt";
|
|
302
|
-
readonly button_permissions: "Access";
|
|
303
|
-
readonly button_models: "Models";
|
|
304
|
-
readonly button_threads: "Threads";
|
|
305
|
-
readonly button_reveal: "Reveal";
|
|
306
|
-
readonly button_auto: "Auto";
|
|
307
|
-
readonly button_new_thread: "New";
|
|
298
|
+
readonly button_allow: "✅ Allow";
|
|
299
|
+
readonly button_allow_session: "✅ Session";
|
|
300
|
+
readonly button_deny: "🚫 Deny";
|
|
301
|
+
readonly button_interrupt: "⏹️ Interrupt";
|
|
302
|
+
readonly button_permissions: "🛡️ Access";
|
|
303
|
+
readonly button_models: "🤖 Models";
|
|
304
|
+
readonly button_threads: "🧵 Threads";
|
|
305
|
+
readonly button_reveal: "↗️ Reveal";
|
|
306
|
+
readonly button_auto: "⚙️ Auto";
|
|
307
|
+
readonly button_new_thread: "➕ New";
|
|
308
308
|
readonly threads_no_matches: "<b>No threads matched</b>\n<code>{searchTerm}</code>";
|
|
309
309
|
readonly threads_no_recent: "<b>No recent threads.</b>";
|
|
310
310
|
readonly threads_recent_title: "<b>Recent threads</b>";
|
|
@@ -313,15 +313,15 @@ declare const MESSAGES: {
|
|
|
313
313
|
readonly threads_filter: "Filter: <code>{searchTerm}</code>";
|
|
314
314
|
readonly threads_range: "Showing {start}-{end}";
|
|
315
315
|
readonly threads_filter_cleared_short: "Filter cleared";
|
|
316
|
-
readonly button_prev_page: "Prev";
|
|
317
|
-
readonly button_next_page: "Next";
|
|
318
|
-
readonly button_clear_filter: "Clear
|
|
319
|
-
readonly button_recent_threads: "Recent";
|
|
320
|
-
readonly button_archived_threads: "Archived";
|
|
321
|
-
readonly button_thread_rename: "Rename";
|
|
322
|
-
readonly button_thread_watch: "Watch";
|
|
323
|
-
readonly button_thread_archive: "Archive/Delete";
|
|
324
|
-
readonly button_thread_unarchive: "Unarchive";
|
|
316
|
+
readonly button_prev_page: "⬅️ Prev";
|
|
317
|
+
readonly button_next_page: "➡️ Next";
|
|
318
|
+
readonly button_clear_filter: "🧹 Clear";
|
|
319
|
+
readonly button_recent_threads: "🕘 Recent";
|
|
320
|
+
readonly button_archived_threads: "🗄️ Archived";
|
|
321
|
+
readonly button_thread_rename: "✏️ Rename";
|
|
322
|
+
readonly button_thread_watch: "👀 Watch";
|
|
323
|
+
readonly button_thread_archive: "🗑️ Archive/Delete";
|
|
324
|
+
readonly button_thread_unarchive: "♻️ Unarchive";
|
|
325
325
|
readonly threads_current: "Current: <b>{title}</b>";
|
|
326
326
|
readonly where_no_thread_bound: "No thread is currently bound.";
|
|
327
327
|
readonly where_send_message_or_new: "Send a message or use /new.";
|
|
@@ -370,8 +370,8 @@ declare const MESSAGES: {
|
|
|
370
370
|
readonly active_turn_message_mode_steer: "Steer current turn";
|
|
371
371
|
readonly active_turn_message_mode_queue: "Queue next turn";
|
|
372
372
|
readonly button_fast_on: "⚡ Fast: on";
|
|
373
|
-
readonly button_fast_off: "Fast: off";
|
|
374
|
-
readonly button_fast_unsupported: "Fast unsupported";
|
|
373
|
+
readonly button_fast_off: "⚪ Fast: off";
|
|
374
|
+
readonly button_fast_unsupported: "⛔ Fast unsupported";
|
|
375
375
|
readonly access_preset_read_only: "Read-only";
|
|
376
376
|
readonly access_preset_default: "Default";
|
|
377
377
|
readonly access_preset_full_access: "Full access";
|
|
@@ -506,10 +506,10 @@ declare const MESSAGES: {
|
|
|
506
506
|
readonly permission_read_paths: "Read paths: {value}";
|
|
507
507
|
readonly permission_write_paths: "Write paths: {value}";
|
|
508
508
|
readonly permission_entries: "File entries: {value}";
|
|
509
|
-
readonly button_accept: "Accept";
|
|
510
|
-
readonly button_decline: "Decline";
|
|
511
|
-
readonly button_cancel: "Cancel";
|
|
512
|
-
readonly button_create_dir: "Create";
|
|
509
|
+
readonly button_accept: "✅ Accept";
|
|
510
|
+
readonly button_decline: "🚫 Decline";
|
|
511
|
+
readonly button_cancel: "✖️ Cancel";
|
|
512
|
+
readonly button_create_dir: "📁 Create";
|
|
513
513
|
readonly thread_name_updated: "Thread renamed: {name}";
|
|
514
514
|
readonly thread_archived_notification: "Thread archived: {threadId}";
|
|
515
515
|
readonly thread_unarchived_notification: "Thread unarchived: {threadId}";
|
|
@@ -696,10 +696,10 @@ declare const MESSAGES: {
|
|
|
696
696
|
readonly auth_add_cancelled: "新 auth 登录已取消,已恢复之前的 auth。";
|
|
697
697
|
readonly auth_add_reverted: "已恢复之前的 auth。";
|
|
698
698
|
readonly auth_add_missing_file: "登录已完成,但没有创建新的 auth 文件:{value}";
|
|
699
|
-
readonly button_login_device: "设备登录";
|
|
700
|
-
readonly button_auth_reload: "重载 auth";
|
|
701
|
-
readonly button_auth_enable: "启用";
|
|
702
|
-
readonly button_auth_disable: "禁用";
|
|
699
|
+
readonly button_login_device: "🔑 设备登录";
|
|
700
|
+
readonly button_auth_reload: "🔄 重载 auth";
|
|
701
|
+
readonly button_auth_enable: "✅ 启用";
|
|
702
|
+
readonly button_auth_disable: "⏸️ 禁用";
|
|
703
703
|
readonly another_turn_running: "已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。";
|
|
704
704
|
readonly working: "处理中...";
|
|
705
705
|
readonly usage_open: "用法:/open <编号>";
|
|
@@ -843,16 +843,16 @@ declare const MESSAGES: {
|
|
|
843
843
|
readonly approval_decision_accept: "允许";
|
|
844
844
|
readonly approval_decision_session: "本会话内允许";
|
|
845
845
|
readonly approval_decision_deny: "拒绝";
|
|
846
|
-
readonly button_allow: "允许";
|
|
847
|
-
readonly button_allow_session: "
|
|
848
|
-
readonly button_deny: "拒绝";
|
|
849
|
-
readonly button_interrupt: "中断";
|
|
850
|
-
readonly button_permissions: "权限";
|
|
851
|
-
readonly button_models: "模型";
|
|
852
|
-
readonly button_threads: "线程";
|
|
853
|
-
readonly button_reveal: "打开";
|
|
854
|
-
readonly button_auto: "自动";
|
|
855
|
-
readonly button_new_thread: "新建";
|
|
846
|
+
readonly button_allow: "✅ 允许";
|
|
847
|
+
readonly button_allow_session: "✅ 本会话";
|
|
848
|
+
readonly button_deny: "🚫 拒绝";
|
|
849
|
+
readonly button_interrupt: "⏹️ 中断";
|
|
850
|
+
readonly button_permissions: "🛡️ 权限";
|
|
851
|
+
readonly button_models: "🤖 模型";
|
|
852
|
+
readonly button_threads: "🧵 线程";
|
|
853
|
+
readonly button_reveal: "↗️ 打开";
|
|
854
|
+
readonly button_auto: "⚙️ 自动";
|
|
855
|
+
readonly button_new_thread: "➕ 新建";
|
|
856
856
|
readonly threads_no_matches: "<b>没有匹配的线程</b>\n<code>{searchTerm}</code>";
|
|
857
857
|
readonly threads_no_recent: "<b>暂无最近线程。</b>";
|
|
858
858
|
readonly threads_recent_title: "<b>最近线程</b>";
|
|
@@ -861,15 +861,15 @@ declare const MESSAGES: {
|
|
|
861
861
|
readonly threads_filter: "筛选:<code>{searchTerm}</code>";
|
|
862
862
|
readonly threads_range: "显示第 {start}-{end} 条";
|
|
863
863
|
readonly threads_filter_cleared_short: "已清除筛选";
|
|
864
|
-
readonly button_prev_page: "上一页";
|
|
865
|
-
readonly button_next_page: "下一页";
|
|
866
|
-
readonly button_clear_filter: "
|
|
867
|
-
readonly button_recent_threads: "最近线程";
|
|
868
|
-
readonly button_archived_threads: "已归档";
|
|
869
|
-
readonly button_thread_rename: "重命名";
|
|
870
|
-
readonly button_thread_watch: "观察";
|
|
871
|
-
readonly button_thread_archive: "归档/删除";
|
|
872
|
-
readonly button_thread_unarchive: "取消归档";
|
|
864
|
+
readonly button_prev_page: "⬅️ 上一页";
|
|
865
|
+
readonly button_next_page: "➡️ 下一页";
|
|
866
|
+
readonly button_clear_filter: "🧹 清除";
|
|
867
|
+
readonly button_recent_threads: "🕘 最近线程";
|
|
868
|
+
readonly button_archived_threads: "🗄️ 已归档";
|
|
869
|
+
readonly button_thread_rename: "✏️ 重命名";
|
|
870
|
+
readonly button_thread_watch: "👀 观察";
|
|
871
|
+
readonly button_thread_archive: "🗑️ 归档/删除";
|
|
872
|
+
readonly button_thread_unarchive: "♻️ 取消归档";
|
|
873
873
|
readonly threads_current: "当前:<b>{title}</b>";
|
|
874
874
|
readonly where_no_thread_bound: "当前没有绑定线程。";
|
|
875
875
|
readonly where_send_message_or_new: "直接发一条消息,或者使用 /new。";
|
|
@@ -918,8 +918,8 @@ declare const MESSAGES: {
|
|
|
918
918
|
readonly active_turn_message_mode_steer: "引导当前回复";
|
|
919
919
|
readonly active_turn_message_mode_queue: "排队到下一轮";
|
|
920
920
|
readonly button_fast_on: "⚡ Fast:开";
|
|
921
|
-
readonly button_fast_off: "Fast:关";
|
|
922
|
-
readonly button_fast_unsupported: "Fast 不支持";
|
|
921
|
+
readonly button_fast_off: "⚪ Fast:关";
|
|
922
|
+
readonly button_fast_unsupported: "⛔ Fast 不支持";
|
|
923
923
|
readonly access_preset_read_only: "只读";
|
|
924
924
|
readonly access_preset_default: "默认";
|
|
925
925
|
readonly access_preset_full_access: "完全访问";
|
|
@@ -1054,10 +1054,10 @@ declare const MESSAGES: {
|
|
|
1054
1054
|
readonly permission_read_paths: "可读路径:{value}";
|
|
1055
1055
|
readonly permission_write_paths: "可写路径:{value}";
|
|
1056
1056
|
readonly permission_entries: "文件条目:{value}";
|
|
1057
|
-
readonly button_accept: "接受";
|
|
1058
|
-
readonly button_decline: "拒绝";
|
|
1059
|
-
readonly button_cancel: "取消";
|
|
1060
|
-
readonly button_create_dir: "新建";
|
|
1057
|
+
readonly button_accept: "✅ 接受";
|
|
1058
|
+
readonly button_decline: "🚫 拒绝";
|
|
1059
|
+
readonly button_cancel: "✖️ 取消";
|
|
1060
|
+
readonly button_create_dir: "📁 新建";
|
|
1061
1061
|
readonly thread_name_updated: "线程已重命名:{name}";
|
|
1062
1062
|
readonly thread_archived_notification: "线程已归档:{threadId}";
|
|
1063
1063
|
readonly thread_unarchived_notification: "线程已取消归档:{threadId}";
|