两种极端都不对
| 极端 | 问题 |
|---|---|
| 全靠 Claude 现写代码 | 相同逻辑每次重写,易出错;无法单元测试;复杂算法(如 OCR、加密)容易写错 |
| 全封装成脚本 | Skill 变重,维护成本高;灵活性差;用户问"能不能改个参数"要改源码 |
正确分界线:"确定逻辑"封装,"组合逻辑"留给 Claude。
该封装为脚本
复杂算法(加密、图像处理)、依赖外部 SDK、要求精确结果(SQL 执行、数值计算)、大量 I/O(读 PDF、写 Excel)。
该让 Claude 现写
一次性的数据转换、简单的文本拼接、根据用户需求组合脚本调用、生成 SQL 查询。
脚本语言选型
| 语言 | 适合场景 | 注意 |
|---|---|---|
| Python | 默认首选,生态最全(pandas / pypdf / PIL) | 指定 python3,避免 python 歧义 |
| Node.js | 前端/Web 相关、npm 生态(puppeteer) | 依赖管理用 package.json,别全局装 |
| Shell | 简单 git / curl / awk 组合,< 30 行 | 超过 30 行就换 Python |
| Go / Rust | CPU 密集、冷启动敏感 | 要预编译二进制,分发复杂 |
Python 为什么是默认
Claude Code 和 Skills 运行环境都预装 Python,脚本门槛最低。除非有强 Node/shell 依赖,都优先 Python。
Claude Code 和 Skills 运行环境都预装 Python,脚本门槛最低。除非有强 Node/shell 依赖,都优先 Python。
脚本的 CLI 设计准则
1. 命令行参数用 argparse 或 click
# scripts/analyze.py import argparse, json def main(): p = argparse.ArgumentParser() p.add_argument("--input", required=True) p.add_argument("--format", choices=["json", "csv"], default="json") p.add_argument("--verbose", action="store_true") args = p.parse_args() # ... if __name__ == "__main__": main()
好处:python script.py --help 能输出说明,Claude 不确定用法时可以自己查。
2. 标准输出用 JSON,错误用 stderr
import sys, json try: result = do_work() print(json.dumps({"ok": True, "data": result})) except Exception as e: print(json.dumps({"ok": False, "error": str(e)}), file=sys.stderr) sys.exit(1)
Claude 读 Bash 结果时会看到两个流,结构化的 JSON 让它准确判断成功/失败。
3. 单职责
一个脚本做一件事。不要写"万能脚本"——让 Claude 根据任务组合调用多个小脚本。
❌ scripts/do_everything.py (--mode=detect|fill|sign|merge) ✅ scripts/detect_fields.py ✅ scripts/fill.py ✅ scripts/sign.py ✅ scripts/merge.py
4. 幂等
同样输入跑两次结果一样。Claude 偶尔会重试脚本,幂等能避免诡异的副作用。
依赖管理
轻量方案:内嵌 requirements
my-skill/ ├── SKILL.md ├── scripts/ │ └── analyze.py └── requirements.txt ← pip install -r 即可
Skill 激活后,Claude 读到某个 import 失败,会主动运行 pip install -r requirements.txt——前提是 SKILL.md 里说明了这个安装步骤。
完善方案:声明在 SKILL.md
## 前置依赖
首次运行前执行:
```bash
pip install -r requirements.txt
```
本 Skill 依赖:pypdf>=5.0, reportlab>=4.0。
别用 sudo pip install
Claude 执行 Bash 时可能没 sudo 权限,或者用户的 Python 是用户空间。用
Claude 执行 Bash 时可能没 sudo 权限,或者用户的 Python 是用户空间。用
pip install --user 或在 SKILL.md 建议用虚拟环境。
敏感操作的保护
Skill 里的脚本不要默认做以下事,或者默认需要用户确认:
- 删除文件
- 提交 git / push 远端
- 调用付费 API(OpenAI、AWS)
- 发送邮件、消息
实现方式:脚本接受 --dry-run 标志,默认打印将要做的事不执行,加 --apply 才真的跑。SKILL.md 里告诉 Claude "先 dry-run 给用户看,确认后再 apply"。
脚本的版本演进
Skill 更新时脚本可能破坏兼容性。两条原则:
- 脚本的 CLI 签名变更时,同步更新 SKILL.md 里的调用示例
- 新增参数给默认值,不要强制必填——老 Skill 调用不至于直接炸
- 删除脚本前,在 SKILL.md 里保留一条"旧方法,已替换为 X"的过渡说明
实战:一个完整的脚本示例
#!/usr/bin/env python3 # scripts/fetch_sec_filing.py # 下载 SEC EDGAR 的 10-K / 10-Q 文件并抽取正文 import argparse, json, sys, re, urllib.request UA = "SkillSample research@example.com" EDGAR = "https://data.sec.gov" def fetch_filing(cik: str, form: str) -> dict: url = f"{EDGAR}/submissions/CIK{int(cik):010d}.json" req = urllib.request.Request(url, headers={"User-Agent": UA}) data = json.loads(urllib.request.urlopen(req).read()) recent = data["filings"]["recent"] for i, f in enumerate(recent["form"]): if f == form: return { "accession": recent["accessionNumber"][i], "date": recent["filingDate"][i], "primary_doc": recent["primaryDocument"][i], } return {} def main(): p = argparse.ArgumentParser() p.add_argument("--cik", required=True) p.add_argument("--form", default="10-K") args = p.parse_args() try: info = fetch_filing(args.cik, args.form) if not info: print(json.dumps({"ok": False, "error": f"no {args.form} found"})) sys.exit(1) print(json.dumps({"ok": True, "filing": info}, indent=2)) except Exception as e: print(json.dumps({"ok": False, "error": str(e)}), file=sys.stderr) sys.exit(2) if __name__ == "__main__": main()
这个脚本符合所有准则:argparse、JSON 输出、stderr 错误、exit code 区分错误类型、User-Agent 合规、单职责。
Exit code 约定
0 成功;1 业务错误(找不到资源);2 系统错误(网络、解析异常);3+ 可自定义。Claude 看到不同 exit code 会采取不同的重试/降级策略。
本章小结
- 确定逻辑封装脚本,组合逻辑交给 Claude
- Python 是默认选择;CLI 用 argparse + JSON 输出 + stderr 错误
- 单职责、幂等、dry-run 保护敏感操作
- 依赖通过 requirements.txt + SKILL.md 显式声明
- CLI 变更同步 SKILL.md,用新参数时给默认值避免破坏性升级