技能可以指示代理运行 shell 命令并在 scripts/ 目录中捆绑可重用的脚本。本指南涵盖一次性命令、带有各自依赖项的独立脚本,以及如何设计用于代理使用的脚本接口。
一次性命令
当现有的包已经满足您的需求时,您可以在 SKILL.md 指令中直接引用它,而无需 scripts/ 目录。许多生态系统提供在运行时自动解析依赖项的工具。
uvx
pipx
npx
bunx
deno run
go run
uvx 在隔离环境中运行 Python 包,具有积极的缓存功能。它随 uv 一起提供。uvx ruff@0.8.0 check .
uvx black@24.10.0 .
- 不随 Python 捆绑 — 需要单独安装。
- 快速。积极缓存,因此重复运行几乎是瞬间的。
pipx 在隔离环境中运行 Python 包。可通过操作系统包管理器获得(apt install pipx, brew install pipx)。pipx run 'black==24.10.0' .
pipx run 'ruff==0.8.0' check .
- 不随 Python 捆绑 — 需要单独安装。
uvx 的成熟替代方案。虽然 uvx 已成为标准推荐,但 pipx 仍然是一个可靠的选项,具有更广泛的操作系统包管理器可用性。
npx 运行 npm 包,按需下载。它随 npm 一起提供(npm 随 Node.js 一起提供)。npx eslint@9 --fix .
npx create-vite@6 my-app
- 随 Node.js 捆绑 — 无需额外安装。
- 下载包,运行它,并缓存以供将来使用。
- 使用
npx package@version 固定版本以实现可复现性。
bunx 是 Bun 的 npx 等效工具。它随 Bun 一起提供。bunx eslint@9 --fix .
bunx create-vite@6 my-app
- 在基于 Bun 的环境中可直接替代
npx。
- 仅适用于用户环境拥有 Bun 而不是 Node.js 的情况。
deno run 直接从 URL 或说明符运行脚本。它随 Deno 一起提供。deno run npm:create-vite@6 my-app
deno run --allow-read npm:eslint@9 -- --fix .
- 权限标志(
--allow-read 等)是文件系统/网络访问所必需的。
- 使用
-- 将 Deno 标志与工具自身的标志分开。
go run 直接编译并运行 Go 包。它内置于 go 命令中。go run golang.org/x/tools/cmd/goimports@v0.28.0 .
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 run
- 内置于 Go — 无需额外工具。
- 固定版本或使用
@latest 使命令明确。
技能中一次性命令的提示:
- 固定版本(例如,
npx eslint@9.0.0),以便命令随时间保持一致的行为。
- 声明先决条件 在您的
SKILL.md 中(例如,“Requires Node.js 18+”),而不是假设代理的环境已具备它们。对于运行时级别的要求,使用 compatibility frontmatter 字段。
- 将复杂命令移入脚本。 当您使用几个标志调用工具时,一次性命令效果很好。当命令变得复杂以至于难以第一次就做对时,
scripts/ 中经过测试的脚本更可靠。
从 SKILL.md 引用脚本
使用相对于技能目录根目录的路径来引用捆绑文件。代理会自动解析这些路径 — 无需绝对路径。
在 SKILL.md 中列出可用脚本,以便代理知道它们存在:
## 可用脚本
- **`scripts/validate.sh`** — 验证配置文件
- **`scripts/process.py`** — 处理输入数据
然后指示代理运行它们:
## 工作流
1. 运行验证脚本:
```bash
bash scripts/validate.sh "$INPUT_FILE"
```
2. 处理结果:
```bash
python3 scripts/process.py --input results.json
```
相同的相对路径约定也适用于支持文件(如 references/*.md)— 脚本执行路径(在代码块中)相对于技能目录根目录,因为代理是从那里运行命令的。
独立脚本
当您需要可重用的逻辑时,在 scripts/ 中捆绑一个脚本,该脚本在内联声明其自己的依赖项。代理可以通过单个命令运行脚本 — 无需单独的清单文件或安装步骤。
多种语言支持内联依赖项声明:
PEP 723 定义了内联脚本元数据的标准格式。在 # /// 标记内的 TOML 块中声明依赖项:# /// script
# dependencies = [
# "beautifulsoup4",
# ]
# ///
from bs4 import BeautifulSoup
html = '<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>'
print(BeautifulSoup(html, "html.parser").select_one("p.info").get_text())
使用 uv 运行(推荐):uv run scripts/extract.py
uv run 创建隔离环境,安装声明的依赖项,并运行脚本。pipx (pipx run scripts/extract.py) 也支持 PEP 723。
- 使用 PEP 508 说明符固定版本:
"beautifulsoup4>=4.12,<5"。
- 使用
requires-python 约束 Python 版本。
- 使用
uv lock --script 创建锁文件以实现完全可复现性。
Deno 的 npm: 和 jsr: 导入说明符使每个脚本默认都是独立的:#!/usr/bin/env -S deno run
import * as cheerio from "npm:cheerio@1.0.0";
const html = `<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>`;
const $ = cheerio.load(html);
console.log($("p.info").text());
deno run scripts/extract.ts
- 对 npm 包使用
npm:,对 Deno 原生包使用 jsr:。
- 版本说明符遵循 semver:
@1.0.0(精确),@^1.0.0(兼容)。
- 依赖项全局缓存。使用
--reload 强制重新获取。
- 带有原生附加组件(node-gyp)的包可能无法工作 — 提供预构建二进制文件的包效果最好。
当未找到 node_modules 目录时,Bun 会在运行时自动安装缺失的包。直接在导入路径中固定版本:#!/usr/bin/env bun
import * as cheerio from "cheerio@1.0.0";
const html = `<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>`;
const $ = cheerio.load(html);
console.log($("p.info").text());
bun run scripts/extract.ts
- 不需要
package.json 或 node_modules。TypeScript 原生工作。
- 包全局缓存。首次运行下载;后续运行几乎瞬间完成。
- 如果目录树中任何位置存在
node_modules 目录,自动安装将被禁用,Bun 回退到标准 Node.js 解析。
Bundler 自 2.6 版本起随 Ruby 一起提供。使用 bundler/inline 直接在脚本中声明 gems:require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'nokogiri'
end
html = '<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>'
doc = Nokogiri::HTML(html)
puts doc.at_css('p.info').text
- 显式固定版本(
gem 'nokogiri', '~> 1.16')— 没有锁文件。
- 工作目录中现有的
Gemfile 或 BUNDLE_GEMFILE 环境变量可能会干扰。
为代理使用设计脚本
当代理运行您的脚本时,它读取 stdout 和 stderr 来决定下一步做什么。一些设计选择使脚本更容易被代理使用。
避免交互式提示
这是代理执行环境的硬性要求。代理在非交互式 shell 中运行 — 它们无法响应 TTY 提示、密码对话框或确认菜单。阻塞等待交互式输入的脚本将无限期挂起。
通过命令行标志、环境变量或 stdin 接受所有输入:
# 不好:挂起等待输入
$ python scripts/deploy.py
目标环境:_
# 好:带有指导的清晰错误
$ python scripts/deploy.py
错误:--env 是必需的。选项:development, staging, production.
用法:python scripts/deploy.py --env staging --tag v1.2.3
使用 --help 记录用法
--help 输出是代理了解脚本接口的主要方式。包括简短描述、可用标志和用法示例:
用法:scripts/process.py [OPTIONS] INPUT_FILE
处理输入数据并生成摘要报告。
选项:
--format FORMAT 输出格式:json, csv, table(默认:json)
--output FILE 将输出写入 FILE 而不是 stdout
--verbose 将进度打印到 stderr
示例:
scripts/process.py data.csv
scripts/process.py --format csv --output report.csv data.csv
保持简洁 — 输出会与其正在处理的其他内容一起进入代理的上下文窗口。
编写有帮助的错误消息
当代理收到错误时,消息直接塑造其下一次尝试。不透明的 “Error: invalid input” 浪费了一次回合。相反,说明出了什么问题、预期是什么以及尝试什么:
错误:--format 必须是以下之一:json, csv, table.
收到:"xml"
使用结构化输出
优先使用结构化格式 — JSON、CSV、TSV — 而不是自由格式文本。结构化格式可以被代理和标准工具(jq, cut, awk)消耗,使您的脚本可在管道中组合。
# 空格对齐 — 难以以编程方式解析
NAME STATUS CREATED
my-service running 2025-01-15
# 分隔 — 明确的字段边界
{"name": "my-service", "status": "running", "created": "2025-01-15"}
将数据与诊断分开: 将结构化数据发送到 stdout,将进度消息、警告和其他诊断发送到 stderr。这让代理能够捕获干净、可解析的输出,同时在需要时仍能访问诊断信息。
进一步考虑
- 幂等性。 代理可能会重试命令。“如果不存在则创建”比“创建并在重复时失败”更安全。
- 输入约束。 用清晰的错误拒绝模糊的输入,而不是猜测。尽可能使用枚举和封闭集。
- 试运行支持。 对于破坏性或状态操作,
--dry-run 标志让代理预览将要发生的事情。
- 有意义的退出代码。 对不同的失败类型使用不同的退出代码(未找到、无效参数、认证失败),并在
--help 输出中记录它们,以便代理知道每个代码的含义。
- 安全的默认值。 考虑破坏性操作是否需要显式确认标志(
--confirm, --force)或其他适合风险级别的保护措施。
- 可预测的输出大小。 许多代理框架会自动截断超过阈值(例如 10-30K 字符)的工具输出,可能会丢失关键信息。如果您的脚本可能产生大量输出,默认使用摘要或合理限制,并支持
--offset 等标志,以便代理在需要时请求更多信息。或者,如果输出很大且不适合分页,要求代理传递 --output 标志,指定输出文件或 - 以显式选择加入 stdout。