用 cli-anything-iterm2 管理 iTerm2 Profiles 与 Preferences:从配色预设到 tmux 偏好的一体化配置实践
【免费下载链接】CLI-Anything"CLI-Anything: Making ALL Software Agent-Native" -- CLI-Hub: https://clianything.cc/项目地址: https://gitcode.com/GitHub_Trending/cl/CLI-Anything
本文聚焦 CLI-Anything 生态中 cli-anything-iterm2 的配置管理能力,讲解如何用profile与pref两组命令对正在运行的 iTerm2 实例进行外观(Profile / 配色预设)与全局偏好(PreferenceKey / 主题 / tmux 相关开关)的编程化读写。读完本文,你将能复刻本文全部命令完成「查看全部 Profile → 按 GUID 取详情 → 套用 Solarized Dark 配色 → 调整 tmux 窗口打开方式与 dashboard 上限 → 检测当前主题」的完整自动化流程,并理解每个命令在底层 iTerm2 Python API 与 CLI 之间是如何落地的。文中所有结论均可在 profile-pref.md 与对应源码模块中得到验证。
一、先分清两组概念:Profiles 与 Preferences
在 iTerm2 中,profile(配置文件)与preference(全局偏好)是两层不同的设置:
| 概念 | 作用对象 | 典型内容 | cli-anything-iterm2 入口 |
|---|---|---|---|
| Profile | 单个会话/窗口的外观与行为模板 | 字体、颜色、badge 文本、按键映射 | profile命令组 |
| Preference | iTerm2 应用级全局开关 | 打开 tmux 窗口的方式、dashboard 条目数、自动隐藏客户端等 | pref命令组 |
这一区分直接映射到 CLI 的两组子命令结构上,见 iterm2_ctl_cli.py(profile组)与 iterm2_ctl_cli.py(pref组)。
二、运行前提:一条命令能触达 iTerm2 的三个必要条件
所有profile/pref命令最终都经由 iterm2_backend.py 中的run_iterm2()同步包装器,驱动 iTerm2 Python API 通过 WebSocket 连接正在运行的 iTerm2.app。因此执行前需满足:
- iTerm2 正在运行(macOS):
brew install --cask iterm2; - 已启用 Python API:iTerm2 → Preferences → General → Magic → Enable Python API(该提示同时出现在 CLI 的帮助文本与后端错误信息中);
- Python 侧依赖就绪:安装
cli-anything-iterm2(或pip install -e .),底层会import iterm2(见 iterm2_backend.py 的require_iterm2_running())。
命令统一形态为:
cli-anything-iterm2 [--json] <group> <command> [OPTIONS] [ARGS]其中--json会以结构化 JSON 输出结果,便于 Agent 解析;profile组还遵守 CLI 的「会话上下文」机制——apply-preset未显式传--session-id时,会回退到此前通过app current/app set-context保存的上下文 session(见 iterm2_ctl_cli.py)。
三、Profile 命令:查列表、取详情、套配色
参考文档 profile-pref.md 给出的 Profile 命令全集:
cli-anything-iterm2 profile list [--filter NAME] cli-anything-iterm2 profile get <guid> # detailed settings cli-anything-iterm2 profile color-presets cli-anything-iterm2 profile apply-preset "Solarized Dark" [--session-id ID]3.1profile list:枚举可用 Profile
列出 iTerm2 中全部 Profile 的名称 + GUID对。--filter NAME支持按名称子串过滤(不区分大小写)。其底层实现位于 core/profile.py 的list_profiles():通过iterm2.PartialProfile.async_query(connection)拉取所有 Profile,过滤时使用name_filter.lower() not in name.lower(),空名以"(unnamed)"兜底。
# 全量列表 cli-anything-iterm2 profile list # 只看名称含 dark 的 Profile cli-anything-iterm2 profile list --filter dark3.2profile get <guid>:按 GUID 取详情
get需要你在profile list中拿到的 GUID,返回该 Profile 的关键字段。需要如实说明:当前的「详情」是精选子集——从 core/profile.py 看,get_profile_detail()先按 GUID 匹配PartialProfile,再调用async_get_full_profile()取全量 Profile,但仅导出三个字段:
return { "name": full.name, "guid": full.guid, "badge_text": full.badge_text, }CLI 层(iterm2_ctl_cli.py)据此逐行打印name、guid、badge_text。若 GUID 不存在,get_profile_detail()会抛出ValueError: Profile with GUID '...' not found.,由 CLI 统一格式化为Error: ...并退出(见 handle_iterm2_error)。
cli-anything-iterm2 profile list # 先找 GUID cli-anything-iterm2 profile get <guid> # 再取详情3.3profile color-presets:枚举可用配色预设
列出 iTerm2 内置的全部颜色预设名,返回经过排序的字符串列表。实现为 core/profile.py 的list_color_presets(),即iterm2.ColorPreset.async_get_list(connection)后sorted()。执行:
cli-anything-iterm2 profile color-presets3.4profile apply-preset:给指定会话套用配色
把命名配色预设应用到某个会话的 Profile 上,例如经典深色方案"Solarized Dark":
cli-anything-iterm2 profile apply-preset "Solarized Dark" cli-anything-iterm2 profile apply-preset "Solarized Dark" --session-id w0t0p0需要特别说明其作用范围:apply_color_preset()(core/profile.py)并非修改磁盘上的 Profile 定义,而是取当前会话的 Profile 对象 →ColorPreset.async_get(connection, preset_name)拿到预设 →profile.async_set_color_preset(preset)→ 再session.async_set_profile(profile)写回会话,本质是对该会话生效的即时配色切换。底层流程可概括为:
async_find_session(connection, session_id)定位目标会话(见 iterm2_backend.py);ColorPreset.async_get()按名取预设;- 把预设套到会话 Profile 并回写会话。
成功返回{session_id, preset_applied};若预设名不存在,iTerm2 API 侧会抛错并由 CLI 统一呈现。
四、Pref 命令:全局偏好的发现、读取与写入
pref组的核心价值在于「以代码方式读写任意 iTerm2 全局偏好」,参考文档给出的完整命令集:
cli-anything-iterm2 pref list-keys # all valid PreferenceKey names cli-anything-iterm2 pref list-keys --filter tmux # filter by substring cli-anything-iterm2 pref get OPEN_TMUX_WINDOWS_IN cli-anything-iterm2 pref set OPEN_TMUX_WINDOWS_IN 2 cli-anything-iterm2 pref theme # current theme tags + is_dark bool4.1pref list-keys:发现所有合法键名
不必记忆偏好键,先让 CLI 告诉你全部合法键名:
cli-anything-iterm2 pref list-keys cli-anything-iterm2 pref list-keys --filter tmux # 只看与 tmux 相关的 cli-anything-iterm2 pref list-keys --filter font # 只看字体相关其实现(iterm2_ctl_cli.py)直接遍历iterm2.preferences.PreferenceKey枚举,把所有成员的枚举名(形如OPEN_TMUX_WINDOWS_IN)按字典序排序输出;--filter为不区分大小写的子串过滤,结果中同时给出count。
4.2pref get/pref set:键名解析与值类型归一化
cli-anything-iterm2 pref get OPEN_TMUX_WINDOWS_IN cli-anything-iterm2 pref set OPEN_TMUX_WINDOWS_IN 2这两个命令背后是 core/pref.py 的get_preference()与set_preference()。有两个值得展开的实现细节:
- 双通道键名解析:入参
key会先尝试当作iterm2.PreferenceKey的枚举成员名解析(如OPEN_TMUX_WINDOWS_IN),失败则原样回退为原始偏好键字符串(如OpenTmuxWindowsIn),所以两种写法都可用。 - 字符串值自动类型化:
_parse_value()(core/pref.py)会把 CLI 传入的字符串转成合适类型,规则如下:
| 输入字符串示例 | 解析结果 | 说明 |
|---|---|---|
"true"/"false"(任意大小写) | True/False | 布尔值 |
"2" | int2 | 整数字符串优先转 int |
"1.5" | float1.5 | 非整数的数字串转 float |
"Solarized Dark"等 | 保持字符串 | 其它一律原样保留 |
因此pref set OPEN_TMUX_WINDOWS_IN 2实际写入的是整数2,而pref set AUTO_HIDE_TMUX_CLIENT_SESSION true写入的是布尔True,无需在命令行区分类型。set_preference最终调用iterm2.async_set_preference(connection, pref_key, parsed)并返回{key, value, set: True}。
4.3pref theme:读取当前主题标签
cli-anything-iterm2 pref theme返回当前 iTerm2 主题的标签列表与is_dark布尔值。实现见get_theme()(core/pref.py):先iterm2.async_get_app(connection),再调用app.async_get_theme()得到一组标签,例如["dark"]、["light"]或["dark", "highContrast"];is_dark = "dark" in tags。这对 Agent 判断「当前是深色还是浅色外观、是否高对比度」非常有用,例如据此决定要向终端发送什么颜色的 ANSI 输出。
五、tmux 偏好速记命令:四个高频开关一步到位
参考文档为 tmux 相关偏好专门提供了速记层:
cli-anything-iterm2 pref tmux-get # all tmux prefs at once cli-anything-iterm2 pref tmux-set open_in 2 # 0=native_windows 1=new_window 2=tabs_in_existing cli-anything-iterm2 pref tmux-set auto_hide_client true cli-anything-iterm2 pref tmux-set use_profile true cli-anything-iterm2 pref tmux-set dashboard_limit 105.1pref tmux-get:一次性读全
get_tmux_preferences()(core/pref.py)一次性并发读取 4 个 PreferenceKey,并附带一个人类可读的标签映射:
| 返回字段 | 对应 PreferenceKey | 说明 |
|---|---|---|
open_tmux_windows_in | OPEN_TMUX_WINDOWS_IN | 0=native_windows,1=new_window,2=tabs_in_existing(label 随附在返回结果中) |
tmux_dashboard_limit | TMUX_DASHBOARD_LIMIT | tmux dashboard 显示的最大条目数 |
auto_hide_tmux_client_session | AUTO_HIDE_TMUX_CLIENT_SESSION | 是否自动隐藏 tmux 客户端会话 |
use_tmux_profile | USE_TMUX_PROFILE | 新建窗口时是否使用 tmux profile |
CLI 层(iterm2_ctl_cli.py)除输出完整字典外,还会附带一行把open_in的数值还原为文字标签,例如open_in: 2 (tabs_in_existing)。
5.2pref tmux-set:按易记名设置
set_tmux_preference()(core/pref.py)只接受四个人类可读设置名,通过内部setting_map映射到 PreferenceKey;如果传了未知设置名,会抛出ValueError并提示合法集合:
setting_map = { "open_in": iterm2.PreferenceKey.OPEN_TMUX_WINDOWS_IN, "dashboard_limit": iterm2.PreferenceKey.TMUX_DASHBOARD_LIMIT, "auto_hide_client": iterm2.PreferenceKey.AUTO_HIDE_TMUX_CLIENT_SESSION, "use_profile": iterm2.PreferenceKey.USE_TMUX_PROFILE, }tmux-set设置名 | 合法取值 | 含义 |
|---|---|---|
open_in | 0/1/2 | tmux 新窗口的呈现方式:原生窗口 / 新 iTerm2 窗口 / 并入现有窗口的标签页 |
dashboard_limit | 整数 | dashboard 最大条目数 |
auto_hide_client | true/false | 是否自动隐藏 tmux 客户端会话 |
use_profile | true/false | 新窗口是否使用 tmux Profile |
例如把 tmux 窗口默认作为现有窗口中的标签页打开(2),并让 dashboard 最多显示 10 个条目:
cli-anything-iterm2 pref tmux-set open_in 2 cli-anything-iterm2 pref tmux-set dashboard_limit 10结合 iTerm2 的 tmux 集成(core/tmux.py)可以理解这些偏好的意义:iTerm2 中每个 tmux 窗口会以 iTerm2 标签页形式出现(list_tmux_tabs()只返回tmux_window_id非空的标签页,见 core/tmux.py),因此「新 tmux 窗口出现在哪里(原生窗口/新窗口/现有窗口标签页)」由OPEN_TMUX_WINDOWS_IN决定;AUTO_HIDE_TMUX_CLIENT_SESSION与USE_TMUX_PROFILE则分别控制客户端会话的显隐与新建窗口所用 Profile。这些偏好与 CLI 的tmux create-window、tmux set-visible、tmux bootstrap等命令配合,可实现完整的 tmux -CC 工作流(完整流程见 tmux-guide.md)。
六、从命令到 Python API 的调用链
理解整条链路有助于排查问题:profile与pref命令并非直接操作 plist 或 AppleScript,而是统一的「Click 命令 → 同步桥 → 异步协程 → iTerm2 Python API」结构:
- Click 层:
iterm2_ctl_cli.py定义profile、pref两组命令,负责参数解析、--json格式化与错误兜底; - 同步桥:iterm2_backend.py 的
run_iterm2(coro_fn, ...)用iterm2.run_until_complete()把异步协程包成同步调用,并捕获 WebSocket 连接失败等异常,给出「iTerm2 是否在运行 / Python API 是否启用」的排查提示; - 协程实现:
core/profile.py与core/pref.py中每个函数都签名为async def ...(connection, ...),内部直接调用iterm2.PartialProfile、iterm2.ColorPreset、iterm2.async_get_preference、iterm2.async_set_preference、app.async_get_theme等官方 Python API 对象。
因此所有配置操作的实时生效性都来自 iTerm2 自身 API,CLI 只是把「需要在 Python REPL 里手写的 async 代码」压缩成了可脚本化、可被 Agent 调用的单行命令。
七、测试覆盖与验证方式
该功能模块在仓库内配有明确测试清单,见 tests/TEST.md 与测试源码:
test_core.py中的test_profile_help(test_core.py)等用例验证profile --help的子命令结构与 CLI 骨架;- E2E 用例(
TestProfileOperations)覆盖profile list(至少返回 1 个 Profile)与profile color-presets(返回字符串列表)等,这些用例依赖正在运行的 iTerm2,见 test_full_e2e.py; - 子进程级测试(如
test_json_profile_list)确认安装后的命令行入口在--json下也能正常输出; - 涉及
pref/tmux的测试会按环境跳过:TEST.md 明确记录,TestTmuxOperations一类的用例在没有活动tmux -CC会话时会跳过(启动方式:在 iTerm2 终端内执行tmux -CC)。
对读者而言,最快的本地验证路径是:先cli-anything-iterm2 profile color-presets,再cli-anything-iterm2 profile apply-preset "Solarized Dark",随后cli-anything-iterm2 pref tmux-get观察 tmux 四项偏好,最后cli-anything-iterm2 pref theme确认主题标签——整个过程均可在一次终端会话内完成,且每条命令都能加--json换成结构化输出。
八、常见问题速查
| 现象 | 原因与排查 |
|---|---|
Error: Profile with GUID '...' not found. | profile get的 GUID 非法;先用profile list取真实 GUID |
Error: No session ID specified... | apply-preset等会话级命令未传--session-id且未设置上下文;先执行app current/app set-context |
Error: Unknown tmux setting '...' | tmux-set只接受open_in/dashboard_limit/auto_hide_client/use_profile四个名字 |
| 命令报无法连接 iTerm2 / WebSocket refused | 未启动 iTerm2,或 Preferences → General → Magic → Enable Python API 未勾选;启用后需重启 iTerm2 |
| 数字以字符串形式写入而非数值 | 只有当值是字符串时_parse_value才会自动转换;如需精确布尔或数值,直接传true/2形态即可 |
以上行为均可在 profile.py、pref.py 与 iterm2_backend.py 中逐行核验。掌握profile与pref两组命令后,iTerm2 的外观与行为配置就不再依赖手工点击菜单,而可以沉淀为可复现的脚本与 Agent 工作流中的标准步骤。
【免费下载链接】CLI-Anything"CLI-Anything: Making ALL Software Agent-Native" -- CLI-Hub: https://clianything.cc/项目地址: https://gitcode.com/GitHub_Trending/cl/CLI-Anything
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考