用命令行智能体写代码时,真正消耗注意力的往往不是思考,而是反复出现的几个动作:接受改动、退回上一版、清空上下文重开、换个模型再试。这些动作每次都要把手从思路里抽出来,去找 Enter、Esc、Ctrl+C,或者敲一串斜杠命令。
这篇教程要做的东西很具体:把一只几十元的小米蓝牙遥控器 2 Pro 配对到电脑上,当成一个只有十来个按键的实体快捷键手柄。手边一按,动作就送到正在跑的智能体会话里。价格以购买渠道当前标价为准,不同时间会浮动。
做出来的效果长这样:左边终端里 Claude Code、Codex CLI 或 aider 正在跑,右边是编辑器。方案看着不错,拇指按下遥控器中间的 OK,等于回车,改动被接受;方案跑偏了,按返回键,等于连按两次 Esc 打开回滚;上下文已经脏了,按主页键,等于 Ctrl+C 之后输入 /new 再回车;想换个模型重问,按菜单键,等于输入 /model 再回车。中间不需要把视线移到键盘上。
整个过程不需要写驱动程序。遥控器在电脑眼里就是一只蓝牙键盘,你要做的只是把一个按键翻译成一个动作。
前置条件清单
- 小米蓝牙遥控器 2 Pro 一只,装上电池,按键时指示灯有反应。
- 一台带蓝牙的电脑。macOS、Windows、Linux 都可以走通;台式机没有蓝牙,插一个 USB 蓝牙适配器即可。
- 一个已经能正常跑起来的命令行智能体,并且你知道它这几个动作对应什么:接受改动、回滚、开新会话、切换模型。不同工具的按键和斜杠命令不一样,以该工具官方文档的当前版本为准。
- 平台对应的按键重映射工具:
- macOS:Karabiner-Elements
- Windows:AutoHotkey v2,也可以用 PowerToys 的 Keyboard Manager,但它按设备区分按键的能力有限
- Linux:python-evdev 配合 wtype(Wayland)或 xdotool(X11)
- 十来分钟不被打断的时间,以及一个可以随便按、按错了也无所谓的空白终端窗口。
步骤一:先弄清它在系统里是什么设备
遥控器出厂时一般已经绑在电视或盒子上。配对前先在原设备上解绑,或者把电视关机、把遥控器拿远,否则它会优先连回老设备。
进入配对模式通常靠长按某两个键,常见组合是同时长按主页键和返回键若干秒,直到指示灯快闪。不同批次可能不一样,以说明书或官方页面为准。然后打开系统蓝牙设置,找名字类似 Xiaomi Bluetooth Remote 2 Pro 的设备。
Linux 下用命令行更直观:
```bash
bluetoothctl
[bluetooth]# power on
[bluetooth]# agent on
[bluetooth]# scan on
此时让遥控器进入配对模式,等它出现在扫描列表里
[bluetooth]# pair XX:XX:XX:XX:XX:XX
[bluetooth]# trust XX:XX:XX:XX:XX:XX
[bluetooth]# connect XX:XX:XX:XX:XX:XX
```
trust 这一步别省,否则系统会在闲置后主动断开,下次按键没反应。
配对完确认它被识别成输入设备:
```bash
ls /dev/input/by-id/ | grep -i -E 'xiaomi|remote'
sudo libinput debug-events --show-keycodes
```
这时按几下遥控器。如果终端里刷出 KEY_ENTER、KEY_BACK、KEY_HOMEPAGE、KEY_VOLUMEUP 这类事件,说明它就是个标准输入设备,后面的思路全部成立。如果什么都不刷,先看是不是配对没连上,而不是急着写脚本。
步骤二:把每个键的真实键码抓出来
同一个型号、不同固件,按键上报的键码可能不一样。所以先做一次“学习”,别照抄别人的表。
Linux 下用一段最小的监听脚本:
```python
#!/usr/bin/env python3
依赖: pip install evdev
import evdev
from evdev import ecodes
HINT = "remote" # 换成 /dev/input/by-id 里看到的设备名片段
def find_device():
for path in evdev.list_devices():
dev = evdev.InputDevice(path)
if HINT.lower() in dev.name.lower():
return dev
raise SystemExit("没找到遥控器,先确认已配对并连接")
dev = find_device()
print("正在监听:", dev.name, dev.path)
for ev in dev.read_loop():
if ev.type == ecodes.EV_KEY and ev.value == 1: # 只打印按下
print(evdev.categorize(ev).keycode)
```
Windows 上打开 AutoHotkey 托盘菜单里的 Key history,逐个按键看扫描码和键名。macOS 上打开 Karabiner-Elements 的 Event Viewer,按一下看 key_code。
把结果记成一张表,左边是物理按键,右边是系统看到的键码。这张表是后面所有配置的依据。
步骤三:设计键位映射表
设计原则只有一条:优先用遥控器独有的键去触发“危险动作”,Enter 和 Esc 这类与键盘共用的键尽量少改,否则会连带影响正常打字。
| 遥控器按键 | 动作 | 发送到终端的内容 | 备注 |
|---|---|---|---|
| OK(方向键中间) | 接受当前改动 / 确认 | Enter | 与键盘回车同码,通常不用重映射 |
| 返回 | 回滚上一轮 | Esc,Esc | 有的工具是 /rewind,按你的工具改 |
| 主页 | 开新会话 | Ctrl+C,然后 /new 回车 | 先中断再清空 |
| 菜单 | 切换模型 | /model 回车 | 换成你工具的模型命令 |
| 上 / 下 | 翻历史、选候选 | Up / Down | 保持原样,不拦截 |
| 左 / 右 | 切标签页 | Ctrl+Tab / Ctrl+Shift+Tab | 视终端而定 |
| 音量加 | 对提示回答 yes | y 回车 | 适合一路确认的批处理场景 |
| 音量减 | 中断当前生成 | Esc | 兼作“停一下” |
| 语音键 | 聚焦终端窗口 | 无按键,只激活窗口 | 语音本身多半用不了,见排错 |
| 电源键 | 建议不映射 | — | 部分系统会当作休眠或关机 |
表里的斜杠命令只是占位。你的智能体用 /clear 还是 /new、用 /model 还是别的写法,以官方文档为准。如果工具支持自定义快捷键,更好的做法是在工具配置里把“接受”绑到一个冷门组合(比如 Ctrl+Y),映射层只负责发这个组合,语义更稳。
步骤四:写映射脚本
所有平台都要处理同一个坑:按键发生时,焦点不一定在终端上。所以每个“动作”都应该先激活终端窗口,再发按键。
macOS 用 Karabiner-Elements 做按键识别,用脚本做动作。先写一个动作脚本:
```bash
#!/usr/bin/env bash
保存为 ~/bin/agent-action,然后 chmod +x ~/bin/agent-action
set -euo pipefail
activate() {
osascript -e 'tell application "System Events" to set frontmost of (first process whose name is "Terminal") to true' || true
}
key() { osascript -e "tell application \"System Events\" to key code $1"; }
type_text() { osascript -e "tell application \"System Events\" to keystroke \"$1\""; }
case "${1:-}" in
accept) activate; key 36 ;; # Return
rollback) activate; key 53; key 53 ;; # Esc Esc
newsession) activate
osascript -e 'tell application "System Events" to key code 8 using control down'
sleep 0.3
type_text "/new"; key 36 ;;
model) activate; type_text "/model"; key 36 ;;
interrupt) activate; key 53 ;;
*) echo "用法: agent-action {accept|rollback|newsession|model|interrupt}"; exit 1 ;;
esac
```
然后在 Karabiner-Elements 的 Complex Modifications 里加规则,用 device_if 把规则限定在这只遥控器上,动作写成 shell_command:
```json
{
"description": "小米遥控器:返回键 -> 回滚",
"manipulators": [
{
"type": "basic",
"from": { "key_code": "escape", "modifiers": { "optional": ["any"] } },
"to": [ { "shell_command": "$HOME/bin/agent-action rollback" } ],
"conditions": [
{ "type": "device_if", "identifiers": [ { "vendor_id": 0, "product_id": 0 } ] }
]
}
]
}
```
vendor_id 和 product_id 不要照抄,从 Karabiner-Elements 的 Devices 面板里抄下遥控器那一行的实际数值。脚本要发按键,需要在系统设置里给终端和 osascript 开辅助功能权限。
Windows 用 AutoHotkey v2。先按步骤二查 Key history,确认遥控器各键在 AHK 里的键名,再写成这样:
```ahk
#Requires AutoHotkey v2.0
SendMode "Input"
SetKeyDelay 30, 30
ActivateTerm() {
if WinExist("ahk_exe WindowsTerminal.exe")
WinActivate
else if WinExist("ahk_exe powershell.exe")
WinActivate
}
Accept() {
ActivateTerm()
Send "{Enter}"
}
Rollback() {
ActivateTerm()
Send "{Esc}{Esc}"
}
NewSession() {
ActivateTerm()
Send "^c"
Sleep 300
SendText "/new"
Send "{Enter}"
}
SwitchModel() {
ActivateTerm()
SendText "/model"
Send "{Enter}"
}
; 下面的键名要换成你 Key history 里实际看到的名字
Browser_Home::NewSession()
AppsKey::SwitchModel()
Volume_Up::Accept()
Volume_Down::Rollback()
```
如果 Key history 显示某个键是扫描码形式,就改用 SC0xx:: 的写法,把 xx 换成实际值。注意 Enter 和 Esc 不要全局拦截,否则正常键盘也会被改写。
Linux 用 python-evdev 直接读事件,配合 grab() 独占设备,按键就不会再漏进终端:
```python
#!/usr/bin/env python3
依赖: pip install evdev;发按键需要 wtype(Wayland) 或 xdotool(X11)
import shutil, subprocess, time
import evdev
from evdev import ecodes
HINT = "remote"
DEBOUNCE = 0.25
def run(cmd):
subprocess.run(cmd, check=False)
def send_key(name):
if shutil.which("wtype"):
run(["wtype", "-k", name])
else:
run(["xdotool", "key", name])
def send_text(text):
if shutil.which("wtype"):
run(["wtype", "--", text])
else:
run(["xdotool", "type", "--", text])
def focus_terminal():
if shutil.which("wmctrl"):
run(["wmctrl", "-a", "Terminal"]) # 换成你终端窗口标题的一部分
ACTIONS = {
ecodes.KEY_ENTER: lambda: (focus_terminal(), send_key("Return")),
ecodes.KEY_BACK: lambda: (focus_terminal(), send_key("Escape"), send_key("Escape")),
ecodes.KEY_HOMEPAGE: lambda: (focus_terminal(), send_key("ctrl+c"), send_text("/new"), send_key("Return")),
ecodes.KEY_MENU: lambda: (focus_terminal(), send_text("/model"), send_key("Return")),
ecodes.KEY_VOLUMEUP: lambda: (focus_terminal(), send_key("Return")),
ecodes.KEY_VOLUMEDOWN: lambda: (focus_terminal(), send_key("Escape")),
}
def main():
dev = None
for path in evdev.list_devices():
d = evdev.InputDevice(path)
if HINT.lower() in d.name.lower():
dev = d
break
if dev is None:
raise SystemExit("没找到遥控器")
dev.grab() # 独占设备
last = {}
for ev in dev.read_loop():
if ev.type != ecodes.EV_KEY or ev.value != 1:
continue
action = ACTIONS.get(ev.code)
if action is None:
continue
now = time.monotonic()
if now - last.get(ev.code, 0) < DEBOUNCE:
continue
last[ev.code] = now
action()
if __name__ == "__main__":
main()
```
普通用户读 /dev/input/event* 需要权限,把账号加进 input 组,重新登录后生效:
```bash
sudo usermod -aG input "$USER"
```
步骤五:加三道保险
第一道是去抖。方向键长按会自动连发,脚本里必须做时间窗过滤,否则一次“返回”可能发出六次 Esc,直接翻过回滚菜单。
第二道是独占。Linux 的 grab()、Karabiner 的设备条件、AHK 里不拦截共用键,都是同一个意思:别让遥控器按键和键盘互相污染。
第三道是能随时退出。映射脚本在后台跑的时候,得有一个不依赖键盘的退出方式,比如关掉终端窗口、用系统托盘菜单退出,或者在脚本里加一条“长按音量减两秒就 exit”。遥控器的电源键不建议拿来当急停,部分系统会把它当成休眠键。
常见坑与排错
配对成功但按键没反应,或者过一会儿就断。 多数是没 trust,或者系统的蓝牙省电策略把设备关了。Windows 在设备管理器里找到这个蓝牙设备,取消“允许计算机关闭此设备以节约电源”;Linux 重新 trust 再 connect;必要时重启蓝牙服务。
遥控器死活连不上电脑,一直连回电视。 先把电视关机或把遥控器拿到另一个房间再配对。有些型号会记住最后一次连接的设备并主动回连。
系统弹出“键盘设置助理”或要求识别键盘布局。 直接关掉,不影响使用。系统把它当成了一把没有物理布局信息的小键盘。
同一个键按一次触发好几次。 去抖窗口没设,或者 D-pad 的自动连发被当成了多次独立按键。
改了 Enter 之后正常打字也乱了。 说明映射没有按设备区分。退回“只映射遥控器独有的键”这个策略,把 Enter、Esc 留给键盘。
按键发出去了,但打到了浏览器或者编辑器里。 动作里少了“先激活终端”这一步。窗口标题也要对得上,wmctrl -a "Terminal" 里的字符串要和实际标题匹配。
Linux 报权限不足。 没加 input 组,或者加完没重新登录。macOS 需要在隐私设置里给辅助功能权限。Windows 上如果目标终端是以管理员身份启动的,AHK 脚本也要用管理员权限运行。
语音键没反应,或者只发出一个普通按键。 语音走的是厂商私有链路,配对到电脑后一般不会传音频,最多上报一个普通键码。把它当“聚焦终端”这类辅助键用就行,别指望语音输入。
蓝牙延迟高、偶尔卡。 USB 3.0 接口对 2.4G 有干扰,把蓝牙适配器插到前面板,或者用一根延长线把它挪远。遥控器和电脑之间尽量别有金属遮挡。
重启后脚本找不到设备。 别把 /dev/input/eventN 写死,那个编号会变。按设备名匹配,或者用 /dev/input/by-id/ 下的稳定路径。
下一步建议
脚本跑通之后,值得补的几件事:
把脚本做成开机自启。Linux 写一个 systemd user service,macOS 用 launchd 或者登录项,Windows 把 AHK 脚本放进启动目录。这样插上电池就能用,不用每次手动起。
把键位从硬编码挪到配置文件。写一个 JSON,左边是键码,右边是动作名,改键位不用动代码。用得越久越会发现某些键位不合手,改配置比改脚本省事。
用“长按”扩展第二层动作。单按是高频动作,长按进低频动作,比如长按菜单键打开 git diff、长按主页键打开测试命令。这样十几个键能覆盖二十来个动作。
加一点声音反馈。遥控器本身没有屏幕,动作发出去有没有生效全靠终端画面。用 paplay 或 afplay 播一个极短的提示音,确认感会强很多。
最后,先用一周再决定要不要调。刚开始的键位设计往往和实际手感有差距,记录一下哪些键你从来没按过、哪些键老是按错,再按真实频率重排一次,比一开始就设计得很复杂更有效。
