Skills 与工具链集成
Skills 可以通过与外部工具链集成,Skill 可以完成单靠 Claude 无法完成的任务。
本篇介绍如何在 Skill 中调用命令行工具、系统工具及第三方库,并提供一个完整的集成示例。
Skills 可以调用的工具类型
| 工具类型 | 示例 | 调用方式 |
|---|---|---|
| 系统命令 | ls、cp、find、wc | subprocess.run() |
| 语言运行时 | python、node、bash | subprocess.run() |
| 文档处理工具 | pandoc、pdflatex | subprocess.run() |
| 媒体处理工具 | ffmpeg、imagemagick | subprocess.run() |
| Python 库 | pandas、openpyxl、pypdf | import 后直接调用 |
| Node.js 工具 | prettier、eslint | npx 调用 |
调用外部工具前,先确认工具在运行环境中已安装。Claude 沙箱中预装了常用工具(python3、pip、node、npm),但 pandoc、ffmpeg 等需要额外安装。
在脚本中调用外部命令
Python 的 subprocess 模块是调用外部命令的标准方式,推荐统一封装成工具函数复用。
实例
# 文件路径:scripts/tool_integration.py
import subprocess
import sys
import json
def run_command(cmd: list, timeout: int = 60) -> dict:
"""
运行外部命令并捕获输出
参数:
cmd: 命令列表,如 ["pandoc", "--version"]
timeout: 超时秒数,默认 60
返回:
包含 success、stdout、stderr、returncode 的字典
"""
try:
result = subprocess.run(
cmd,
capture_output=True, # 同时捕获 stdout 和 stderr
text=True, # 以字符串返回,而非字节
timeout=timeout
)
return {
"success": result.returncode == 0,
"returncode": result.returncode,
"stdout": result.stdout.strip(),
"stderr": result.stderr.strip()
}
except FileNotFoundError:
return {
"success": False,
"returncode": -1,
"stdout": "",
"stderr": f"命令不存在:{cmd[0]},请确认已安装"
}
except subprocess.TimeoutExpired:
return {
"success": False,
"returncode": -1,
"stdout": "",
"stderr": f"命令执行超时(超过 {timeout} 秒)"
}
# 示例:用 pandoc 将 Markdown 转换为 HTML
if __name__ == "__main__":
result = run_command([
"pandoc",
"/mnt/user-data/uploads/readme.md",
"-o", "/mnt/user-data/outputs/readme.html",
"--standalone"
])
if result["success"]:
print("转换成功")
else:
print(f"转换失败:{result['stderr']}")
sys.exit(1)
import subprocess
import sys
import json
def run_command(cmd: list, timeout: int = 60) -> dict:
"""
运行外部命令并捕获输出
参数:
cmd: 命令列表,如 ["pandoc", "--version"]
timeout: 超时秒数,默认 60
返回:
包含 success、stdout、stderr、returncode 的字典
"""
try:
result = subprocess.run(
cmd,
capture_output=True, # 同时捕获 stdout 和 stderr
text=True, # 以字符串返回,而非字节
timeout=timeout
)
return {
"success": result.returncode == 0,
"returncode": result.returncode,
"stdout": result.stdout.strip(),
"stderr": result.stderr.strip()
}
except FileNotFoundError:
return {
"success": False,
"returncode": -1,
"stdout": "",
"stderr": f"命令不存在:{cmd[0]},请确认已安装"
}
except subprocess.TimeoutExpired:
return {
"success": False,
"returncode": -1,
"stdout": "",
"stderr": f"命令执行超时(超过 {timeout} 秒)"
}
# 示例:用 pandoc 将 Markdown 转换为 HTML
if __name__ == "__main__":
result = run_command([
"pandoc",
"/mnt/user-data/uploads/readme.md",
"-o", "/mnt/user-data/outputs/readme.html",
"--standalone"
])
if result["success"]:
print("转换成功")
else:
print(f"转换失败:{result['stderr']}")
sys.exit(1)
检查工具是否可用
在 Skill 执行前检查所需工具是否安装,能在任务中途失败前给出清晰提示。
实例
# 文件路径:scripts/check_tools.py
import shutil
import sys
REQUIRED_TOOLS = [
("pandoc", "sudo apt-get install pandoc"),
("ffmpeg", "sudo apt-get install ffmpeg"),
("convert", "sudo apt-get install imagemagick"),
]
def check_tools(tools: list) -> bool:
"""检查工具列表,返回 True 表示全部就绪"""
all_ok = True
for tool, install_cmd in tools:
path = shutil.which(tool) # 在 PATH 中查找工具路径
if path:
print(f" 已安装:{tool} → {path}")
else:
print(f" 未安装:{tool} 安装命令:{install_cmd}")
all_ok = False
return all_ok
if __name__ == "__main__":
print("检查工具依赖...")
if not check_tools(REQUIRED_TOOLS):
print("\n请先安装缺少的工具。")
sys.exit(1)
print("\n所有工具已就绪,继续执行。")
import shutil
import sys
REQUIRED_TOOLS = [
("pandoc", "sudo apt-get install pandoc"),
("ffmpeg", "sudo apt-get install ffmpeg"),
("convert", "sudo apt-get install imagemagick"),
]
def check_tools(tools: list) -> bool:
"""检查工具列表,返回 True 表示全部就绪"""
all_ok = True
for tool, install_cmd in tools:
path = shutil.which(tool) # 在 PATH 中查找工具路径
if path:
print(f" 已安装:{tool} → {path}")
else:
print(f" 未安装:{tool} 安装命令:{install_cmd}")
all_ok = False
return all_ok
if __name__ == "__main__":
print("检查工具依赖...")
if not check_tools(REQUIRED_TOOLS):
print("\n请先安装缺少的工具。")
sys.exit(1)
print("\n所有工具已就绪,继续执行。")
检查工具依赖... 已安装:pandoc → /usr/bin/pandoc 未安装:ffmpeg 安装命令:sudo apt-get install ffmpeg 未安装:convert 安装命令:sudo apt-get install imagemagick 请先安装缺少的工具。
在 SKILL.md 中声明工具依赖
用 YAML frontmatter 的 compatibility 字段列出所有外部依赖,让用户安装 Skill 前就知道需要准备什么。
---
name: doc-converter
description: >
将文档在不同格式间转换,支持 Markdown、HTML、PDF、Word。
当用户需要格式转换、导出文档时触发。
compatibility:
tools:
- pandoc >= 2.14 # 文档格式转换
- python >= 3.8 # 运行辅助脚本
python_packages:
- pypdf >= 3.0 # PDF 处理
- python-docx >= 1.0
---
集成 Node.js 工具
通过 npx 可以直接调用 npm 包中的 CLI 工具,无需全局安装。
实例
# 文件路径:scripts/use_node_tool.py
import subprocess
import sys
def run_prettier(file_path: str) -> bool:
"""用 prettier 格式化代码文件"""
result = subprocess.run(
["npx", "--yes", "prettier", "--write", file_path],
capture_output=True,
text=True
)
if result.returncode == 0:
print(f"格式化成功:{file_path}")
return True
print(f"格式化失败:{result.stderr}")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法:python use_node_tool.py <文件路径>")
sys.exit(1)
sys.exit(0 if run_prettier(sys.argv[1]) else 1)
import subprocess
import sys
def run_prettier(file_path: str) -> bool:
"""用 prettier 格式化代码文件"""
result = subprocess.run(
["npx", "--yes", "prettier", "--write", file_path],
capture_output=True,
text=True
)
if result.returncode == 0:
print(f"格式化成功:{file_path}")
return True
print(f"格式化失败:{result.stderr}")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法:python use_node_tool.py <文件路径>")
sys.exit(1)
sys.exit(0 if run_prettier(sys.argv[1]) else 1)
npx --yes首次运行时会自动下载工具包,需要网络连接到 npm registry。在网络受限的环境中应提前全局安装:npm install -g prettier。
工具调用结果的标准化处理
不同工具的输出格式各不相同,建议在 Skill 层统一封装,对外只暴露一致的结构。
| 处理方式 | 适用场景 | 典型工具 |
|---|---|---|
| 捕获 stdout | 工具将结果输出到标准输出 | pandoc、wc、cat |
| 检查返回码 | 工具成功返回 0,失败返回非 0 | 几乎所有 CLI 工具 |
| 读取输出文件 | 工具将结果写入指定文件 | ffmpeg、pandoc -o |
| 解析 JSON 输出 | 工具支持 --json 参数 | eslint --format json |
完整集成示例:Markdown 转 PDF 报告
以下示例将 pandoc 与 Python 脚本结合,实现一个完整的 Markdown → PDF 生成 Skill 脚本。
实例
# 文件路径:scripts/md_to_pdf.py
# 完整工作流:检查工具 → 验证输入 → 调用 pandoc → 确认输出
import subprocess
import shutil
import sys
import os
import json
from datetime import datetime
def check_pandoc() -> bool:
"""检查 pandoc 是否可用"""
if not shutil.which("pandoc"):
print("错误:未找到 pandoc,请运行:sudo apt-get install pandoc")
return False
return True
def validate_input(md_path: str) -> bool:
"""验证输入的 Markdown 文件"""
if not os.path.exists(md_path):
print(f"错误:文件不存在 → {md_path}")
return False
if not md_path.lower().endswith(".md"):
print(f"错误:文件必须是 .md 格式 → {md_path}")
return False
return True
def convert_to_pdf(md_path: str, output_dir: str) -> dict:
"""调用 pandoc 将 Markdown 转换为 PDF"""
os.makedirs(output_dir, exist_ok=True)
# 生成带时间戳的输出文件名,避免覆盖旧文件
base_name = os.path.splitext(os.path.basename(md_path))[0]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = os.path.join(output_dir, f"{base_name}_{timestamp}.pdf")
result = subprocess.run(
[
"pandoc", md_path,
"-o", output_path,
"--pdf-engine=xelatex", # 支持中文
"-V", "CJKmainfont=SimSun", # 中文字体(按环境调整)
"-V", "geometry:margin=2cm" # 页边距
],
capture_output=True,
text=True,
timeout=120
)
if result.returncode == 0 and os.path.exists(output_path):
size_kb = os.path.getsize(output_path) // 1024
return {
"status": "success",
"output": output_path,
"size_kb": size_kb
}
return {
"status": "error",
"message": result.stderr or "pandoc 未生成输出文件"
}
def main():
if len(sys.argv) < 2:
print("用法:python md_to_pdf.py <Markdown文件路径>")
sys.exit(1)
md_path = sys.argv[1]
output_dir = "/mnt/user-data/outputs"
# 逐步检查,任一步骤失败立即退出
if not check_pandoc():
sys.exit(1)
if not validate_input(md_path):
sys.exit(1)
print(f"正在转换:{md_path}")
result = convert_to_pdf(md_path, output_dir)
print(json.dumps(result, ensure_ascii=False, indent=2))
sys.exit(0 if result["status"] == "success" else 1)
if __name__ == "__main__":
main()
# 完整工作流:检查工具 → 验证输入 → 调用 pandoc → 确认输出
import subprocess
import shutil
import sys
import os
import json
from datetime import datetime
def check_pandoc() -> bool:
"""检查 pandoc 是否可用"""
if not shutil.which("pandoc"):
print("错误:未找到 pandoc,请运行:sudo apt-get install pandoc")
return False
return True
def validate_input(md_path: str) -> bool:
"""验证输入的 Markdown 文件"""
if not os.path.exists(md_path):
print(f"错误:文件不存在 → {md_path}")
return False
if not md_path.lower().endswith(".md"):
print(f"错误:文件必须是 .md 格式 → {md_path}")
return False
return True
def convert_to_pdf(md_path: str, output_dir: str) -> dict:
"""调用 pandoc 将 Markdown 转换为 PDF"""
os.makedirs(output_dir, exist_ok=True)
# 生成带时间戳的输出文件名,避免覆盖旧文件
base_name = os.path.splitext(os.path.basename(md_path))[0]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = os.path.join(output_dir, f"{base_name}_{timestamp}.pdf")
result = subprocess.run(
[
"pandoc", md_path,
"-o", output_path,
"--pdf-engine=xelatex", # 支持中文
"-V", "CJKmainfont=SimSun", # 中文字体(按环境调整)
"-V", "geometry:margin=2cm" # 页边距
],
capture_output=True,
text=True,
timeout=120
)
if result.returncode == 0 and os.path.exists(output_path):
size_kb = os.path.getsize(output_path) // 1024
return {
"status": "success",
"output": output_path,
"size_kb": size_kb
}
return {
"status": "error",
"message": result.stderr or "pandoc 未生成输出文件"
}
def main():
if len(sys.argv) < 2:
print("用法:python md_to_pdf.py <Markdown文件路径>")
sys.exit(1)
md_path = sys.argv[1]
output_dir = "/mnt/user-data/outputs"
# 逐步检查,任一步骤失败立即退出
if not check_pandoc():
sys.exit(1)
if not validate_input(md_path):
sys.exit(1)
print(f"正在转换:{md_path}")
result = convert_to_pdf(md_path, output_dir)
print(json.dumps(result, ensure_ascii=False, indent=2))
sys.exit(0 if result["status"] == "success" else 1)
if __name__ == "__main__":
main()
正在转换:/mnt/user-data/uploads/runoob_report.md
{
"status": "success",
"output": "/mnt/user-data/outputs/runoob_report_20260518_102305.pdf",
"size_kb": 248
}
在 SKILL.md 中调用该脚本:
## 执行转换 获取用户上传的 .md 文件路径后,运行: ```bash python scripts/md_to_pdf.py <文件路径> ``` 解析 JSON 输出: - status 为 "success":调用 present_files 展示 output 路径的文件 - status 为 "error":将 message 内容展示给用户,说明可能原因
