名称: portable-tools
描述: 构建跨设备工具,无需硬编码路径或账户名
构建能在不同设备、命名方案和配置下工作的工具的方法论。基于 2026-01-23 OAuth 刷新器调试会话的经验教训。
切勿假定你的设备是唯一设备。
你的本地设置只是众多可能配置中的一种。为通用情况构建,而非特定实例。
在编写任何读取配置、数据或凭据的代码之前:
自问:
- 文件路径?(macOS vs Linux,不同的主目录)
- 账户名称?(user123 vs default vs oauth)
- 服务名称?(拼写/大小写的细微差异)
- 数据结构?(不同版本,不同格式)
- 运行环境?(不同的 shell,不同的可用工具)
OAuth 刷新器示例:
- ❌ 错误假设:账户总是 "claude"
- ✅ 实际情况:可能是 "claude"、"Claude Code"、"default" 等
行动: 列出变量,使其可配置或可自动发现
在宣称成功之前:
要求:
- 具体的“之前”状态(精确值)
- 具体的“之后”状态(精确值)
- 证明两者不同的证据(并列对比)
OAuth 刷新器示例:
之前:
- 访问令牌:POp5z1fi...eSN9VAAA
- 过期时间:1769189639000
之后:
- 访问令牌:01v0RrFG...eOE9QAA ✅ 已改变
- 过期时间:1769190268000 ✅ 已延长
行动: 始终用真实值展示数据转换过程
在推送到生产环境之前:
测试:
- 错误配置(故意破坏配置)
- 数据缺失(移除预期字段)
- 多条目(模糊不清的情况)
- 边界情况(空值、特殊字符)
OAuth 刷新器示例:
- 测试 keychain_account: "wrong-name" → 后备方案应生效
- 测试不完整的钥匙串数据 → 应优雅失败并提供有用的错误信息
行动: 测试故障模式,而不仅仅是正常路径
❌ 错误做法:
# 模糊 - 返回第一个匹配项
security find-generic-password -s "Service" -w
✅ 正确做法:
# 明确 - 返回特定条目
security find-generic-password -s "Service" -a "account" -w
规则: 如果命令可能产生歧义,务必使其明确。
❌ 错误做法:
DATA=$(read_config)
USE_VALUE="$DATA" # 期望它是有效的
✅ 正确做法:
DATA=$(read_config)
if ! validate_structure "$DATA"; then
error "无效的数据结构"
fi
USE_VALUE="$DATA"
规则: 切勿假设数据具有预期的结构。
❌ 错误做法:
ACCOUNT="claude" # 硬编码
✅ 正确做法:
# 尝试配置项 → 尝试常见项 → 提供帮助信息的错误
ACCOUNT="${CONFIG_ACCOUNT}"
if ! has_data "$ACCOUNT"; then
for fallback in "claude" "default" "oauth"; do
if has_data "$fallback"; then
ACCOUNT="$fallback"
break
fi
done
fi
[[ -z "$ACCOUNT" ]] && error "未找到账户。已尝试:..."
规则: 为常见变体提供自动后备方案。
❌ 错误做法:
[[ -z "$TOKEN" ]] && error "无令牌"
✅ 正确做法:
[[ -z "$TOKEN" ]] && error "未找到令牌
已检查:
- 配置文件:$CONFIG_FILE
- 字段名:$FIELD_NAME
- 预期格式:{ \"tokens\": { \"refresh\": \"...\" } }
验证命令:
cat $CONFIG_FILE | jq '.tokens'
"
规则: 错误信息应帮助用户诊断和修复问题。
不要问: “它坏了吗?”
要问: “你看到的确切值是什么?存在多少条目?哪个条目包含数据?”
示例:
# 模糊
"检查钥匙串"
# 具体
"运行:security find-generic-password -l 'Service' | grep 'acct'"
"告诉我:1. 有多少条目 2. 哪个包含令牌 3. 最后修改时间"
不要说: “现在应该能用了”
展示: “这是之前的令牌(POp5z...),这是之后的(01v0R...),它们不同”
模板:
之前:
- 字段1:<精确值>
- 字段2:<精确值>
之后:
- 字段1:<新值> ✅ 已改变
- 字段2:<新值> ✅ 已改变
证明:值已发生变化
不要想: “在我的机器上能运行”
要想: “如果他们的设置在 [X] 方面不同怎么办?”
检查清单:
- [ ] 不同的账户名?
- [ ] 不同的文件路径?
- [ ] 不同的工具/版本?
- [ ] 不同的权限?
- [ ] 不同的数据格式?
--dry-run 或 --test 模式# 假设单一条目,无验证,无后备方案
KEYCHAIN_DATA=$(security find-generic-password -s "Service" -w)
REFRESH_TOKEN=$(echo "$KEYCHAIN_DATA" | jq -r '.refreshToken')
# 使用令牌(期望它是有效的)
问题:
- 返回按字母顺序的第一个匹配项(可能是错误的条目)
- 无验证(可能为空或格式错误)
- 无后备方案(账户名不同时失败)
# 明确账户,带验证和后备方案
validate_data() {
echo "$1" | jq -e '.claudeAiOauth.refreshToken' > /dev/null 2>&1
}
# 尝试配置的账户
DATA=$(security find-generic-password -s "$SERVICE" -a "$ACCOUNT" -w 2>&1)
if validate_data "$DATA"; then
log "✓ 使用账户:$ACCOUNT"
else
log "⚠ 尝试后备账户..."
for fallback in "claude" "Claude Code" "default"; do
DATA=$(security find-generic-password -s "$SERVICE" -a "$fallback" -w 2>&1)
if validate_data "$DATA"; then
ACCOUNT="$fallback"
log "✓ 在 $fallback 中找到数据"
break
fi
done
fi
[[ -z "$DATA" ]] || ! validate_data "$DATA" && error "未找到有效数据
已尝试账户:$ACCOUNT, claude, Claude Code, default
验证命令:security find-generic-password -l '$SERVICE'"
REFRESH_TOKEN=$(echo "$DATA" | jq -r '.claudeAiOauth.refreshToken')
改进:
- ✅ 明确的账户参数
- ✅ 验证数据结构
- ✅ 自动回退到常见名称
- ✅ 包含验证命令的有用错误信息
FILE="/Users/patrick/.config/app.json" # 硬编码路径
修复: 使用 $HOME,检测操作系统,或使其可配置
TOKEN=$(cat config.json | jq -r '.token')
# 如果 .token 不存在怎么办?脚本会继续使用空值
修复: 使用前验证
TOKEN=$(cat config.json | jq -r '.token // empty')
[[ -z "$TOKEN" ]] && error "配置中无令牌"
# 如果存在多个条目,哪个是正确的?
ENTRY=$(find_entry "service")
修复: 明确指定或枚举所有
ENTRY=$(find_entry "service" "account") # 指定具体项
# 或
ALL=$(find_all_entries "service")
for entry in $ALL; do
validate_and_use "$entry"
done
process_data || true # 忽略错误
修复: 带上下文地大声失败
process_data || error "处理数据失败
数据:$DATA
预期:{ ... }
检查命令:command_to_verify"
添加到测试部分:
## 跨设备测试
- [ ] 使用不同账户名测试
- [ ] 使用错误配置值测试
- [ ] 使用缺失数据测试
- [ ] 记录后备行为
发布前添加:
## 可移植性检查
- [ ] 无硬编码路径(使用 $HOME,检测操作系统)
- [ ] 无硬编码名称(使用配置或后备方案)
- [ ] 对所有输入进行验证
- [ ] 为常见问题提供有用的错误信息
构建新技能时:
1. 列出设备间可能变化的因素
2. 使其可配置或可自动发现
3. 使用错误配置测试
4. 记录故障排除方法
编码前:
1. 设备间哪些因素会变化?
2. 如何证明它有效?
3. 当它出错时会发生什么?
强制性模式:
- 显式优于隐式
- 使用前验证
- 后备链
- 有用的错误信息
测试:
- 正确配置 → 正常工作
- 错误配置 → 回退或有用的错误信息
- 缺失数据 → 清晰的诊断信息
文档:
- 数据流图
- 常见变体
- 故障排除指南
当工具满足以下条件时,即为便携式:
测试: 将其交给设置不同的人。如果他们需要问你问题,说明工具还不够便携。
此方法论源于调试 OAuth 刷新器(2026-01-23):
- 脚本读取了错误的钥匙串条目(未指定账户)
- 假设只存在单一条目(实际存在多个)
- 无验证(使用了空数据)
- 无后备方案(账户名不同时失败)
Patrick 的方法:
1. 要求提供精确数据(有多少条目,哪个包含令牌)
2. 要求提供证明(展示之前/之后的令牌)
3. 考虑跨设备(如果命名不同怎么办?)
结果:工具从单设备/有缺陷变为通用/生产就绪。
关键洞察: 错误不在逻辑中,而在假设中。
适用于:
- 构建读取系统配置的工具
- 处理钥匙串、凭据、环境变量
- 创建在多个机器上运行的脚本
- 向 ClawdHub 发布技能(其他人将使用它们)
应用步骤:
1. 实现前:回答三个关键问题
2. 实现中:使用强制性模式
3. 测试前:运行发布前检查清单
4. 测试后:记录变体和故障排除方法
记住: 你的设备只是一个特例。为通用情况构建。