@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 CHANGED
@@ -1,23 +1,37 @@
1
1
  中文 | [English](./README_EN.md)
2
2
 
3
- # FoxClaw
3
+ # 🦊 FoxClaw · 狸爪
4
4
 
5
- FoxClaw Foxden agents 的本地执行爪——跑在你自己电脑上,让你通过 Telegram 或微信远程操控本机的 Codex。
5
+ **狸爪:一个更适配编程需求的移动端 Codex 控制器。**
6
6
 
7
- 不需要公网服务器。FoxClaw 在本地和 `codex app-server` 通信,审批在你电脑上完成,结果推送回手机。
7
+ FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex,把移动端变成一个真正能用的 web coding 入口。Telegram 或微信负责交互,`codex app-server` 负责本地执行;你发任务、看进度、批审批、切线程,Codex 在电脑上继续写代码。
8
8
 
9
- ## 从这里开始
9
+ 这适合你离开办公桌去吃饭、在旅途中、在跑步机上,或者陪小孩逛公园的时候继续工作。人可以离开电脑,Codex 不必停;它会把关键进展、错误、审批请求和最终结果同步回手机。
10
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)。
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
- FoxClaw 适合这些场景:
21
+ **适合这些场景:**
17
22
 
18
- - 在手机上用 Codex,但不想把电脑暴露到公网
19
- - 代码、shell、认证、审批、运行数据全部留在本机
20
- - 只允许一个受信任的 Telegram 用户远程操作
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
- - 单个 Telegram 用户通过私聊、群组、话题控制
87
+ **核心能力:**
88
+ - 通过 Telegram 私聊、群组、话题控制本地 Codex
74
89
  - 可选微信/iLink 通道,复用同一套桥接核心
75
- - `/threads`、`/open`、`/new`、`/where`、`/interrupt`——稳定的聊天-线程绑定
76
- - 手机上管理线程生命周期:重命名、归档、取消归档、fork、回滚、compact、review、diff
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[`skills/foxclaw`](./skills/foxclaw)。用它可以让 Codex 通过 SSH 在本机或远程 Mac 上 bootstrap FoxClaw——写 `.env`、构建、跑 doctor、装 launchd、引导首次消息验证,一条龙。
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
- FoxClaw is the local execution claw for Foxden agents.
5
+ **A mobile Codex controller built for real programming workflows.**
6
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.
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
- 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.
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 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.
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
- ## npm Quick Setup
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 can use:
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, so run it again after upgrading.
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
- - Telegram private chat, group, and topic control for one allowed Telegram user
74
- - Optional Weixin/iLink channel for the same bridge core
75
- - Sticky chat-to-thread binding with `/threads`, `/open`, `/new`, `/where`, and `/interrupt`
76
- - Thread lifecycle controls from mobile: rename, archive, unarchive, fork, rollback, compact, review, and diff
77
- - Chat-scoped setup panel for model, reasoning effort, Fast service tier, access preset, Agent/Plan mode, and active-turn behavior
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
- - store: `~/.foxclaw/data/bridge.sqlite`
112
- - bridge log: `~/.foxclaw/logs/service.log`
113
- - status: `~/.foxclaw/runtime/status.json`
114
- - app-server state: `~/.foxclaw/runtime/codex-app-server.json`
115
- - app-server log: `~/.foxclaw/logs/codex-app-server.log`
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 the store, lock, and app-server paths with `STORE_PATH`, `LOCK_PATH`, `CODEX_APP_SERVER_STATE_PATH`, and `CODEX_APP_SERVER_LOG_PATH`.
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
- To discover group and topic ids:
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` opens the unified preference panel
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`, and `/agent`
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, write `.env`, build, run doctor, install launchd, and guide first-message validation.
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 `https://github.com/foxden-app/foxclaw`.
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 ? '* ' : ''}${candidate.name}${candidate.disabled ? ' off' : ''}`),
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: `${ordinal}. ${truncate(compactWhitespace(thread.name || thread.preview || t(locale, 'empty')), 28)}`,
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: `${currentPreset === 'read-only' ? '• ' : ''}${t(locale, 'access_preset_read_only')}`,
113
+ text: selectedButtonText(currentPreset === 'read-only', accessPresetButtonLabel(locale, 'read-only')),
114
114
  callback_data: 'settings:access:read-only',
115
115
  },
116
116
  {
117
- text: `${currentPreset === 'default' ? '• ' : ''}${t(locale, 'access_preset_default')}`,
117
+ text: selectedButtonText(currentPreset === 'default', accessPresetButtonLabel(locale, 'default')),
118
118
  callback_data: 'settings:access:default',
119
119
  },
120
120
  {
121
- text: `${currentPreset === 'full-access' ? '• ' : ''}${t(locale, 'access_preset_full_access')}`,
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: `${currentModel === model.model ? '• ' : ''}${truncate(model.model, 14)}`,
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: `${ctx.settings?.reasoningEffort === effort ? '• ' : ''}${effort}`,
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: `${ctx.access.preset === 'read-only' ? '• ' : ''}${t(locale, 'access_preset_read_only')}`,
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: `${ctx.access.preset === 'default' ? '• ' : ''}${t(locale, 'access_preset_default')}`,
294
+ text: selectedButtonText(ctx.access.preset === 'default', accessPresetButtonLabel(locale, 'default')),
295
295
  callback_data: 'setup:access:default',
296
296
  },
297
297
  {
298
- text: `${ctx.access.preset === 'full-access' ? '• ' : ''}${t(locale, 'access_preset_full_access')}`,
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: `${currentMode === 'default' ? '• ' : ''}${t(locale, 'collaboration_mode_default')}`,
304
+ text: selectedButtonText(currentMode === 'default', `🤖 ${t(locale, 'collaboration_mode_default')}`),
305
305
  callback_data: 'setup:mode:default',
306
306
  },
307
307
  {
308
- text: `${currentMode === 'plan' ? '• ' : ''}${t(locale, 'collaboration_mode_plan')}`,
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: `${activeTurnMessageMode === 'steer' ? '• ' : ''}${t(locale, 'active_turn_message_mode_steer')}`,
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: `${activeTurnMessageMode === 'queue' ? '• ' : ''}${t(locale, 'active_turn_message_mode_queue')}`,
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: `${currentModel === model.model ? '• ' : ''}${truncate(model.model, 14)}`,
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: `${settings?.reasoningEffort === effort ? '• ' : ''}${effort}`,
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 device";
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: "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 filter";
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}";