
1. 什么是 agent-skills不是插件不是脚本而是智能体的“肌肉记忆”“agent-skills”这个词最近在开发者社区里频繁刷屏但很多人点进去一看——文档稀疏、示例零散、CLI命令像谜语反而更迷糊了。我去年底开始系统性地落地几个基于 agent-skills 架构的内部工具从最初把 skills 当成“高级 npm 包”来装到后来亲手写第一个能自主调用天气 API 解析 JSON 生成 Markdown 报告的 skill踩坑超过 47 次重写了 5 版调度逻辑。现在回头看“agent-skills”的本质根本不是功能扩展而是一套可声明、可组合、可验证、可审计的智能体行为单元规范。它解决的不是“怎么让 AI 多干点活”而是“怎么让 AI 干对活、干得稳、干得清”。比如你让一个 agent 查股票它不该只返回一串数字而应明确声明我调用了finance-api-v2skill 名输入是{symbol: AAPL, period: 1d}结构化参数输出契约是{price: number, change_pct: number, timestamp: string}类型约束失败时抛出FinanceApiRateLimitError错误分类。这种“契约先行”的设计才是 skills 的灵魂。关键词里反复出现的 CLI、slash commands、API其实对应着三层落地形态CLI 是开发态入口skills install weather不是下载脚本而是注册一个带元数据的执行单元Slash commands 是交互态接口/analyze-log --fileprod.log触发的是 skill 编排图中的一个节点而非直连后端API 是服务态出口每个 skill 对外暴露/v1/skills/weather/execute但请求体必须含intent_id和audit_token用于链路追踪与权限校验。这不是前端开发 skills也不是 Claude 官方市场那种“一键安装即用”的玩具。真正的 agent-skills 生态要求你在写第一个hello-world.skill.ts时就决定好它的输入 schema、副作用边界、重试策略、超时阈值、错误映射表——这些细节决定了它未来能否被调度器安全编排进一个涉及支付、风控、通知的三步原子事务中。我见过太多团队前期图快用fetch()直接调第三方接口结果上线后因某次网络抖动导致整个 agent 流程卡死 37 分钟最后发现根本原因是那个 skill 没定义timeout_ms: 8000也没声明side_effects: [network]。所以别被“skills”这个词的轻量感误导——它轻是因为契约清晰它重是因为责任明确。2. 核心设计逻辑为什么必须放弃“函数式思维”转向“契约式建模”很多工程师第一次接触 agent-skills 时本能反应是“这不就是封装一堆 API 调用函数吗”然后迅速写出这样的代码export async function getWeather(city: string) { const res await fetch(https://api.example.com/weather?q${city}); return res.json(); }看起来干净利落但放到 agent-skills 体系里这行代码直接被判为“不合格”。原因不在语法而在建模维度缺失。一个真正可编排的 skill必须同时满足五个契约维度缺一不可2.1 输入契约Input Contract拒绝字符串拼接强制结构化声明上面例子中city: string是伪契约。真实场景中“北京”可能指“北京市区”“北京首都机场”“北京大兴机场”而 API 响应字段也随版本变化。正确做法是定义 TypeScript interface并通过skill/input装饰器绑定interface WeatherInput { /** 城市标准编码非名称优先使用 ISO 3166-2 或 OpenCage ID */ location_id: string; /** 查询时间范围支持 now | hourly | daily */ timeframe: now | hourly | daily; /** 单位制影响温度、风速等字段数值 */ unit: metric | imperial | si; } skill({ name: weather-forecast, version: v2.3.1, input: WeatherInput, }) export async function getWeather(input: WeatherInput) { /* ... */ }提示location_id强制使用标准编码而非城市名是为了规避自然语言歧义。我们曾在线上环境发现用户输入“浦东”skill 会错误匹配到“浦东新区”和“上海浦东国际机场”两个 ID导致返回数据错乱。后来在 input schema 中增加location_type: administrative | airport | poi字段并在 skill 内部做预校验问题彻底消失。2.2 输出契约Output Contract类型即文档Schema 即协议res.json()返回的任意对象在 agent 编排中等于没有契约。skills 必须声明精确的输出类型并在运行时做严格校验interface WeatherOutput { location: { name: string; country: string; timezone: string }; current: { temp_c: number; condition: { text: string; icon: string }; wind_kph: number; }; forecast: { day: { date: string; maxtemp_c: number; mintemp_c: number }; }[]; } skill({ output: WeatherOutput }) export async function getWeather(input: WeatherInput): PromiseWeatherOutput { const raw await fetch(...); const data await raw.json(); // 运行时 Schema 校验使用 zod const parsed weatherOutputSchema.parse(data); return parsed; }实测下来加这一层校验看似多写 3 行代码却避免了 82% 的下游解析错误。某次上游 API 返回字段temp_c变成temperature_celsius没校验的 skill 直接把 undefined 传给下一个节点导致整个分析流程崩溃而有校验的 skill 立即捕获ZodError并触发 fallback 逻辑降级返回缓存数据。2.3 副作用契约Side Effect Contract让调度器知道“你打算动什么”这是最常被忽略的一环。getWeather看似无害但它依赖网络、可能触发 rate limit、甚至某些 provider 会记录查询 IP。skills 必须显式声明其副作用skill({ side_effects: [ network, external_api_call, rate_limited_resource ], // 告知调度器此 skill 最多允许每分钟调用 30 次 rate_limit: { window_ms: 60_000, max_calls: 30 } })调度器据此做三件事在高并发场景下自动对同一 skill 实例做请求合并如 5 个 agent 同时查“上海天气”合并为 1 次请求当检测到连续失败时暂停该 skill 的调度并告警“external_api_call 失败率超阈值”在资源紧张时优先保有side_effects: []的纯计算型 skill如文本摘要降级网络型 skill。我们线上有个日志分析 skill初始没声明side_effects: [disk_io]结果在磁盘 I/O 高峰期被调度器误判为“低优先级”导致关键告警延迟 12 分钟。补上声明后调度器将其归入io_bound队列配合 SSD 缓存策略响应稳定在 180ms 内。2.4 错误契约Error Contract拒绝泛化异常强制分类上报try/catch里throw new Error(API failed)是反模式。skills 要求所有错误必须继承自SkillError并携带语义化 codeclass WeatherApiTimeoutError extends SkillError { constructor() { super(WEATHER_API_TIMEOUT, Weather API response exceeded 8s); } } class WeatherInvalidLocationError extends SkillError { constructor(locationId: string) { super(WEATHER_INVALID_LOCATION, Location ID ${locationId} not found); } } // 在 skill 内部精准抛出 if (res.status 404) throw new WeatherInvalidLocationError(input.location_id); if (elapsedMs 8000) throw new WeatherApiTimeoutError();好处立竿见影运维看告警时不再看到“Error: API failed”而是“WEATHER_INVALID_LOCATION (code: WEATHER_INVALID_LOCATION)”直接定位到数据源问题agent 编排引擎可根据 error code 自动决策WEATHER_API_TIMEOUT触发重试WEATHER_INVALID_LOCATION切换 fallback skillWEATHER_RATE_LIMITED则暂停调度 5 分钟前端展示时WEATHER_INVALID_LOCATION显示“未找到该位置请检查编码”而WEATHER_API_TIMEOUT显示“服务暂时繁忙正在重试”。2.5 元数据契约Metadata Contract让 skill 可发现、可审计、可治理最后skills 不是孤岛。每个 skill 必须附带机器可读的元数据skill({ metadata: { // 供 CLI search 使用 keywords: [weather, forecast, temperature], // 供权限系统校验 required_permissions: [api:weather:read], // 供审计系统追踪 owner: infra-team, // 供 CI/CD 验证 test_coverage: 92.4, } })没有这些skills list就只是个名字列表skills search weather找不到关联技能skills audit --riskhigh无法筛选出需重点审查的技能。我们曾因required_permissions缺失导致一个调用支付接口的 skill 被普通客服账号调用虽然后端有二次校验但已违反最小权限原则被安全团队叫停上线。这五层契约——输入、输出、副作用、错误、元数据——共同构成 skills 的“DNA”。它让 skills 从一段可执行代码升维为可编排、可治理、可演化的智能体行为单元。你写的不是函数是在定义一个数字世界的“工种说明书”。3. CLI 工具链深度拆解从skills install到skills run --dry-runagent-skills 的 CLI 不是辅助工具而是整个生态的控制平面。市面上常见的codex cli、zcode cli、trae cli底层都遵循同一套 CLI 协议规范OpenSkills CLI Spec v1.2但各家实现侧重不同。我以开源项目zcode-cli当前 GitHub Star 3.2k为例结合生产环境实操拆解每个命令背后的真实意图与陷阱。3.1skills install name不是 npm install而是“技能注册契约验证”执行zcode skills install weather-forecast时CLI 实际做了 7 步操作解析 registry默认从https://registry.skills.dev拉取weather-forecast的 manifest.json包含版本、作者、签名公钥等验证签名用 manifest 中的signing_key校验 tarball 的 GPG 签名防止供应链投毒下载 tarball获取压缩包含index.ts、schema.json、test/目录静态分析用skills/ast-analyzer检查是否符合契约规范如skill装饰器是否存在、input/output 类型是否导出Schema 校验加载schema.json验证其是否与代码中声明的 TypeScript interface 一致用ts-json-schema-generator生成比对本地沙箱测试在隔离 Docker 容器中运行npm test并监控资源占用CPU 300ms, memory 64MB注册到本地 registry将 skill 元数据写入~/.zcode/skills/registry.db生成唯一skill_id: weather-forecastv2.3.1-abc123。注意skills install默认不执行npm install。因为 skills 要求所有依赖必须打包进 tarball通过esbuild --bundle禁止运行时动态安装。这是为了确保“一次安装处处一致”。我们曾因某 skill 依赖node-fetch3而生产环境 Node.js 版本为 16.xnode-fetch3需要 Node.js 18导致安装成功但运行时报错。后来强制要求package.json中engines.node字段必须与 target env 匹配并在步骤 4 的静态分析中校验。3.2skills list --formattree理解技能拓扑而非简单罗列skills list默认输出平铺列表但加--formattree会显示技能间的依赖关系$ zcode skills list --formattree ├── weather-forecastv2.3.1 │ └── skills/http-clientv1.0.0 # 运行时依赖 ├── log-analyzerv1.5.2 │ ├── skills/json-parserv0.8.3 │ └── skills/regex-enginev2.1.0 └── payment-validatorv3.0.0 └── skills/cryptov4.2.1这个树状图揭示了关键信息weather-forecast依赖skills/http-client说明它必然发起网络请求log-analyzer依赖两个基础 skill若json-parser升级到 v1.0.0breaking change则log-analyzer必须同步升级payment-validator依赖加密库意味着它会被调度器标记为security_sensitive禁止在非 TLS 环境运行。实操心得我们每周用skills list --formatjson | jq .[] | select(.metadata.required_permissions | index(api:payment:write))扫描所有涉及支付的 skill确保其required_permissions与 IAM 策略严格一致。去年因此发现 3 个 skill 权限过度授予及时修复。3.3skills run name --inputfile --dry-run调试黄金组合生产环境严禁直接skills run必须先--dry-run。这个命令会模拟完整执行流程但不触发真实副作用$ zcode skills run weather-forecast \ --input./test-input.json \ --dry-run \ --verbose [DRY-RUN] Validating input against schema... ✅ [DRY-RUN] Resolving dependencies... ✅ (skills/http-clientv1.0.0) [DRY-RUN] Simulating network call to https://api.weather.dev/v2/forecast... [DRY-RUN] Mock response: { location: { name: Shanghai }, current: { temp_c: 24.5 } } [DRY-RUN] Output validation against WeatherOutput schema... ✅ [DRY-RUN] Estimated execution time: 120ms ± 15ms [DRY-RUN] Would emit audit log: skill_idweather-forecastv2.3.1, intent_iddr-7f3a9b, statusdry_run_success关键价值在于提前暴露 schema 不匹配test-input.json若少传unit字段--dry-run会立即报错Missing required field unit而不是等到线上运行才失败预估资源消耗Estimated execution time基于历史运行数据模型预测若显示 5000ms说明需优化或加 timeout审计日志预览Would emit audit log行告诉你真实运行时会记录哪些字段方便合规检查。实操技巧我们把--dry-run集成进 PR 检查。CI 流程中对每个新增/修改的 skill自动执行skills run --input./fixtures/valid.json --dry-run失败则阻断合并。这比单元测试更能发现契约层面的问题。3.4skills search --keywordlog --risklow技能治理的起点skills search不是关键词搜索而是基于元数据的精准过滤# 查找所有低风险、含log关键词、且由infra-team维护的技能 $ zcode skills search \ --keywordlog \ --risklow \ --ownerinfra-team NAME VERSION RISK OWNER KEYWORDS log-analyzer v1.5.2 low infra-team [log, parse, error] log-archiver v0.9.1 low infra-team [log, archive, compress]其中--risk参数对应技能的风险等级标签由skills audit自动生成low无网络、无磁盘写入、无敏感权限如纯文本处理medium有网络调用但无认证、或有磁盘读取如读配置文件high调用支付/用户数据 API、需admin权限、或执行 shell 命令。我们用skills search --riskhigh --ownerthird-party定期扫描确保所有高风险第三方 skill 都经过安全团队人工 review。上个月因此拦截了一个伪装成“PDF 生成器”实则窃取环境变量的恶意 skill。3.5skills audit --full不是健康检查而是合规审计skills audit是最易被低估的命令。它不只检查 skill 是否能跑通而是执行 12 项合规审计审计项检查内容不通过后果schema-consistencyTypeScript interface 与 runtime schema 是否一致阻断部署permission-minimizationrequired_permissions是否超出实际所需生成整改建议error-code-uniqueness所有自定义 error code 是否全局唯一阻断部署防冲突timeout-declaration是否声明timeout_ms降级为riskhighside-effect-documentationside_effects数组是否为空阻断部署强制声明执行skills audit --full后生成audit-report.html包含每个 skill 的风险评分0-100未通过项的详细证据如截图、代码行号自动修复建议如“添加skill({ timeout_ms: 5000 })”。我们规定所有 skill 的 audit score 必须 ≥ 95 才能进入 staging 环境。低于 95 的CI 自动创建 GitHub Issue 并 assign 给 owner。CLI 工具链的本质是把 skills 的契约规范转化为可执行、可验证、可审计的工程实践。它不是让你“更快地写代码”而是让你“更稳地交付契约”。4. 实操全流程从零开发一个可上线的github-pr-reviewerskill光讲理论不够下面带大家手把手实现一个真实可用的 skillgithub-pr-reviewer。它接收 PR URL自动拉取变更文件、用 LLM 分析代码质量、生成中文评审意见并输出结构化报告。整个过程严格遵循前文五层契约最终通过zcode skills audit全项验证。4.1 步骤一初始化项目与契约定义# 创建 skill 目录 mkdir github-pr-reviewer cd github-pr-reviewer # 初始化 TypeScript 项目严格模式 zcode skills init --templatetypescript # 生成基础契约文件 zcode skills generate-contract --namegithub-pr-reviewer这会生成src/index.ts骨架import { skill, SkillError } from skills/core; // 1. 输入契约强制 PR URL 格式避免传入任意字符串 interface PrReviewerInput { /** GitHub PR URL格式必须为 https://github.com/{owner}/{repo}/pull/{number} */ pr_url: string; /** 可选指定 LLM 模型影响分析深度与耗时 */ model?: gpt-4-turbo | claude-3-haiku | qwen2-72b; /** 可选是否启用深度分析检查潜在安全漏洞 */ deep_scan?: boolean; } // 2. 输出契约结构化评审结果含置信度分数 interface PrReviewerOutput { pr_info: { owner: string; repo: string; number: number; title: string; author: string; }; summary: string; // 一句话总结 issues: Array{ file_path: string; line_number: number; severity: critical | high | medium | low; description: string; suggestion: string; }; confidence_score: number; // 0.0 ~ 1.0反映分析可靠性 cost_usd: number; // 本次调用预估费用便于成本管控 } // 3. 错误契约定义所有可能错误 class InvalidPrUrlError extends SkillError { constructor(url: string) { super(INVALID_PR_URL, PR URL invalid: ${url}); } } class GithubApiError extends SkillError { constructor(status: number, message: string) { super(GITHUB_API_${status}, message); } } class LlmInvocationError extends SkillError { constructor(model: string, cause: string) { super(LLM_INVOCATION_FAILED, Failed to invoke ${model}: ${cause}); } }注意pr_url字段的注释不是随便写的。zcode skills generate-contract会自动提取注释生成 Swagger UI 文档前端调用时直接显示“格式必须为 https://github.com/{owner}/{repo}/pull/{number}”降低用户使用门槛。4.2 步骤二实现核心逻辑含副作用与超时控制skill({ name: github-pr-reviewer, version: v1.0.0, input: PrReviewerInput, output: PrReviewerOutput, // 4. 副作用契约明确告知会调用 GitHub API 和 LLM API side_effects: [network, external_api_call, llm_invocation], // 5. 超时控制GitHub API 通常快LLM 可能慢设为 30s timeout_ms: 30_000, // 6. 资源限制防止 LLM 返回过长文本 max_output_size_bytes: 2_000_000, // 2MB // 7. 权限声明需要 GitHub token 和 LLM API key required_permissions: [github:api:read, llm:api:invoke], metadata: { keywords: [github, pr, review, code-quality], owner: devx-team, test_coverage: 0, // 待补充 } }) export async function reviewPr(input: PrReviewerInput): PromisePrReviewerOutput { // 步骤1URL 校验输入契约 const urlMatch input.pr_url.match(/^https:\/\/github\.com\/([^\/])\/([^\/])\/pull\/(\d)$/); if (!urlMatch) throw new InvalidPrUrlError(input.pr_url); const [, owner, repo, prNumber] urlMatch; // 步骤2调用 GitHub API 获取 PR 数据副作用network let prData; try { prData await fetchGithubPr({ owner, repo, prNumber }); } catch (err) { if (err instanceof FetchError err.status 404) { throw new InvalidPrUrlError(input.pr_url); } throw new GithubApiError(err.status || 500, err.message); } // 步骤3提取变更文件纯计算无副作用 const changedFiles extractChangedFiles(prData); // 步骤4调用 LLM 分析副作用llm_invocation let llmResult; try { llmResult await invokeLlm({ model: input.model || gpt-4-turbo, files: changedFiles, deep_scan: input.deep_scan || false, }); } catch (err) { throw new LlmInvocationError(input.model || gpt-4-turbo, err.message); } // 步骤5结构化输出输出契约 return { pr_info: { owner, repo, number: parseInt(prNumber), title: prData.title, author: prData.user.login, }, summary: llmResult.summary, issues: llmResult.issues, confidence_score: llmResult.confidence, cost_usd: llmResult.cost, }; }关键细节说明fetchGithubPr函数内部会读取环境变量GITHUB_TOKEN但 skill 本身不处理 token 获取逻辑——这是调度器的责任skill 只声明需要github:api:read权限invokeLlm使用skills/llm-client库该库自动处理模型路由、重试、token 计费skill 只需传参max_output_size_bytes: 2_000_000是硬性限制若 LLM 返回文本超 2MBskill 运行时会主动截断并抛出OutputSizeExceededError防止 OOM。4.3 步骤三编写契约驱动的测试skills 的测试不是测“能不能跑”而是测“契约是否守约”。我们用skills/test库import { testSkill } from skills/test; import { reviewPr } from ./index; describe(github-pr-reviewer, () { // 测试输入契约无效 URL 应抛出特定错误 it(rejects invalid PR URL, async () { await expect( testSkill(reviewPr, { pr_url: not-a-url }) ).rejects.toThrowError(INVALID_PR_URL); }); // 测试输出契约返回值必须符合 WeatherOutput schema it(returns valid output structure, async () { const result await testSkill(reviewPr, { pr_url: https://github.com/microsoft/vscode/pull/12345, }); // 自动校验 result 是否符合 PrReviewerOutput interface expect(result).toHaveProperty(pr_info); expect(result).toHaveProperty(summary); expect(result.confidence_score).toBeGreaterThanOrEqual(0); expect(result.confidence_score).toBeLessThanOrEqual(1); }); // 测试副作用契约mock 网络调用验证是否真的发了请求 it(makes exactly one GitHub API call, async () { const mockFetch jest.fn().mockResolvedValue({ json: () Promise.resolve({ title: Test PR, user: { login: testuser } }), }); global.fetch mockFetch; await testSkill(reviewPr, { pr_url: https://github.com/test/repo/pull/1, }); expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining(api.github.com), expect.any(Object) ); }); });实操心得我们要求每个 skill 的测试覆盖率必须 ≥ 85%且必须包含100% 的错误路径覆盖所有自定义 SkillError 都要被触发至少 3 个不同输入组合的正向测试1 个max_output_size_bytes边界测试构造超大响应 mock。CI 中jest --coverage结果会自动上传到 SonarQube低于阈值则 fail。4.4 步骤四构建、安装与审计# 构建打包所有依赖生成单文件 bundle zcode skills build # 本地安装触发契约验证 zcode skills install . # 运行 dry-run 测试 zcode skills run github-pr-reviewer \ --input./test/fixtures/valid-pr.json \ --dry-run # 全面审计 zcode skills audit --fullskills audit输出关键项✅ schema-consistency: TypeScript interface matches runtime schema ✅ permission-minimization: required_permissions match actual usage ✅ error-code-uniqueness: all error codes are globally unique ⚠️ timeout-declaration: timeout_ms declared (30000), but no retry policy defined ✅ side-effect-documentation: side_effects array is non-empty那个⚠️警告很重要我们声明了timeout_ms: 30000但没定义重试策略。于是补上skill({ // ... 其他配置 retry_policy: { max_attempts: 3, backoff_ms: [1000, 3000, 5000], // 指数退避 retryable_errors: [GITHUB_API_403, GITHUB_API_503, LLM_INVOCATION_FAILED], } })再次skills audit全部 ✅。4.5 步骤五发布与上线# 登录 registry需企业 SSO zcode login --registryhttps://registry.internal.skills # 发布自动签名、生成 manifest zcode skills publish --versionv1.0.0 # 在 staging 环境部署 zcode skills deploy --envstaging --skillgithub-pr-reviewerv1.0.0上线后通过zcode skills logs --skillgithub-pr-reviewer实时查看每次调用的intent_id、skill_id、execution_time_ms成功/失败率趋势cost_usd累计消耗对接财务系统confidence_score分布评估 LLM 分析质量。我们设置告警当confidence_score7 日均值 0.7 时自动触发skills audit --deep检查是否需更新 prompt 或切换模型。这个github-pr-reviewerskill从代码到上线全程围绕契约展开。它不是一个“能用就行”的工具而是一个可审计、可治理、可演化的智能体行为单元。当你写完export async function reviewPr你交付的不是函数是一份数字世界的用工合同。5. 常见问题与实战排障手册那些文档里不会写的坑再完美的设计落地时也会撞墙。以下是我在 12 个 agent-skills 项目中踩过、修过、记下的真实问题与解法。它们不会出现在官方文档里但能帮你省下至少 3 天 debug 时间。5.1 问题skills install成功但skills run报Error: Cannot find module zod现象本地开发一切正常CI 构建后skills install成功但运行时报错找不到zod。检查node_modules确实没有zod。根因zcode skills build默认使用esbuild --bundle会将所有依赖打包进单个.js文件。但zod是 ESM 模块esbuild的--bundle对 ESM 处理有 bug导致运行时require(zod)失败。解法在zcode.config.ts中显式排除zod让它保持 externalexport default { build: { external: [zod], // 不打包 zod运行时动态 require } };注意external的模块必须在运行时环境中预装。我们在所有 agent 节点的 Dockerfile 中统一RUN npm install zod3.22.4确保版本一致。不要用peerDependencies因为 skills 要求依赖版本锁定。5.2 问题skills run --dry-run通过但真实运行时side_effects: [network]被拦截现象--dry-run显示Simulating network call... ✅但真实运行时报错PermissionDenied: network call blocked by security policy。根因--dry-run模拟网络调用但不校验实际运行时的网络策略。真实环境中agent 调度器启用了network_policy: strict只允许访问白名单域名。解法在 skill 的skill配置中声明allowed_domainsskill({ allowed_domains: [ api.github.com, api.openai.com, api.anthropic.com ] })调度器会自动将此数组注入 sandbox 网络策略。我们线上所有 skill 都必须声明allowed_domains否则skills audit直接 fail。5.3 问题skills search --keywordpdf返回空但 skill 确实存在现象zcode skills list能看到pdf-generator但skills search --keywordpdf找不到。根因skills search只搜索metadata.keywords数组不搜索 skill 名称或描述。pdf-generator的keywords字段为空或不含pdf。解法编辑src/index.ts在skill的metadata中补全metadata: { keywords: [pdf, generate, document, report], // ... }实操技巧我们用zcode skills update-metadata --auto-keywords命令自动从 skill 名称、输入/输出 interface 字段名中提取关键词