装完一个命令行工具,想改下配置,结果 find ~ -name settings.json 跑了半分钟才定位到文件——这种体验你一定遇到过。前两天我装了个朋友写的 AI 编辑器 CLI,npm 全局安装,跑起来没问题,顺手翻配置才发现它把文件写到了 ~/Library/Application Support/vibecoding/settings.json。一个命令行工具,配置藏在 macOS GUI 应用的专属目录里,ls ~ 根本看不见。
反观 Claude Code,配置一目了然:~/.claude/,干净、直观、符合 CLI 工具的惯例。这看似是个小细节,但对日常使用和自动化脚本的影响很大。
配置放哪,不是随便决定的
CLI 工具的配置路径选择,背后有三条主流惯例:
| 惯例 | 路径示例 | 适用场景 |
|---|---|---|
| Unix 传统 | ~/.claude/ |
简单直接,一个目录搞定 |
| XDG 规范 | ~/.config/claude/ |
与其他工具统一管理,支持 $XDG_CONFIG_HOME |
| macOS GUI 惯例 | ~/Library/Application Support/Claude/ |
macOS 图形应用,CLI 不该用 |
Claude Code 选了第一条,~/.claude/。好处很明显:
ls -a ~一眼就能看到rm -rf ~/.claude一条命令清干净- shell 脚本里
~/.claude/config.json直接拼路径,不需要处理空格和特殊字符
而 ~/Library/Application Support/ 的问题恰好相反:路径带空格,shell 里必须加引号;目录层级深,ls ~ 看不到;更关键的是——这是 Apple 给 GUI 应用划的地盘,Finder 和系统工具会管理这个目录,CLI 工具混进去既不合规矩也不方便。
XDG 规范:更"正确"但更复杂的选择
XDG Base Directory Specification 定义了 ~/.config/ 作为配置集中存放的位置。理论上,所有 CLI 工具都应该尊重 $XDG_CONFIG_HOME,把配置放到 ~/.config/<appname>/ 下。Git、Neovim、bat 等工具已经跟进。
但现实是:大量经典工具(如 .ssh/、.bashrc)仍住在 ~ 根目录,用户也习惯了 ~/.appname 的模式。Claude Code 选 ~/.claude/ 而不是 ~/.config/claude/,牺牲了一点规范一致性,换来的是更低的心智负担和更好的可发现性。
对大多数 CLI 工具来说,两种选择都合理,但有一条底线:别用平台专属的 GUI 目录。
写一个靠谱的配置路径解析函数
如果你在写 Node.js CLI 工具,配置路径的解析逻辑应该考虑优先级:环境变量 > XDG 规范 > 传统路径。下面是一个可以直接用的实现:
// config-path.js — CLI 工具配置目录解析
import { homedir } from 'node:os';
import { join } from 'node:path';
import { mkdirSync } from 'node:fs';
const APP_NAME = 'mycli'; // 替换成你的工具名
/**
* 按优先级解析配置目录:
* 1. MYCLI_CONFIG_DIR 环境变量(允许用户强制指定)
* 2. XDG_CONFIG_HOME/mycli(遵循 XDG 规范)
* 3. ~/.mycli(Unix 传统,兜底方案)
*
* 不使用 ~/Library/Application Support,这是 macOS GUI 应用的地盘。
*/
function resolveConfigDir() {
// 优先级 1:用户通过环境变量显式指定
const envDir = process.env[`${APP_NAME.toUpperCase()}_CONFIG_DIR`];
if (envDir) return envDir;
// 优先级 2:XDG 规范
const xdgDir = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
const xdgAppDir = join(xdgDir, APP_NAME);
// 如果 XDG 目录已存在,优先使用它
try {
mkdirSync(xdgAppDir, { recursive: true });
return xdgAppDir;
} catch {
// XDG 目录不可写,降级到传统路径
}
// 优先级 3:传统 ~/.mycli
const traditionalDir = join(homedir(), `.${APP_NAME}`);
mkdirSync(traditionalDir, { recursive: true });
return traditionalDir;
}
// 使用示例
const configDir = resolveConfigDir();
console.log(`配置目录: ${configDir}`);
// Linux/Mac 默认输出: 配置目录: /home/you/.config/mycli
// 设置环境变量后: MYCLI_CONFIG_DIR=/tmp/mycli_test node config-path.js
// 输出: 配置目录: /tmp/mycli_test
运行方式:
# 默认行为,配置落到 ~/.config/mycli
node config-path.js
# 强制指定配置目录(测试或特殊部署时有用)
MYCLI_CONFIG_DIR=/tmp/mycli_test node config-path.js
# 清理配置,一条命令搞定——不管走哪条路径都好删
rm -rf ~/.config/mycli # XDG 路径
rm -rf ~/.mycli # 传统路径
# 对比:如果放在 ~/Library/Application Support/mycli/
# rm -rf "$HOME/Library/Application Support/mycli" # 空格必须加引号
关键设计点:
- 环境变量覆盖让测试和 CI 场景干净,不用污染真实配置。
- XDG 优先但可降级,尊重规范但不强迫用户。
- 绝不写入
~/Library/Application Support,CLI 工具就该走 CLI 的路。
几条实用的检查清单
下次写 CLI 工具或者审查别人的项目,可以对照这几条:
- 配置路径不含空格和特殊字符——
~/.mycli可以,~/Library/Application Support/mycli不行。 - 支持环境变量覆盖——至少提供
APP_CONFIG_DIR一类的变量,方便测试和临时切换。 - 一条
rm -rf能清干净——用户卸载或重置时不需要到处找文件。 - 尊重 XDG 但不强迫——如果
~/.config/已存在就用它,否则~/.appname也完全可以。 - 跨平台一致——Linux 和 macOS 用同一套路径逻辑,Windows 上才考虑
%APPDATA%。
Claude Code 的 ~/.claude/ 不是什么惊天设计,但它做到了该做到的事:用户找得到、删得掉、脚本拼路径不用处理空格。CLI 工具的配置目录不需要花哨,需要的是不给用户添麻烦。