Dog-LittleTools,九个 Bash 脚本和我不想再敲第二遍的命令
Dog-LittleTools 没有什么宏大的起点。
它不是我规划了三个月的开发平台,也不是一套试图重新定义 DevOps 的基础设施。
它只是来自一个非常朴素的想法。
有些麻烦,我不想处理第二遍。
日常开发里有很多这种小问题。它们没有难到值得单独找一个大型软件,却又频繁到让人烦躁。服务器上的 GPU 到底哪张空着,某个进程是不是已经没人管了,Claude Code 这个月大概花了多少钱,数据库什么时候才能启动完成,当前目录里究竟哪些文件被 Git 修改过。
每次遇到,我都可以临时拼一条命令。
nvidia-smi 接一段 awk。
git status 配合 tree 来回切。
脚本开头先写一个 sleep 10,然后祈祷十秒后服务已经启动。
问题是,临时命令很少真正只写一次。
第二周我会再写一遍,第三周换一台机器又写一遍。参数顺序忘了,边界情况也忘了。最后终端历史里躺着七个长得差不多的版本,哪个都不敢直接复制。
Dog-LittleTools 就是我对这种状态的投降。
与其每次现场发挥,不如把它们收集起来,统一维护。
九个工具不住在一个巨型脚本里
仓库里有九个工具。
它们不是一个拥有几十个子命令的大型程序。每个工具都是独立目录里的单文件 Bash 脚本,有自己的安装脚本、帮助信息和测试。
你可以只安装一个,也可以全部安装。
这里所谓的轻量,主要是没有 npm、Python 包或常驻服务。部分工具仍然需要系统里已有的 git、tree、jq、curl、nvidia-smi 或 nc。我不想为了显得纯粹,就假装操作系统命令不算依赖。
重点是没有新的运行时生态需要维护。
脚本放进 ~/.local/bin,它就可以工作。
Dog-Pick 帮我决定训练到底用哪张卡
多 GPU 机器上最常见的操作之一,是先运行 nvidia-smi,观察哪张卡空闲,再手动设置 CUDA_VISIBLE_DEVICES。
这套流程在只有两张卡时还算轻松。
一旦机器由多人共享,GPU 0 可能显存还剩很多,但利用率已经接近满载。另一张卡利用率很低,却因为残留进程占着显存。还有一张看起来空闲,温度却高得像刚跑完马拉松。
Dog-Pick 会读取 nvidia-smi 的结果,根据空闲显存、利用率和温度为 GPU 评分,再选择更合适的设备运行命令。
它支持最低空闲显存、最高利用率、最高温度和排除设备等条件。真正执行之前,还可以通过列表、JSON 或试运行模式查看它为什么做出这个选择。
我很在意「为什么」这件事。
自动化工具如果只告诉我「我选了 GPU 2」,却不展示判断依据,它就很难进入更复杂的工作流。试运行和机器可读输出不是附加功能,而是让脚本可以被人和 Agent 同时使用的基础。
Dog-Lock 用一个目录代替调度系统
Dog-Pick 解决了哪张卡看起来适合使用,却没有解决多人同时抢到同一张卡的问题。
于是有了 Dog-Lock。
它不使用 Redis,也不需要数据库,更没有后台守护进程。锁信息放在 /tmp/gpulock 下,通过原子 mkdir 操作声明某张 GPU 已经被占用。
锁里会记录用户、主机、进程号、命令、时间和超时时间。
命令结束后可以自动释放。进程已经死亡或锁已经过期时,也可以识别并清理陈旧记录。
这套方案当然不能替代 Kubernetes 调度器,也不能服务一个拥有几百张卡的训练集群。
但在实验室服务器和小团队工作站上,很多时候大家缺少的不是一套分布式系统,而是一个比群里发「0 号卡我用了」更可靠的约定。
Dog-Lock 做的就是让约定变得可见。
Dog-Cost 回答一个让人不敢看的问题
我使用 Claude Code 之后,经常会遇到一个问题。
今天到底用了多少 Token。
平台账单通常给出总额,但我更想知道哪些会话最贵,哪个模型占比最高,这周和上周差了多少。
Dog-Cost 会读取本地 ~/.claude 下的 JSONL 日志,提取 Token 使用信息,再按照模型和时间范围估算成本。
整个过程离线完成。
日志不会上传到外部服务,这一点对包含私有代码的开发会话尤其重要。
它支持今天、昨天、本周、本月、指定日期和全部历史,也能输出 JSON 或 CSV。这里的结果仍然是估算,因为价格表可能变化,日志字段也可能不完整,但它至少让我不必等到账单出现后才知道自己写代码时有多投入。
有些 Bug 修好之后很有成就感。
有些 Bug 修好之后,只会让人想关闭成本统计。
Dog-TG 让目录树带上 Git 状态
我经常在 tree 和 git status 之间来回切换。
tree 告诉我文件在哪里。
git status 告诉我哪些文件发生了变化。
但我真正想看的,是一棵直接标出修改、增加、删除、冲突和未跟踪文件的目录树。
Dog-TG 就做这一件事。
它读取 git status --porcelain -z,再把状态标记放回目录树对应的位置。使用以零字符分隔的输出,是为了正确处理文件名中的空格和特殊字符。
普通 git status 更像一张清单。
Dog-TG 更像地图。
当我接手一个不熟悉的仓库,或者一次修改跨越多个目录时,这种空间关系比单纯的文件列表更容易理解。
它不会试图替代 Git。
它只是把两个我本来就会使用的视角合在一起。
Dog-Wait 终结了 sleep 10 && hope
Shell 脚本里最常见的迷信,大概就是固定等待。
先启动数据库,等待十秒,再执行迁移。
先启动后端,等待五秒,再跑集成测试。
这些数字没有依据,只是某一次运行时刚好够用。机器快一点就在浪费时间,机器慢一点就直接失败。
Dog-Wait 不等待一个猜出来的秒数。
它等待一个真实条件。
端口已经监听。
文件已经生成。
健康检查返回预期状态。
某条命令执行成功。
条件满足后,它可以继续执行后续命令,也可以发送桌面通知。底层会优先使用现有工具,并在可能时回退到 Bash 的 /dev/tcp 等能力。
它没有消灭等待。
它只是让等待终于有了理由。
跨平台真正难的不是判断系统名称
我最初希望这些脚本可以在 Windows、Linux 和 macOS 上以相似方式工作。
后来我越来越谨慎地使用「跨平台」这个词。
仓库目前明确的稳定目标是 Linux 和 macOS。Windows 用户更适合通过 WSL 等完整 Bash 环境运行。Git Bash 可以执行部分脚本,但只要涉及 /proc、/tmp、桌面通知、NVIDIA 工具路径或系统包查询,差异就会迅速出现。
判断当前系统是 Linux 还是 Darwin 并不困难。
真正困难的是同一个命令在三套环境里输出不同,甚至根本不存在。GNU sed 和 BSD sed 的参数不完全一样。readlink -f 在某些系统中没有。进程信息在 Linux 上可以读取 /proc,macOS 却需要另一套方案。
中文终端又带来了字符宽度问题。
字符串长度不等于屏幕宽度。一个汉字通常占两个终端列,ANSI 颜色转义序列则占用字符串长度却不显示。emoji 可能更复杂。如果直接用 ${#text} 计算填充空格,英文环境看着整齐,换成中文就像表格经历了一场小型地震。
这个问题最早在同一生态里的 Dog-RigLine 状态栏中暴露得最明显,后来也反过来影响了 LittleTools 的输出规范。
从那以后,我开始区分三个概念。
字节长度。
Unicode 字符数量。
终端显示宽度。
它们经常不是同一个数字。
给人用,也给 Claude Code 用
Dog-LittleTools 不只是为了让我少敲几次键盘。
我希望这些工具可以被 Claude Code 直接调用。
这意味着脚本不能只输出一堆适合人眼观看的彩色文字。它们还需要稳定的退出码、清楚的参数、试运行模式,以及 JSON 或安静输出模式。
人类看到红色警告就知道失败了。
Agent 更希望进程返回非零退出码,并在标准错误流里说明原因。
Dog-Pick 的 JSON 输出可以交给后续流程。Dog-Cost 的结构化结果可以进入报表。Dog-Wait 可以作为自动化任务的准备门。Dog-TG 可以帮助 Agent 理解修改分布。
这也是 Dog-Skills 生态里我设想的一种工作方式。
Skill 负责理解任务和组织步骤。
CLI 工具负责执行确定性的本地操作。
一个负责想清楚,一个负责把事情做稳。
小工具不需要长成平台
开发工具最容易出现的方向,是不断增加功能。
有了 GPU 选择,就想加任务队列。
有了任务队列,就想做网页管理面板。
有了网页面板,就需要用户系统、数据库和远程服务。
最后,为了省下一条命令,我维护了一个小型云平台。
Dog-LittleTools 有意停在另一个方向。
九个脚本。
每个只处理一种麻烦。
文件可以直接阅读,逻辑可以直接修改,不满意时也可以直接删除。
这很接近 Unix 哲学最吸引我的部分。不是把所有能力塞进一个程序,而是让每个工具做好一件事,再通过管道、退出码和文件把它们连接起来。
它们不会改变软件工程。
它们只是让我每天少重复几次已经做过的操作。
对一个工具箱来说,这就够了。
