普通视图

Ghostty 折腾小记

2025年9月17日 21:22

刚入手 Macbook 的时候用的 Terminal.app,后来用 iTerm2,同时用 Termius 来连接服务器,再到后来用 Tabby + tssh 大一统,取代了 Termius。前两天心血来潮又换成了 Ghostty,说实话 Tabby 没什么不好,唯一的缺点就是不是原生。

Ghostty 号称「零配置」(Zero Configuration Philosophy),确实开箱的体验也足够好了。但是我有看惯了的配色和字体,所以这些还要折腾一下。先贴一下我的配置,以及效果,方便你抄作业。

theme = Catppuccin Mocha
font-family = Hack
font-family = Noto Sans SC
font-thicken = true
font-size = 12
window-inherit-font-size = false
window-padding-x = 10
window-padding-y = 10
shell-integration-features = no-cursor
cursor-style = block
cursor-style-blink = true
window-save-state = never
term = xterm-256color

它的配置采用的是一种宽松的类 ini 的格式,这点好评,不会一不小心就损坏配置没法打开。

主要修改了这些配置项:

  • theme 用了平常就在用的 Catppuccin Mocha
  • window-padding-x, window-padding-y 不喜欢元素贴着边边,有种拥挤的感觉
  • window-save-state 设成 never 每次重新打开都会变成默认大小,强迫症福音
  • term 这个选项控制 $TERM,它默认会把自己宣称 xterm-ghostty ,会导致有些 CLI 报兼容问题,所以改成 xterm-256color

比较值得一提的是字体的搭配。它支持设置多个 font-family 来决定字形的回落。

我之前一直使用的终端字体是 MesloLGS NF,编程字体是 Hack。我对字体的偏好其实就是等宽、无衬线。趁着折腾我又尝试了一些其他的组合:

  • Iosevka(Iosevka Term / Iosevka Term Extended):一款比较好评的终端字体,其中不带 Extended 的版本极窄,可以在更窄的屏幕内展示更多内容。但是实在窄得要命,接受不了。
  • Sarasa Gothic / 更纱黑体 (Sarasa Term SC):合成字体,是上面的 Iosevka + Source Han Sans 作为 CJK 增补合成的字体。其实用 Iosevka Extended + 这个还行,只是没有太好看。特点是中英文 2:1 等距。
  • JetBrains Maple Mono:也是一款合成字体,JetBrains Mono + Maple Mono。也是中英文 2:1 等距的。
  • Noto Sans:一款 CJK 字体,不怎么特别,但是在终端里比 PingFang SC 稍微耐看一点。
  • Fira Code:顾名思义一款为代码而生的字体,连字设计比较有特色。

有印象尝试过的自己大概就这些。折腾到最后,我使用了 Hack + Noto Sans 的组合。因为主要是英文字体用得居多,加上 vscode 里一直就是 Hack,所以看得顺眼的留下了。我发现在看惯某一款字体后,大脑识别起来会更快一点,过拟合不是虚的。

Anyway,趁这次折腾也对字体稍微有了一点了解,例如风格、等宽、连字、衬线、字形、字重等等,也许后面有机会再写一篇文章来讲这个。

通过 git pre-commit hook 防止误提交二进制文件

2025年9月5日 00:58

在开发 Go 的时候经常不小心把二进制 commit 上去(顺手就 gaa 了),虽然有几个不太靠谱的方法似乎能避免,例如

  • 把二进制的名称放进 .gitignore,仅限项目有 Makefile 的时候可能好用,换个名就完蛋。
  • 如果 git add 的时候耗时特别长,应该发觉完蛋。
  • 提交前通过 git status 好好看看 stage。

提交错了怎么处理这里就不展开叙述,反正就是很麻烦,会把仓库撑大,简单删除不行,要递归删除,还会搞乱提交历史,然后覆盖远端,非常麻烦。

但是我们可以通过 pre-commit hook 来检测 stage 里是否有二进制文件,并且在检测到时给出指引如何去掉二进制文件。

修改项目中 .git/hooks/pre-commit 为以下内容即可。

#!/bin/sh

# Function to detect binary files
detect_binaries() {
    echo "Checking for binary files..."

    # Get list of files to be committed (cached/staged files)
    files_to_commit=$(git diff --cached --name-only --diff-filter=A)

    if [ -z "$files_to_commit" ]; then
        return 0
    fi

    binary_files=""

    # Check each file
    for file in $files_to_commit; do
        if [ -f "$file" ]; then
            # Use file command to detect binary files
            if file "$file" | grep -q "binary\|executable\|ELF\|PE32\|Mach-O"; then
                binary_files="$binary_files $file"
            fi

            # Also check for common binary file extensions
            case "$file" in
                *.exe|*.dll|*.so|*.dylib|*.a|*.o|*.bin|*.class|*.jar|*.war|*.ear|*.pyc|*.pyo|*.pyd)
                    binary_files="$binary_files $file"
                    ;;
            esac
        fi
    done

    if [ -n "$binary_files" ]; then
        echo "ERROR: Binary files detected in commit:"
        for file in $binary_files; do
            echo "  - $file"
        done
        echo ""
        echo "To remove these files from staging:"
        for file in $binary_files; do
            echo "  git reset HEAD '$file'"
        done
        echo ""
        echo "Consider adding these patterns to .gitignore to prevent future commits."
        return 1
    fi

    return 0
}

# Check for binary files first
detect_binaries
if [ $? -ne 0 ]; then
    echo "Pre-commit hook failed: Binary files detected."
    exit 1
fi

效果如下

优雅地在 crontab 中运行脚本

2025年8月12日 23:11

一般来说,像 Node.js、Python 这类解释型的语言,系统上都会存在比较多个版本,利用一些诸如 nvm、venv、poetry、uv 等环境管理器来管理。一般来说部署这些脚本都算是一个比较噩梦的体验(相比可以打包可执行文件的语言来说),因为还要涉及到配环境的问题,另外配定时任务更是另一番折磨。

本篇介绍一个技巧,避免在 crontab 中反复使用绝对路径,更优雅地进行部署。解决方案就是写一点 shell 脚本,比在 crontab 里面反复 && 要好。

首先你需要一个配置环境的脚本,以及运行定时任务的脚本。

env.sh

#!/bin/bash

# 如果你的 nvm 在其他目录,需要修改这里
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm

# 如果你需要指定node版本,可以反注释下面一行
#nvm use v21.4.5

script.sh

#!/bin/bash
$(which node) script.js

单独给定时任务写一个脚本的好处是,如果你的参数非常长,或者定时任务想要复杂一点的逻辑,例如脚本 1 和脚本 2,需要在脚本 1 运行成功后再运行脚本 2,如果失败就告警,可以写以下这样的脚本。

#!/bin/bash

# 定义需要执行的命令
COMMAND1="ls /path/to/nonexistent/dir"  # 示例:故意让command1失败(返回非0)
COMMAND2="echo 'command1执行成功,现在执行command2'"

# 执行command1并检查结果
$COMMAND1

# 判断返回值:0为成功,执行command2;否则处理失败
if [ $? -eq 0 ]; then
    echo "INFO: command1执行成功(退出码0)"
    $COMMAND2
else
    echo "ERROR: command1执行失败(退出码$?)"
    exit 1  # 可选:失败时退出脚本(返回非0码)
fi

crontab -e

*/1 * * * * (cd path; . env.sh; script.sh >> path/cron.log 2>&1; )

解释两个地方,第一个是 .source 命令的缩写,与直接运行一个 sh 不一样的是,source 会在当前 shell 中执行,而不是在一个子 shell 中执行。第二个是后面的重定向,>> 表示以增加的方式(而不是覆盖)把日志写入(重定向至)文件,2>&1 则表示把标准错误流(stderr)也重定向至标准输出流(stdout)。这些日志能够帮助你定位定时任务的问题。

如果调试通过,最好把这个日志变成可轮转的(超过指定大小/时间会清除),避免占用太多磁盘。也可以重定向到 /dev/null ,这样就会丢弃掉所有输出。

使用这种方式,比以下这种更优雅、灵活和好维护的多。

*/1 * * * * cd path && /home/user/.nvm/versions/node/v24.5.0/bin/node script.js

Reference

Run `node` scripts using `nvm` and `crontab` without hardcoding the node version · GitHub

你不是在 vibe coding,而是在十倍速生成屎山

2025年7月24日 04:37

前言

就在过去的一天我花了大概十个小时 vibe coding 了下面这么个玩意,主要有这些功能:

  • 列出连接到服务器的设备列表
  • 连接设备,通过 scrcpy 实时监看屏幕,并支持控制
  • 支持执行 ADB 命令

事先声明,我是 AI 编程的积极拥护者,去年初我还写过一篇用 GitHub Copilot 写前端的文章。时隔一年多,这个领域涌现出了很多新产品,Agent、MCP、Cursor、Claude Code 等等的推出,我觉得是时候更新一下这方面的体验了。

先说一下这趟 vibe 旅程的花费和成果吧。我主要使用了 Cursor(主要是 claude-4-sonnet 和 Auto 模式),以及 Claude Code(Kimi K2 和 Claude 模型)。其中 Cursor Pro 的额度被我用完了,Claude Code 花费了十几刀。「产出」的代码有 10000 行左右,虽然中间有部分组件的代码,我估计实际逻辑代码大概也有几千行。

屎山

是的,标题里说得很清楚了,我基本上是在十倍速生成屎山。这本来应该是一篇经验介绍的文章,但是我觉得非常应该「把丑话说在前头」,而且这也是我在 vibe coding 中最大的心得。

我之前就说过:AI 决定下限,人决定上限。我觉得 vibe coding 分为以下几种情况:其一,你对要实现的功能非常熟悉,只是让 AI 代劳敲键盘的工作;其二,你对要实现的功能略知一二,要你自己拿起来写会磕磕绊绊;其三,也是我这次的情况,你对要实现的功能基本一无所知。第一种情况下,你跟 AI 很可能会配合得亲密无间,而后两种情况,你的体验估计够呛。后面的体验基本都是基于第三种情况来说。

一旦你开始 vibe coding,你就只能 vibe 到底了。

在你不懂的时候,AI 一旦写出一大段代码,这会迅速超出你的上下文范围。是的,虽然 AI 只有 200K 上下文,但这已经比大多数人能理解的上下文要大得多了(我认为一个普通的程序员上下文应该在 500 行代码左右)。这时候你的代码质量就会快速崩塌:混用 AI 模型、多次生成之间使你的代码库出现了多种不同风格的代码;不清楚最佳实践,导致有各种可能蹩脚的实现。很快你就会发现你陷入一种循环:贴错误日志/截图/描述错误 - AI 修改 - 无脑接受变更 - 运行,你对代码已经完全失去了掌控。

在 vibe coding 十个小时后,我的代码库里出现了几千行我根本看不懂也没法维护的代码,以及一个「好像能用」的产品。说实话,这个项目如果要我自己一边查文档一边写,估计至少要花上一周。但是这在工程上没有太大的意义,完全就是十倍速生成屎山。

工具简评

我这里决定把 Claude Code 和 Cursor 的 Agent Mode 放在一起说,他们在设计上是类似的,都是有个模型做规划(TODO),然后分步执行,调用工具这样的路子。先说优点:

  • 自动选择上下文的功能太好了,相比以前需要手动选是个极大的改进(这点 Claude Code 做得更好,交互上基本只用描述需求;Cursor 可以手动选择来增加精确度,但是自动的也不错)。
  • 规划的设计也很不错,如果发现方向不对可以尽早停止,在大型任务或者用户完全不懂的时候能取得更好的效果。
  • Fetch/Doc 工具非常实用,能避免一些冷门的包产生大量幻觉。
  • 能自动修复 lint,这点对强类型语言非常友好。

缺点也有不少:

  • 鬼打墙,遇到卡点还是会反复加日志调试,基本上解决不了。我觉得第一蛋疼的事,AI 往代码里加了一大堆日志,而且还带了很多 emoji,怎么都删不完。
  • 遇事不决删代码,mock 实现。这是我觉得第二蛋疼的地方。他会冠冕堂皇地说「我们尝试一个最简单的实现」,结果就是给你 mock 掉,甚至留个 TODO 在注释里,太过分了。
  • 烧钱快,Claude Code 可以在半个小时内烧完十几刀;不过换成 Kimi K2 的话相对经济很多。
  • Claude Code 没法贴图片,在遇到布局、样式问题时,只能通过语言描述。
  • 在架构、时序等问题上能力尤其弱。在写 scrcpy 的实现的时候,实际上 scrcpy 的服务端运行在安卓设备上,然后需要服务端实现一个 scrcpy 的客户端,然后起一个服务端,推流给客户端(浏览器)。一开始我没搞懂这个逻辑,而 AI 一直想在浏览器运行 scrcpy,纠结了非常久。

一些技巧

补充上下文

Cursor 和 Claude Code 都支持 MCP,有两个我觉得必备DeepWikicontext7

DeepWiki 能让帮助 AI 快速梳理项目结构,理解包的使用,找到对应的 API 。它甚至提供了一个问答的功能,可以让 DeepWiki 的 AI 帮你找到项目中对应的实现。例如我这个主要是想参考 midscene 的 android playground,我就让他去读项目的 DeepWiki,理解 scrcpy 的实现。又例如,ya-webadb这个包中,DeepWiki 梳理出了这个实现的架构,这其实对我的理解也有很大的帮助。

至于 context7 则能够给 AI 提供对应版本对应包的 API 文档,极大地减少它出现幻觉乱编 API 的情况。

当然了,你可以直接贴网页地址,或者也可以通过 Cursor 的 @Docs 文档去指定对应的开发文档,来手动地给 AI 增加上下文。

另外,虽然 Agent 模式会自动选择上下文,你可以手动指定一些上下文来帮助它更好理解和解决问题。在 Cursor 中,你可以在代码、终端中按 Cmd + I 来添加多个上下文,或者按 @ 展开下拉框选择。

按照需求选择模型

主观来说 claude-4-sonnet >= gemini-2.5-pro > Auto > Kimi K2,我推测 Auto 应该是 DeepSeek-R1 或 GPT-4.1 之类。分享几个零散的观察:

  • claude-4-sonnet 和 gemini-2.5-pro 在 Cursor 上都能触发类似 Claude Code 的 、TODO 模式,Auto 则不能。
  • 接上条,如果你要做比较复杂的修改,建议还是用 claude-4-sonnet 和 gemini-2.5-pro 这两个。但是他俩特别慢,如果修改比较简单(例如只是简单改改布局,可以用 Auto)。另外,Auto 没有用量限制,在超出额度之后就只能用这个。
  • Gemini 不喜欢说中文,Kimi K2 会被上下文带偏开始说英文,如果对这个有要求建议还是用 claude-4-sonnet;Auto 则基本会说中文(所以我觉得他是 DeepSeek)。
  • Kimi K2 是显著便宜,能力也是显著地差,几乎完全没法独立解决问题。

能力强的模型一定贵而且慢,因此不可能成为万金油。Cursor 中的 Auto 可以算是一种万金油,在解决复杂问题时有一定的竞争力,解决简单问题时速度则很快,废话也少。

回到一开始说的三种情况,第一种情况我建议你用 Auto,或者 Kimi K2,或者 DeepSeek 也行,甚至最基础的 Tab 自动补全都已经很好用,因为只是帮你省打字功夫。后面两种情况,如果你要 vibe 到底,那基本只能选 claude-4-sonnet 或者 gemini-2.5-pro ,除了他们没人能看懂你的屎山。

约定好规范

Cursor 支持 cursor rules,也就是一个 markdown 文档,来告诉 AI 如何解决问题,或者代码的风格等。我没有一个完整的规则可以分享,但是网上有很多,可以自行参考。我提供一些思路:

  • 规定框架、偏好的包,避免引入多个类似的库。
  • 适当解释你的项目结构,例如服务端的结构,希望 model 层和 service 等分离等。
  • 代码规范,大小驼峰这一类,避免风格不一致导致强迫症难受。
  • 语言风格,我不喜欢让 AI 再总结自己的变更,因为基本上我需要 review 代码。

写 AI 友好的代码

这点我放到了最后,也是我觉得最难的。在规范良好、逻辑清晰的代码上,AI 的表现最好;而在一些老项目、屎山上,AI 很难做出有效的修改。其实所谓 AI 友好,其实也是对人友好,也就是「可读性」。把函数拆得原子化、加单测、加注释、简化逻辑,这些都是老生常谈的问题了。

最后

激动的心,颤抖的手,写到现在已是凌晨四点半。在分享技巧之余我想回答的一个问题是,「AI 编程是否能提升效率」。我觉得能,但是有限,而且很大程度上还是取决于使用者本身。一是 AI 并没有显著降低人的认知负担,你还是要理解、维护同样代码量的东西;二是在屎山堆积的业务代码里,很难找到 AI 大显身手的空间;三是使用者的认知水平、技巧决定了 AI 的能力上限。

当然,市面上还有很多工具,例如 Gemini CLI、Kiro 等,我还没来得及去体验;本次的过程也是从「新手」的角度去体验,我也知道有些工作流、MCP 之类的组合的高阶玩法,我也没有涉及,但我认为总体的体验应该并无大异。我并非想泼 AI 的冷水,而是要跟它磨合好,人和 AI 协作必然是主流,人自身的能力和认知会更加重要。

❌