技能与自定义命令
SKILL.md 从哪些目录被发现、同名时谁先被加载,技能清单怎样以系统提醒进入上下文、Skill 工具怎样读取全文;Markdown 自定义命令的参数替换与 shell 展开及其安全边界,以及内置斜杠命令在 TUI、无头模式与 App 协议里的不同面貌。
ZCode 有两种“写一段 Markdown 就能扩展 Agent”的机制。技能是一个目录加一份 SKILL.md:模型平时只看到名字和描述,需要时调用 Skill 工具把正文读进上下文。自定义命令是一份 .md 提示词模板:用户输入 /名字 参数,模板展开成一条普通提示词交给模型,展开前还能先跑几段 shell。两者再加上 19 个内置斜杠命令,就是用户在输入框里敲 / 时能用到的全部东西。
代码分三层:发现与读取在 apps/zcode-cli/packages/adapters/src/skills/ 与 apps/zcode-cli/packages/adapters/src/commands/;装配、命令展开与 shell 执行在 apps/zcode-cli/packages/bootstrap/src/ 下的 skills.ts、custom-commands.ts、custom-command-prompt.ts、custom-command-shell-expansion.ts、builtin-prompt-command.ts、slash-command-surface.ts;Skill 工具与技能清单在 core/src/tool/handlers/skill.ts 和 core/src/context/sections/skills.ts;终端里的斜杠命令分发在 cli/src/command-center/。插件同样能携带技能与命令,插件本身留给下一篇。
怎么用
| 操作 | 入口 | 说明 |
|---|---|---|
| 列出技能 | zcode skills 或 zcode skills list | 每条显示名字、scope/source、描述与 SKILL.md 路径;--json 输出 JSON,--verbose 附诊断 |
| 查看一个技能 | zcode skills inspect <name> | 另显示 safeToAutoLoad、读取字节数与正文 |
| 列出或查看命令 | zcode commands list、zcode commands inspect <name> | inspect 显示解析出的 frontmatter,--verbose 才打印正文 |
| TUI 里列技能 | /skill | 与 zcode skills list 同一份文本 |
| 点名使用技能 | /skill <name> [task] | 改写成一条“先调 Skill 工具”的提示词 |
| 调用自定义命令 | /<name> [args] | 展开模板后作为普通提示词提交 |
| 帮助 | /help [command] | 内置命令与自定义命令一并列出 |
子命令分派在 apps/zcode-cli/packages/cli/src/run.ts:546;zcode --help 只写了 skills list 与 commands list(apps/zcode-cli/packages/i18n/src/locales/zh-CN.ts:19、zh-CN.ts:24),inspect 没有出现在帮助里,但两个子命令都支持它(apps/zcode-cli/packages/cli/src/skills-command.ts:26、apps/zcode-cli/packages/cli/src/commands-command.ts:30)。
个人技能放 ~/.zcode/skills/<name>/SKILL.md,项目技能放仓库里的 .zcode/skills/<name>/SKILL.md,两处都另认一个 .agents/skills;命令对应 ~/.zcode/commands/ 与 .zcode/commands/(外加 .agents/commands)下的 .md 文件。相关配置写在用户配置文件 ~/.zcode/cli/config.json(apps/zcode-cli/packages/adapters/src/config/file-config.adapter.ts:61)或项目的 .zcode/config.json 里:
| 键 | 默认 | 作用 |
|---|---|---|
features.skill、skills.enabled | 都是 true | 两者同时为真才创建技能端口,否则连 Skill 工具都不注册 |
skills.roots | [] | 额外技能根目录,支持 ~/ 与相对路径 |
skills.metadataBudget | 20000 | 技能清单的字符预算 |
skills.includeInstructions | true | 只被解析与合并,运行时没有读取它的地方 |
skill.<SKILL.md 绝对路径>.enable | 无 | 写 false 禁用单个技能 |
command.<命令文件绝对路径>.enable | 无 | 写 false 禁用单条命令 |
默认值在 apps/zcode-cli/packages/contracts/src/config/index.ts:313 与 contracts/src/config/index.ts:330。桌面设置页的技能开关写的是复数形式 skills.<路径>(packages/services/src/skills/skillsService.ts:568),解析配置时两种写法合并成同一张禁用表(apps/zcode-cli/packages/adapters/src/config/schema.ts:411),发现阶段直接剔除命中的路径(apps/zcode-cli/packages/bootstrap/src/skill-command-overrides.ts:5)。
技能从哪些目录来
resolveDefaultSkillRoots 按固定顺序排出根目录,每排一个就分到一个递增的 priority,步长 10(apps/zcode-cli/packages/adapters/src/skills/roots.ts:8)。核心一段(skills/roots.ts:34):
for (const extraRoot of options.extraRoots ?? []) {
roots.push(
root(
resolveConfiguredRoot(extraRoot, resolvedWorkingDirectory),
"project",
"zcode",
nextPriority(),
),
);
}
if (includeZcode) {
roots.push(...skillRootsForBase(home, "user", nextPriority));
}
const projectDirectories = await resolveProjectSkillDirectories(resolvedWorkingDirectory);
for (const directory of projectDirectories) {
if (includeZcode) {
roots.push(...skillRootsForBase(directory, "project", nextPriority));
}
}
roots.push(...(options.extraResolvedRoots ?? []));
return roots;| 顺序 | 根目录 | scope/source |
|---|---|---|
| 1 | skills.roots 里的每个目录 | project/zcode |
| 2 | ~/.zcode/skills、~/.agents/skills | user/zcode、user/agents |
| 3 | 从工作目录起逐级向上,每层的 .zcode/skills、.agents/skills,到含 .git 的那一层为止 | project/zcode、project/agents |
| 4 | 已启用插件的技能目录(由 bootstrap 传入) | 官方插件为 system/plugin,其余为 user/plugin |
找不到 .git 时只看工作目录本身(skills/roots.ts:61)。每一层的 .zcode 与 .agents 是合并而不是回退,注释写明“用户可能同时安装原生 .zcode skill 和兼容 .agents skill”(skills/roots.ts:99)。.agents 是注释里所说的“兼容”目录,名字不带任何产品前缀,ZCode 在技能和命令两处都认它;桌面设置页早先把它当成 .zcode 的后备,结果同层 .zcode 只要有一个技能,.agents 整根就从界面上消失,后来改成与 Agent 一致的合并语义(packages/services/src/skills/skillsService.ts:157)。插件技能根的 priority 从 1000 起步(apps/zcode-cli/packages/adapters/src/plugins/index.ts:104),scope 按插件来源区分(adapters/src/plugins/index.ts:704)。
同名时谁赢。discoverSkills 按路径而不是按名字去重,注释说“同名技能可能来自不同技能生态或版本,不能只按 name 去重;路径才是安装项身份”(apps/zcode-cli/packages/adapters/src/skills/index.ts:80),所以同名技能会全部出现在清单里。按名字排序时排序是稳定的(adapters/src/skills/index.ts:89),loadSkill 取第一个名字匹配的条目(adapters/src/skills/index.ts:108),效果是 priority 数值小的根目录胜出。这与多数人的直觉相反:~/.zcode/skills/foo 会压过仓库里的 .zcode/skills/foo,工作目录这一层压过上层目录,插件技能排在最后,而 skills.roots 里配的目录排在所有目录之前。
ZCode 不扫描 Claude Code 的技能目录 ~/.claude/skills 与 .claude/skills(见 Claude Code 手册的 Skills 篇)。想复用那边的技能,只能复制或软链接到 ~/.zcode/skills:用户级根跟随符号链接,注释明说这是受支持的导入方式(apps/zcode-cli/packages/adapters/src/skills/scan.ts:23)。
扫描策略
- 只看两层:根目录自身的
SKILL.md,加上一层子目录里的SKILL.md(scan.ts:43)。skills/engineering/foo/SKILL.md这种分组布局不会被 Agent 发现。共享策略里的MAX_SKILL_SCAN_DEPTH = 8只给桌面端的递归扫描用(packages/shared/src/skill-scan-policy.ts:43),桌面设置页因此“支持分组目录”(packages/services/src/skills/skillsService.ts:991)。结果是分组目录里的技能能在桌面界面上看到,Agent 的Skill工具却加载不到。 - 跳过的目录:
node_modules、dist、build、out、target、vendor、coverage、.cache、.next、.turbo、.venv、__pycache__(skill-scan-policy.ts:21),以及除.system以外所有以.开头的目录(skill-scan-policy.ts:50)。 - 符号链接:用户级根跟随链接;插件根在根目录、子目录、
SKILL.md三个粒度一律拒绝链接,防止插件借链接读到插件根外的文件(scan.ts:16、apps/zcode-cli/packages/adapters/src/skills/index.ts:141)。 - 错误:路径不存在视为常态、返回空;权限错误等其余异常记成
skill_scan_failed诊断,不中断其他根(adapters/src/skills/index.ts:149)。 - 大小:描述超过 1024 个字符的技能直接丢弃(
adapters/src/skills/index.ts:20、adapters/src/skills/index.ts:206);Skill工具最多读 100000 字节,超出部分截断(adapters/src/skills/index.ts:28、apps/zcode-cli/packages/core/src/tool/handlers/skill.ts:16)。
SKILL.md 的格式与 Claude Code 兼容程度
frontmatter 用一个手写的扁平 YAML 解析器读:只认顶层 键: 值,缩进行一律跳过,但支持 > 与 | 两种块标量,免得多行描述只剩一个符号(adapters/src/skills/index.ts:327)。规则如下(adapters/src/skills/index.ts:178):
- 没有 frontmatter:名字取目录名,描述可以为空,照样加载。
- 有 frontmatter 却缺
name或description:记 error 级诊断,丢弃该技能。 when_to_use会拼在描述后面一起进清单(apps/zcode-cli/packages/core/src/context/sections/skills.ts:61)。name、description、when_to_use、license、metadata之外的键不报错,只让safeToAutoLoad变成false(adapters/src/skills/index.ts:21、adapters/src/skills/index.ts:231)。这个字段只在zcode skills inspect里显示,没有任何地方据它拦截加载;同样,policy.allowImplicitInvocation恒为true(adapters/src/skills/index.ts:234),也没有消费者。- 正文里的
${ZCODE_SKILL_DIR}与${CLAUDE_SKILL_DIR}在加载时替换成技能目录(tool/handlers/skill.ts:73)。
所以兼容程度可以概括为:Claude Code 风格的 SKILL.md 能原样加载,CLAUDE_ 前缀的变量名也认,但目录不通用,控制类键不生效。仓库自己的 .agents/skills 就是例子:dep-refs 写了 disable-model-invocation: true(.agents/skills/dep-refs/SKILL.md:4),agent-browser 写了 allowed-tools(.agents/skills/agent-browser/SKILL.md:4),在 ZCode 里前者照样可以被模型自动调用,后者也不会放宽任何权限。
技能怎样进入上下文
技能在会话初始化上下文时发现一次(apps/zcode-cli/packages/core/src/runtime/methods/context.ts:65),之后整场会话用这份快照;输入框要展示技能时也读同一份快照,而不是另扫磁盘(context.ts:76)。清单由 buildSkillsSection 生成(core/src/context/sections/skills.ts:39):
function buildSkillsContent(skills: SkillMetadata[], budget: number): string {
const lines = [
"The following skills are available for use with the Skill tool:",
"",
];
const sortedSkills = [...skills].sort((a, b) =>
skillDisplayName(a).localeCompare(skillDisplayName(b)),
);
const skillLines = sortedSkills.map((skill) => formatSkillLine(skill, MAX_DESCRIPTION_CHARS));
const full = [...lines, ...skillLines].join("\n");
if (full.length <= budget) {
return full;
}
const namesOnly = sortedSkills.map(
(skill) => `- ${skillDisplayName(skill)}${bareAliasSuffix(skill)} (file: ${skill.path})`,
);
return [...lines, ...namesOnly].join("\n");
}每条是“名字、描述、(file: 路径)”,描述截到 250 个字符(skills.ts:10);插件技能显示为 插件名:技能名,后缀注明也能用裸名加载(skills.ts:75)。整份清单超过预算(默认 20000 个字符)就只列名字和路径,不会逐条丢弃。它不在系统提示词里:这一段的注入目标是 meta_user,拼成来源为 skills_listing 的附件(apps/zcode-cli/packages/core/src/context/builder.ts:282),属于请求前缀里的一条系统提醒(apps/zcode-cli/packages/core/src/system-reminder/source.ts:91)。当前工具表里没有 Skill 时(例如某些工作流子 Agent),整段省略,免得模型以为自己有一个其实不存在的工具(builder.ts:175)。提醒与缓存布局的细节见系统提示词、上下文与提醒。
Skill 工具
工具输入是 skill 加可选的 args,旧写法 name 仍被接受(apps/zcode-cli/packages/contracts/src/tools/skill.ts:9、contracts/src/tools/skill.ts:21),但处理函数只取 skill,args 被解析后就丢掉了(core/src/tool/handlers/skill.ts:19)。加载时 loadSkill 会重新跑一遍发现,按裸名或 插件名:技能名 取第一个匹配,读正文、去掉 frontmatter(adapters/src/skills/index.ts:95),然后包装成(tool/handlers/skill.ts:58):
return [
`<skill_content name="${loaded.metadata.name}">`,
`# Skill: ${loaded.metadata.name}`,
"",
expandSkillContextVariables(loaded.content, loaded.baseDirectory),
"",
`Base directory for this skill: ${loaded.baseDirectory}`,
"Relative paths in this skill are relative to this base directory.",
loaded.truncated ? "[Skill content truncated]" : "",
"</skill_content>",
]
.filter((line) => line.length > 0)
.join("\n");
};技能目录里的附属文件由模型按“Base directory”自己去读。工具声明为只读、并发安全、无需审批,超时固定 30000 毫秒(tool/handlers/skill.ts:101、tool/handlers/skill.ts:134);遥测只记录限定名、插件 ID 与来源,不带正文和描述(tool/handlers/skill.ts:52)。它只在技能端口存在时注册(apps/zcode-cli/packages/core/src/runtime/helpers/runtime-tools.ts:51),端口的创建条件就是前面表里的两个开关(apps/zcode-cli/packages/bootstrap/src/app/create-app.ts:745)。
工具说明里有一条“看到 <command-name> 标签说明技能已经加载,不要再调”(tool/handlers/skill.ts:99),但 ZCode 的代码里没有任何地方生成这个标签,这一条在 ZCode 里用不上。用户这一侧,/skill <name> [task] 并不自己读文件,而是把输入改写成一条提示词,要求模型先用 Skill 工具加载该技能再做事(apps/zcode-cli/packages/cli/src/command-center/slash-commands.ts:218)。直接输入 /技能名 的效果则取决于入口:TUI 与 -p 无头模式都只回一句 Unknown command;App 协议(桌面与 Web)找不到同名自定义命令时把原文交给模型,由模型按工具说明自己去调 Skill。
自定义命令:位置、名字与 frontmatter
命令根目录的排法与技能完全相同:~/.zcode/commands、~/.agents/commands,再从工作目录逐级向上到 Git 根,每层 .zcode/commands 与 .agents/commands,最后是插件的命令目录(apps/zcode-cli/packages/adapters/src/commands/roots.ts:20)。有两点不同:bootstrap 没有给命令传额外根目录,所以没有类似 skills.roots 的配置(apps/zcode-cli/packages/bootstrap/src/custom-commands.ts:91);同名命令先到先得,后来的记一条 custom_command_duplicate_name 警告(apps/zcode-cli/packages/adapters/src/commands/index.ts:71),因此同样是用户级命令压过项目级命令。
- 名字:取相对根目录的路径,目录分隔符换成
:,再转小写,frontend/test.md就是/frontend:test(adapters/src/commands/index.ts:270);必须匹配^[a-z0-9][a-z0-9_:-]{0,63}$,文件名里带点或空格的会被拒绝(adapters/src/commands/index.ts:22)。 - 扫描:递归扫描,深度上限 12,跟随符号链接,悬空链接跳过(
adapters/src/commands/index.ts:25、adapters/src/commands/index.ts:242);单个文件最多读 100000 字节(adapters/src/commands/index.ts:23)。 - 描述:取 frontmatter 的
description,没有就取正文第一行非空内容,去掉开头的#与列表符号;两者都没有则丢弃该命令(adapters/src/commands/index.ts:182、adapters/src/commands/index.ts:362)。
frontmatter 认六个键,其余的记 custom_command_unknown_frontmatter 警告但不影响加载(adapters/src/commands/index.ts:26、adapters/src/commands/index.ts:194):
| 键 | 实际作用 |
|---|---|
description | 补全列表与 /help 里的说明 |
argument-hint | 附在用法后面,如 /review <file> |
skills | 逗号分隔的技能名;展开时在提示词开头要求模型先用 Skill 工具加载它们 |
disable-noninteractive | 只在两处生效:App 协议的 / 目录剔除该命令,zcode commands list 注上 (interactive only) |
allowed-tools | 被解析,在 zcode commands inspect 里显示,运行时没有消费者 |
model | 同上,不会切换模型 |
disable-noninteractive 的两处消费见 apps/zcode-cli/packages/bootstrap/src/zcode-protocol/slash-commands.ts:43 与 apps/zcode-cli/packages/cli/src/commands-command.ts:176;-p 无头模式里没有找到对应的拦截。allowed-tools 与 model 在 Claude Code 里是有效的控制键(见 Claude Code 手册的 Skills 与 Commands 两篇),在 ZCode 里只是元数据。
参数替换与提示词包装
参数替换在 contracts 里(apps/zcode-cli/packages/contracts/src/commands/index.ts:138):
export function expandCustomCommandTemplate(input: {
args: string;
command: CustomCommandContent;
}): CustomCommandTemplateExpansion {
const args = input.args.trim();
const positional = splitCustomCommandArguments(args);
let usedArgumentsPlaceholder = input.command.content.includes(ALL_ARGUMENTS_TOKEN);
let body = input.command.content.replaceAll(ALL_ARGUMENTS_TOKEN, args);
body = body.replace(POSITIONAL_ARGUMENT_PATTERN, (_match, index: string) => {
usedArgumentsPlaceholder = true;
const offset = Number(index) - 1;
return positional[offset] ?? "";
});
if (args.length > 0 && !usedArgumentsPlaceholder) {
body = `${body.trimEnd()}\n\nUser arguments:\n${args}`;
}
return {
argumentCount: positional.length,
body,
usedArgumentsPlaceholder,
};
}$ARGUMENTS 换成整串参数,$1、$2 换成按空白切开的第几个参数,切分支持单双引号与反斜杠转义(contracts/src/commands/index.ts:191);缺位的编号换成空串。模板里一个占位符都没用、用户却给了参数时,参数以 User arguments: 一段附在末尾,不会丢。展开后的正文前面再加两行:Run custom command /名字. 与 Command source: scope/source.,声明了 skills 的还有两行加载要求(contracts/src/commands/index.ts:163)。最终提交给回合的是这段提示词,界面与历史里显示的仍是用户输入的原文(apps/zcode-cli/packages/bootstrap/src/app/input-facade.ts:131)。
展开入口是 bootstrap 的 resolveZCodeCustomCommandPrompt(apps/zcode-cli/packages/bootstrap/src/custom-command-prompt.ts:31):保留名直接放过,动态工作流灰度关闭时 /workflow 也放过(custom-command-prompt.ts:42,这个命令来自内置的 zcode-guide 插件),找不到命令时返回 undefined,原文当普通提示词交给模型。它挂在 input facade 上,每条提示词都先过一遍;在它之前还有一道内置的 /init,把输入换成一段生成或更新工作区 AGENTS.md 的长提示词(apps/zcode-cli/packages/bootstrap/src/builtin-prompt-command.ts:9、create-app.ts:793),所以名为 init 的自定义命令永远不会生效。
Shell 展开与它的边界
模板里可以写两种 shell 片段:行内是感叹号后接一对反引号包住的命令,块状是以三个反引号加感叹号开头的代码块(apps/zcode-cli/packages/bootstrap/src/custom-command-shell-expansion.ts:13)。展开时每段依次执行,用标准输出(去掉末尾空白)替换原文,执行方式如下(custom-command-shell-expansion.ts:119):
const result = await input.executionPort.run(
{
command: {
mode: "shell",
command: input.shellCommand,
},
cwd: input.workingDirectory,
env: createShellExpansionEnv({
plugin,
sessionId: input.sessionId,
workingDirectory: input.workingDirectory,
}),
outputLimit: {
maxBufferBytes: DEFAULT_SHELL_EXPANSION_OUTPUT_BYTES,
maxInlineBytes: DEFAULT_SHELL_EXPANSION_OUTPUT_BYTES,
persistOutput: "none",
},
timeoutMs: DEFAULT_SHELL_EXPANSION_TIMEOUT_MS,
// ...
},
{ signal: input.signal },
);
if (result.status === "completed" && (result.exitCode ?? 0) === 0) {
return result.stdout.text.trimEnd();
}
throw new Error(formatShellExpansionError(input.command, input.shellCommand, result));- 限制:每段超时 30000 毫秒,输出上限 128 KiB(
custom-command-shell-expansion.ts:10);任何一段非零退出,整条命令就以shell expansion failed报错,不会带着半截结果进回合。 - 环境变量:子进程拿到
ZCODE_PROJECT_DIR,有会话时加ZCODE_SESSION_ID,插件命令再加ZCODE_PLUGIN_ROOT、ZCODE_PLUGIN_DATA、ZCODE_PLUGIN_ID、ZCODE_PLUGIN_NAME;除后两个外都有对应的CLAUDE_别名(custom-command-shell-expansion.ts:160)。这些是导出的环境变量,不是文本替换;命令里引用技能目录变量会直接报错,因为命令没有技能上下文(custom-command-shell-expansion.ts:193)。 - 不经过权限系统:展开直接调用执行端口,不走工具执行器里的钩子、权限模式与审批,也不受 plan 模式约束(对照权限模式与规则与执行边界)。另外参数替换发生在 shell 展开之前(
custom-command-prompt.ts:59),写在 shell 片段里的$1会原样拼进命令行。
三个入口对 shell 展开的态度并不一致。bootstrap 的解析器总带着执行端口(create-app.ts:808),所以 App 协议与 -p 无头模式都会执行;无头模式为了不把用户的 shell 片段跑两遍,探测命令是否存在时只读文件、不展开(apps/zcode-cli/packages/cli/src/prompt-command.ts:464)。TUI 空闲时提交走 CLI 自己的展开函数,遇到这两种语法直接报“Dynamic expansion is not available yet”(apps/zcode-cli/packages/cli/src/custom-command-expand.ts:26);可一旦回合正在运行,TUI 改走 sendInput 把原文交给 App,同一条命令反而会被执行(apps/zcode-cli/packages/cli/src/tui-prompt-handler.ts:338)。
仓库里的 .zcode/commands 与 .agents/commands 属于项目内容。克隆一个陌生仓库后,只要输入了其中某条命令的名字,它模板里的 shell 片段就会以当前用户身份执行,事先不会弹出任何审批;从代码看,这里没有类似工作区钩子那样的信任闸门(钩子的信任机制见生命周期 Hooks 与工作区信任)。
内置斜杠命令与三个入口
内置命令只有一张表:BUILTIN_ZCODE_SLASH_COMMAND_HELP_ENTRIES(packages/shared/src/zcode-slash-command-help.ts:9),/help 的文本、TUI 的补全、保留名单都从它生成。19 条按在 TUI 里的去向分组:
| 去向 | 命令 |
|---|---|
| TUI 本地处理,不进模型 | /help、/login、/logout、/effort(别名 /variant)、/locale(/language)、/mode、/model、/mcp、/plugins(/plugin)、/new(/clear)、/resume(/continue)、不带参数的 /skill |
| 改写成提示词再提交 | /init、/skill <name> [task] |
| 交给回合,在运行时里再识别 | /compact、/rewind、/fork |
| 专门的处理器 | /goal(/target)、/expert、/dwf |
TUI 的分派在 apps/zcode-cli/packages/cli/src/command-center/create.ts:38:先用 parseSlashCommand 识别内置名字(command-center/slash-commands.ts:17),认不出的当成自定义命令,再找不到才报 Unknown command(create.ts:63)。补全列表是内置条目加自定义命令,技能不在其中(command-center/slash-commands.ts:238)。/compact、/rewind、/fork 在回合入口由运行时自己解析(apps/zcode-cli/packages/core/src/runtime/methods/turn.ts:105,解析器在 apps/zcode-cli/packages/core/src/runtime/helpers/commands.ts:5),各自的机制见上下文压缩与检查点、回退与分叉。
App 协议(桌面与 Web)是另一个面:/ 目录里的内置命令只有 goal、compact、init,外加只给 App 输入框用的 plan(apps/zcode-cli/packages/bootstrap/src/slash-command-surface.ts:3);自定义命令去掉 disable-noninteractive 与保留名后附在后面,来自 zcode-guide 的 /workflow 被钉在 goal 之后(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/slash-commands.ts:27、zcode-protocol/slash-commands.ts:67)。保留名单是全部内置名字与别名,再加 compress 和 plan(slash-command-surface.ts:19),自定义命令起了这些名字,在 bootstrap 这一层永远不会展开。桌面快捷键表里的“Command Center”(默认 CmdOrCtrl+k)是另一个东西,是界面上的命令面板(packages/shared/src/shortcutCommands.ts:63),与这里的斜杠命令分发器无关。
仓库里的 .agents/skills 与产品的内置技能
仓库根目录的 .agents/skills 有 8 个技能:agent-browser、ai-elements、architecture-governance、dep-refs、dogfood、electron、feature-boundary-planner、react-best-practices,其中几个注明派生自 Vercel 的开源项目。它们是给在本仓库里干活的 Agent 用的开发工具,AGENTS.md 要求改代码前先用 architecture-governance 做架构检查(AGENTS.md:40)。因为 ZCode 本身就读项目的 .agents/skills,在这个仓库里开 ZCode 会话也能看到它们,但它们不随产品发布。
产品自带的技能全部来自官方插件:Browser Use 插件的 control-browser 与 web-gui-tester(apps/zcode-cli/packages/browser-use-plugin/skills/);文档、PDF、演示文稿、电子表格四个插件各带一个 docx、pdf、pptx、xlsx 技能(apps/zcode-cli/packages/bootstrap/src/app/official-plugin-definitions.ts:175);plugin-creator 与 skill-creator 带同名技能(official-plugin-definitions.ts:266、packages/ui/src/lib/builtinSkillI18n.ts:120);zcode-guide 带配置指南、自诊断技能与 dynamic-workflows 编写指南,外加 /workflow 命令(official-plugin-definitions.ts:82、official-plugin-definitions.ts:297);默认关闭的两个模拟器插件分别带 ios-dev 与 android-dev(builtinSkillI18n.ts:88、builtinSkillI18n.ts:45)。开源仓库里只有 browser-use-plugin 与 node-repl-host 两个插件包,其余只有定义没有内容;superpowers-plugin 目录下只剩一份 LICENSE,第三方清单写明原先内置的插件实现已被移除(third-party/copied-components.json:241)。桌面端还能把本机的用户级技能打包同步到远程工作区(packages/services/src/skill-sync/skillSync.ts:11),见远程工作区与手机远控。
下一篇:插件与官方市场——插件能带来哪些组件、清单怎样解析、官方市场的内置与 CDN 两个分区,以及插件从来源到运行时的整条装载链。