---
name: hacklyc/tool-creator
source: https://app.decimal.ai/s/hacklyc-tool-creator@1/SKILL.md
source_sha256: 78d549f58305
---

# Tool Creator — 创建并注册 Agent-CLI 工具

你正在 MyAgents 里运行。MyAgents 有一个**工具注册表**（`~/.myagents/tools/`）：注册进去的 CLI 工具会被投放到 PATH（`~/.myagents/bin/`），它的 description 会自动注入所有未来会话的 system prompt——**未来的 AI（包括别的 runtime 上的）会自己想起它、查它的用法、调用它**。用户也能在设置页「工具箱」里看到并管理它。

这个 skill 教你两件事：**写出一个对 Agent 友好的合格 CLI 工具**，以及**把它注册进去**。

## 第 0 步：判断要不要铸工具

铸一个工具是在为未来的几百次调用做投资，但注册表里的每个工具都占一行 system prompt。判断标准：

- **铸**：需求会重复出现（用户明说"以后还要用"，或你已经第二次写同类脚本）；有清晰的输入→输出边界；参数可枚举。
- **不铸**：一次性任务（就地写脚本跑完即弃）；纯交互探索类需求；已有注册工具能覆盖（先 `myagents tool list` 查一遍）。
- 模型管理器里已配置的模型、单发单收的调用——未来由 `myagents model call` 覆盖（若该命令存在，优先用它，不铸工具）。

灰色地带主动问用户："这个要不要我注册成工具，以后直接用？"

## 第 1 步：选原型

| 原型 | 特征 | 范例（动手前先读对应那个） |
|------|------|--------------------------|
| **A · 纯本地处理** | 文件进文件出，零网络、零密钥 | `references/example-local-tool.md` |
| **B · API 包装** | 调外部 API，有密钥，可能多步工作流（上传→轮询→取结果） | `references/example-api-tool.md` |

两个范例都是完整可跑的代码，**照着改，不要从零发明结构**。

## 第 2 步：写工具——形态契约

### 目录布局

```
~/.myagents/tools/<tool-name>/
├── tool.json      # manifest（注册时被读取校验）
└── run.mjs        # 入口，Node 单文件
```

### tool.json

```json
{
  "name": "md-merge",
  "version": "1.0.0",
  "description": "<≤800 字符，见第 3 步的撰写模板>",
  "entry": "run.mjs",
  "runtime": "node",
  "envKeys": [],
  "deps": []
}
```

- `name`：kebab-case，3–30 字符。**起名避开常见系统命令**（`curl`、`jq`、`git`、`node`…）——`~/.myagents/bin` 在 PATH 里排在系统路径之前，重名会遮蔽系统命令，注册时会被直接打回。加领域前缀最稳妥（`md-merge` 而不是 `merge`）。
- `envKeys`：工具需要的环境变量名列表（API key 等）。
- `deps`：依赖的外部二进制（`ffmpeg` 等），没有就空数组。

### 技术栈

内置 Node v24 单文件 + `node:util` 的 `parseArgs`，**零第三方依赖**。这不是偏好是约束：MyAgents 已内置 Node 并打通全部 PATH，单文件意味着没有 node_modules、拷目录即分发、跨平台问题已被产品解决过一遍。确实绕不开外部二进制时声明进 `deps` 并做启动自检（见生死线 8）。

### 八条生死线

每条都解释了违反的后果——它们不是风格偏好，是工具在 Agent 手里能不能活的分界：

1. **绝对禁止交互式输入。** 任何 stdin prompt（确认、选择、密码）都会让 Agent 的 shell 调用永久挂死——Agent 没有键盘。危险操作（删除、覆盖、花钱）用确认协议替代：缺 `--yes` 时打印将要做的变更 + 完整的带 `--yes` 重跑命令，以退出码 `4` 退出。Agent 会把变更展示给用户、获准后重跑。
2. **stdout 只放结果，stderr 放诊断，退出码语义化。** Agent 判断成败只靠这三样。退出码约定：`0` 成功 / `1` 一般错误 / `2` 用法或参数错误 / `3` 环境缺失（缺 env key、缺依赖）/ `4` 需要确认。进度提示、调试信息一律 stderr；spinner / 彩色转义码对 Agent 是纯噪音，不要。
3. **必须有 `--json` 模式，错误也要结构化。** 默认输出给人读的简洁文本；`--json` 输出机器可解析结果。出错时 stderr 给一行 JSON：`{"error": "...", "code": "...", "remediation": "怎么修"}`——`remediation` 是给下一个 Agent 的可行动建议（"run `myagents tool env <name> set KEY=...`"），不是模糊的 "something went wrong"。
4. **密钥走 env，绝不走 argv。** argv 会泄进进程列表和日志。从 `process.env[KEY]` 读，缺失时按退出码 `3` + remediation 处理。key 在 `tool.json::envKeys` 声明，由 `myagents tool env <name> set KEY=value` 设置。
5. **有界运行时间。** 不准变 daemon。所有网络请求带 `AbortSignal.timeout(...)`；轮询循环必须有次数/时间上限。预期超过 30 秒的操作在 readme 里声明耗时量级。
6. **产物文件输出绝对路径到 stdout。** 生成的文件落到当前工作区（或用户指定路径），把绝对路径作为结果打印——这是下游（预览、IM 发送）能接住产物的前提。
7. **readme 子命令返回标准化使用文档。** 固定章节（见第 3 步），未来的 AI 第一次用这个工具前会先跑 `<tool> readme`。
8. **依赖自检，失败要可行动。** `deps` 里声明的二进制在启动时探测（等效 `which`），缺失则按生死线 3 的错误格式给出安装指引 + 退出码 `3`——Agent 拿到的是"装 ffmpeg：brew install ffmpeg"，不是一个看不懂的崩溃栈。

### 三条进阶约定（让工具经得起时间）

- **接口即契约**：工具被注册后，未来的 Agent 会从历史会话、readme 缓存里学到它的参数。改版时只加不改不删——加新参数可以，改既有参数的含义/删参数等于在所有学过它的 Agent 脚下抽地毯。
- **高信号输出**：返回语义化字段（文件名、人类可读状态），不要裸 UUID / 内部 ID / 全量原始响应。输出大时做截断并提示如何取全量（`--limit`/`--offset`）。
- **本地先验证**：参数格式、文件存在性在发网络请求**之前**验证，错误更快更准（退出码 `2`）。

## 第 3 步：写三面文档

工具有三个文档面，**各答各的问题，不许混**：

| 面 | 回答 | 消费者 |
|----|------|--------|
| `description`（tool.json） | "什么情况下该想起我" | system prompt 注入 + 设置页列表 |
| `--help` | "参数怎么传" | AI 调用前现查 + 终端用户 |
| `readme` 子命令 | "解决这类问题的方法论" | AI 决定用之后 fetch + 设置页详情 |

### description（≤800 字符，超长注册时被打回）

这 800 字符决定未来的 AI 会不会想起这个工具。按五件套写：

1. 一句话能力声明
2. **触发条件**——给具体的用户说法（"用户要合并多个 markdown / 把文档拼成一个文件时"）。这是五件套里最值钱的部分：写"什么时候用"，不要重复"是什么"
3. 反模式排除（如有）："单文件转换不要用这个，直接处理即可"
4. 2–4 行 quick reference（最常用的调用形态）
5. 收尾固定句式：`首次使用前先运行 \`<tool> readme\`；机器可读输出加 --json`

写完做"新同事测试"：一个不知道这个工具存在的同事（或 AI），只读这 800 字符，能不能在对的时机想起它、并大致知道怎么开始？参数命名同理——`--input-file` 不要 `--i`，歧义是 Agent 的第一杀手。写不下的细节全部挪进 readme。

### --help

必含：用法契约行（`md-merge <files...> --out <path> [--json]`）、每个参数一行说明、**至少 3 条贴近真实的示例**（Agent 靠示例适配自己的场景）、退出码表。

### readme 子命令

固定章节，顺序不变（AI 每次读到的形状一致才能快速定位）：

```markdown
# <tool-name>
## 何时使用       # 适用场景 + 不适用场景
## 快速开始       # 最小可用示例
## 参数           # 完整参数表（与 --help 一致）
## 示例           # 3+ 个真实场景的完整命令
## 失败模式       # 每种退出码/常见错误的含义与处理
## 产物回流       # 产物落在哪、怎么展示给用户（见下）
```

**产物回流章节是强制的**：写明"调用方（Agent）拿到产物路径后，必须在回复中引用它（Markdown 链接/图片）；IM 会话里用 `myagents im send-media --file <path>` 发送"。工具跑成功但用户看不到结果 = 这次调用白跑。

## 第 4 步：自测三连

注册前在 shell 里依次验证，任何一项不过就修：

```bash
node run.mjs --help        # 用法契约行 + 示例齐全？
node run.mjs readme        # 六个固定章节齐全？
node run.mjs <真实参数>     # 一次真实调用：stdout 干净？exit code 对？--json 可解析？
```

原型 B 还要验：故意不设 env key 跑一次——应得到退出码 `3` + 带 remediation 的 JSON 错误，而不是裸异常栈。

## 第 5 步：注册 + 告知

```bash
myagents tool add ~/.myagents/tools/<tool-name>     # 校验 manifest、投 shim、进注册表
myagents tool env <tool-name> set API_KEY=<value>    # 原型 B：设密钥（让用户提供，绝不编造）
myagents tool list                                   # 确认出现在清单里
```

`tool add` 的常见打回：description 超 800 字符（精简后重试）、工具名撞系统命令（换名加前缀）。报错里带 recoveryHint，照做即可。

**注册成功后必须在回复中明确告知用户**（可审计性，不可省略）：

> 已注册工具 `md-merge`（合并多个 Markdown 文件）。我和之后的会话都能直接用它；你可以在 设置 → 工具箱 里查看、停用或删除它。

注册的工具对**当前 session 之外**的会话在它们下次启动时生效；你自己刚写完它，本 session 直接用就行。

如果 `myagents tool --help` 报 unknown command：当前 app 版本还没有注册机。把工具完整写好放在 `~/.myagents/tools/<name>/`，告知用户"工具已就绪，等应用更新后运行 `myagents tool add` 注册"。

## 速查：完整流程

```
判断值不值得铸（第 0 步）
→ 选原型 A/B，读对应 references 范例（第 1 步）
→ 写 tool.json + run.mjs，过八条生死线（第 2 步）
→ 写 description 五件套 / --help / readme（第 3 步）
→ 自测三连（第 4 步）
→ tool add + env + 告知用户（第 5 步）
```