从手动配置到预设即代码 逐行编写 ArgsTemplate 的繁琐在预设系统出现之前开发者需要为每条 CLI 命令在appsettings.json的Connectors字典中手写完整配置。以 GitHub CLIgh为例若要暴露 6 条常用命令——查看仓库元数据repo_view、列举议题issue_list、列举拉取请求pr_list、查看拉取请求详情pr_view、列举发布版本release_list以及添加评论issue_comment——手动配置的规模如下所示Connectors: { gh: { Enabled: true, Executable: gh, DefaultOutputFormat: Json, Commands: { repo_view: { ArgsTemplate: [repo, view, {{repo}}, --json, name,owner,description,url,isPrivate], Parameters: { repo: { Required: true, Pattern: ^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$ } }, OutputFormat: Json }, issue_list: { ArgsTemplate: [issue, list, --repo, {{repo}}, --json, number,title,state,author,url,labels], Parameters: { repo: { Required: true, Pattern: ^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$ } }, OutputFormat: Json }, pr_list: { ArgsTemplate: [pr, list, --repo, {{repo}}, --json, number,title,state,author,url,headRefName,baseRefName], Parameters: { repo: { Required: true, Pattern: ^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$ } }, OutputFormat: Json }, pr_view: { ArgsTemplate: [pr, view, {{number}}, --repo, {{repo}}, --json, number,title,state,url,mergeStateStatus,reviewDecision,headRefName,baseRefName], Parameters: { repo: { Required: true, Pattern: ^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$ }, number: { Required: true, Pattern: ^[0-9]$ } }, OutputFormat: Json }, issue_comment: { ArgsTemplate: [issue, comment, {{number}}, --repo, {{repo}}, --body, {{body}}], ReadOnly: false, RiskLevel: Medium, RequiresApproval: true, Parameters: { repo: { Required: true, Pattern: ^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$ }, number: { Required: true, Pattern: ^[0-9]$ }, body: { Required: true, MaxLength: 16000 } } } } } }这段配置仅覆盖 5 条只读命令和 1 条变更型命令却已占据超过 40 行 JSON。其中ArgsTemplate数组的每一元素都需精确对应 CLI 的 positional 和 flag 参数顺序--json输出格式标志后紧跟的字段列表必须与 GitHub CLI 的--json选项兼容Parameters字典中的正则表达式Pattern需要与对应参数的语法规则保持一致。任何一个元素的错位——例如将--json误写为--format json——都会导致命令执行失败或被拒绝解析 [1]。1.1.2 安全风险参数占位符与输出格式标志的误配手动配置中的安全隐患不止于格式错误。参数占位符{{parameter}}的注入风险是首要关注点若正则约束缺失或过于宽松LLM 生成的参数值可能突破预期范围将额外的 CLI 标志注入命令行。例如若repo参数未施加RepoPattern^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$约束攻击者可能构造owner/repo; rm -rf /形式的输入在未经适当转义的环境中触发命令注入 [2]。输出格式标志的误配同样构成安全威胁。GitHub CLI 的部分命令默认输出彩色文本而--json标志要求结构化数据。若两者混用解析器可能将 ANSI 转义序列误判为 JSON 内容导致下游处理异常。此外ReadOnly、RiskLevel和RequiresApproval三个安全属性的设置完全依赖配置者的安全判断——遗漏其中任何一个都可能导致变更型命令在未审批的情况下执行 [2]。1.1.3 维护困难CLI 工具版本升级时模板同步的成本CLI 工具的新版本经常调整命令语法、增减输出字段或废弃旧标志。以ghCLI 为例其--json可用字段列表在版本迭代中多次扩展。当本地安装的gh升级后网关中的ArgsTemplate可能与新版本不兼容——输出字段名变更导致 JSON 解析失败或旧标志被移除导致命令退出码非零。维护者需要逐条审查每个连接器的每条命令手动更新模板和字段列表这个成本随连接器数量和命令数量线性增长 [1]。1.2 预设系统的诞生1.2.1 从 40 行到 3 行ExternalCliPresetCatalog 的引入Commit113151c7以 972 行新增代码将预设系统注入 OpenClaw.NET其中核心文件ExternalCliPresetCatalog.cs占 682 行 [3]。该文件定义了一个编译期静态字典内置 8 个主流 CLI 工具的配置模板ghGitHub CLI、azAzure CLI、kubectlKubernetes CLI、stripeStripe CLI、larkLark CLI、github-copilot、codexOpenAI Codex CLI和geminiGoogle Gemini CLI[4]。覆盖版本控制、云原生、支付、协作和 AI Agent 五大技术领域。上述 40 余行的gh手动配置在使用预设后压缩为Presets: [gh], Connectors: { gh: { Enabled: true } }Executable字段从预设自动继承ArgsTemplate和Parameters从编译期字典填充全局选项超时、脱敏等沿用默认值。预设系统不是配置文件的替代而是配置文件的基线——它将经过安全审查的命令模板打包进程序集使开发者从重复性工作中解放出来 [1]。1.2.2 设计哲学保守默认、显式启用、安全基线不可降预设系统的安全设计围绕三个原则展开。第一保守默认appsettings.json中Presets的默认值为空数组[]未显式声明的预设不会导入任何命令模板确保升级后现有配置行为完全一致 [2]。第二显式启用opt-in预设导入后生成的连接器初始状态为Enabled: false必须在Connectors字典中单独设置Enabled: true方可执行。这种Preset 导入 Connector 激活的双层启用机制意味着单一配置层的遗漏不会导致命令面意外开放 [2]。第三安全基线不可降三层合并引擎Three-layer Merge Engine在应用用户配置覆盖时RiskLevel取Max(preset, configured)ReadOnly取preset AND configuredRequiresApproval取preset OR configured——预设所设定的风险等级、只读限制和审批要求均无法被用户配置削弱 [2]。1.2.3 本文目标不仅介绍系统更指导开发者扩展自己的预设预设系统的价值不仅在于开箱即用的 8 个模板更在于它为扩展提供了清晰的工程路径。ExternalCliPresetCatalog的静态字典结构、ExternalCliPresetDefinition的数据模型、以及Apply()方法的三层合并语义共同构成一套可预测的扩展接口。接下来的章节将首先带领读者遍历 8 个内置预设的全貌理解每个预设的命令覆盖范围和安全设计意图随后深入预设的数据模型和合并引擎的内部机制最终以一个完整的自定义预设开发示例收尾演示如何将一个新的 CLI 工具从零纳入 OpenClaw.NET 的治理体系。无论你是在评估是否采用预设系统还是正准备为团队内部的私有 CLI 工具编写预设本文都将提供可直接复用的代码片段和配置模式。2. 内置预设全景速览ExternalCliPresetCatalog以 682 行源码将 8 个主流 CLI 工具的保守模板固化到程序集中[1]。这些预设并非随机选取而是围绕版本控制、云原生基础设施、支付处理、企业协作和 AI Agent CLI 五大技术领域进行的有意识覆盖命令数量从 2 条到 6 条不等[2]。理解每个预设的风险设计策略是安全使用预设系统的前提。2.1 八大预设覆盖五大技术领域2.1.1 版本控制ghGitHub CLI— 6 条命令1 条变更型gh预设是内置预设中命令数量最多的一个覆盖 6 条常用命令。其中issue_comment为唯一变更型命令风险等级设为 Medium —— 该命令允许在 issue 上添加评论属于写入操作但不触及代码仓库本身[2]。其余 5 条命令均为查询类操作默认 Low 风险。标签github和vcsVersion Control System版本控制系统标识了该预设的技术归属。对于以 GitHub 为核心代码托管平台的团队gh预设提供了 issue 查询、PR 列表、仓库状态等高频操作的即开即用能力。2.1.2 云原生azAzure CLI与kubectl— 全只读设计az和kubectl两个云原生预设的全部命令均为只读ReadOnly设计[2]。az预设包含 3 条命令覆盖资源组列表、账户信息和 VM 状态查询kubectl预设包含 5 条命令覆盖 Pod 列表、服务列表、部署状态、节点信息和命名空间查询。这种全只读设计与生产环境中基础设施变更需经人工审批的安全实践一致——查询类操作可以自动化但任何可能修改集群状态或云资源的命令均被排除在预设之外。如果团队确实需要变更型云操作必须通过手动配置自行定义命令模板并显式设置对应的风险等级和审批要求。2.1.3 支付与协作stripePII 敏感与lark飞书stripe预设仅有 2 条命令但customers_list明确标记为 Medium 风险原因是客户列表可能包含个人身份信息PIIPersonally Identifiable Information[2]。Stripe 作为支付处理平台其 CLI 返回的数据天然涉及敏感金融信息即使纯粹的列表查询操作也需要提升风险等级以触发相应的脱敏和审批流程。lark飞书预设包含 5 条命令其中message_send是唯一变更型命令风险等级 Medium —— 该命令允许通过飞书 API 发送消息虽非破坏性操作但涉及企业通信渠道的写入权限[2]。标签lark、feishu和collaboration覆盖了该预设在中英文语境下的识别需求。2.1.4 AI Agent CLIgithub-copilot、codex、gemini— 统一 High 风险 强制审批三个 AI Agent CLI 预设采用与基础设施预设截然不同的安全策略。github-copilot2 条命令、codex4 条命令和gemini2 条命令的RiskLevel统一固定为HighRequiresApproval强制为true[3]。这一决策的技术依据在于这些 CLI 的prompt命令可将本地仓库上下文发送到外部 AI 服务而codex的exec命令在workspace-write模式下具备文件系统写权限[3]。每个 AI Agent 预设还配置了最大 prompt 长度限制8000 字符和非交互式模式参数-p或--sandbox确保自动化场景中不会触发交互式提示阻塞执行流[3]。codex预设进一步区分了只读沙箱read-only和可写沙箱workspace-write两种模式后者映射到 High 风险等级。以下矩阵汇总了 8 个内置预设的完整配置预设 ID连接器名称可执行文件命令数风险特征标签ghghgh61 条变更型issue_commentMediumgithub, vcsazazaz3全只读azure, cloudkubectlkubectlkubectl5全只读kubernetes, clusterstripestripestripe2customers_list为 Medium含 PIIstripe, paymentslarklarklark-cli51 条变更型message_sendMediumlark, feishu, collaborationgithub-copilotgithub-copilotcopilot2prompt为 HighAI Agentgithub, copilot, aicodexcodexcodex4exec_workspace_write为 Highcodex, openai, aigeminigeminigemini2prompt为 HighAI Agentgemini, google, ai从风险设计维度审视8 个预设呈现清晰的二分格局。5 个基础设施预设gh、az、kubectl、stripe、lark遵循保守策略仅当命令涉及数据写入或返回 PII 时方设为 Medium 风险其余查询类命令均为 Low 风险且默认只读[2]。这种最小必要风险原则确保日常查询操作不会触发不必要的审批阻塞。而 3 个 AI Agent 预设github-copilot、codex、gemini则统一采用最高安全级别——High 风险与强制审批的组合[3]。这一差异反映了基础设施 CLI 与 AI Agent CLI 在威胁模型上的本质区别前者的风险边界由命令本身的副作用定义后者的风险则来自本地数据向外部 AI 服务的自动传输以及 AI 生成代码对文件系统的潜在写入。命令数量方面gh以 6 条命令居首stripe和 AI Agent 预设以 2 条居末这种差异与 CLI 工具自身功能广度和预设系统的保守筛选策略均相关。2.2 安全策略矩阵2.2.1 风险等级分布预设系统的风险等级分为三级Low查询类操作、Medium涉及 PII 或轻度写入、High涉及外部数据传输或文件系统写入。5 个基础设施预设的绝大多数命令处于 Low 和 Medium 两级3 个 AI Agent 预设全部锁定在 High 级[2][3]。这种分布并非随意设定而是对应三种不同的威胁模型Low 级命令的潜在损害限于信息泄露Medium 级命令可能修改项目数据或暴露敏感个人信息High 级命令则可能导致本地代码上下文外流至第三方 AI 服务或授予 AI 代理文件系统写权限。风险等级的三级划分直接驱动下游的行为差异Medium 级命令触发内容脱敏RedactSecretsHigh 级命令额外触发执行前人工审批。2.2.2 双层启用机制预设系统采用Preset 导入 Connector 激活的双层启用机制opt-in security[1]。第一层appsettings.json中的Presets: []默认空数组确保升级后现有配置行为不变未显式声明的预设不会导入任何命令模板。第二层预设导入后在Connectors字典中生成的连接器初始状态为Enabled: false必须单独设置Enabled: true方可执行[1]。这意味着仅将gh加入Presets数组并不会实际开放任何命令面——还需在Connectors中显式启用对应连接器。单层遗漏不会暴露命令面的设计将配置误操作的风险降至最低。配置验证器ConfigValidator进一步在合并后对有效配置执行校验拒绝未知预设 ID 并检查数组中的空值与重复项[1]。2.2.3 三层合并引擎Apply()方法实现了三层配置合并[4]。第一层复制全局选项第二层遍历Presets[]中的每个 ID在静态字典中查找对应预设通过CloneConnector()深拷贝后填入Connectors字典第三层遍历用户配置的每个 Connector若存在对应预设则调用MergeConnector(preset, configured)否则直接CloneConnector(configured)[4]。合并规则遵循安全保守原则。其中三项规则构成不可被用户配置削弱的安全基线RiskLevel取Max(preset, configured)风险等级只升不降ReadOnly取preset AND configured只读限制不放宽RequiresApproval取preset OR configured审批要求不绕过[4]。举例而言若某 AI Agent 预设将RiskLevel设为High用户无法通过本地配置将其降为Low若预设要求审批用户配置无法将其关闭。ExternalCliPresetCatalog还定义了三组正则表达式约束参数输入RepoPattern^[A-Za-z0-9_.-]/[A-Za-z0-9_.-]$用于 GitHub 仓库格式NumberPattern^[0-9]$用于 PR 号、issue 号等数字参数SimpleNamePattern^[A-Za-z0-9_.:-]$用于 Kubernetes 命名空间、文档 ID 等标识符[3]。这些模式与合并引擎中的参数约束合并逻辑协同工作形成参数层面的输入验证防线。理解了 8 个预设的覆盖范围和安全设计策略后下一章将深入预设系统的核心架构——从ExternalCliPresetCatalog的静态字典结构到三层合并引擎的完整实现路径。3. 预设系统的核心架构OpenClaw.NET 的预设系统是一套以编译时固化和安全保守合并为核心约束的微型配置引擎。理解其三层架构——静态字典的存储层、合并引擎的计算层、安全属性的代数层——是正确编写自定义预设的前提。3.1 ExternalCliPresetCatalog 静态字典3.1.1 编译时固化设计所有预设存储于ExternalCliPresetCatalog的static readonly字典中编译期嵌入程序集[1]。运行时无外部文件系统或网络 I/O 依赖预设查找完全发生在内存中。ExternalCliPresetCatalog.cs以 682 行代码承载 8 个内置预设定义[2]将数据与查找逻辑封装在同一编译单元便于版本控制和代码审查。字典的键为预设 ID如gh、kubectl值为包含命令模板、风险等级、参数约束的ExternalCliConnector对象。ID 匹配使用StringComparer.OrdinalIgnoreCaseGH、Gh、gh均指向同一预设[1]。3.1.2 四个核心 API预设目录暴露四个操作[1]List()返回全部预设摘要ID、连接器名称、命令数量、标签供openclaw external presetsCLI 命令消费TryGet(id)执行单条查找ID 不存在时返回null由调用方决定处理策略FindUnknownIds()验证Presets[]中是否存在未定义 ID为ConfigValidator提供前置校验能力拒绝未知预设并在启动阶段报错Apply(options)执行三层合并引擎输出完整有效配置。四个 API 遵循只读语义——ExternalCliPresetCatalog不修改输入Apply()内部通过深拷贝确保调用方的原始配置对象不受影响。3.1.3 大小写不敏感 ID 匹配StringComparer.OrdinalIgnoreCase基于 Unicode 序号的逐字节比较忽略大小写但不进行文化敏感转换性能和文化一致性均优于CurrentCultureIgnoreCase[1]。对于 ASCII 预设 ID该比较器等价于ToLowerInvariant()后比较但避免了字符串分配。// ExternalCliPresetCatalog 核心结构 public static class ExternalCliPresetCatalog { // 编译期固化零运行时 I/O private static readonly Dictionarystring, ExternalCliConnector _presets new(StringComparer.OrdinalIgnoreCase) { [gh] new ExternalCliConnector { /* 6 条命令模板 */ }, [az] new ExternalCliConnector { /* 3 条命令模板 */ }, [kubectl] new ExternalCliConnector { /* 5 条命令模板 */ }, // ... 其余 5 个预设 }; public static IReadOnlyListExternalCliPresetSummary List() _presets.Select(p p.Value.ToSummary()).ToList(); public static ExternalCliConnector? TryGet(string id) _presets.TryGetValue(id, out var conn) ? conn : null; public static IEnumerablestring FindUnknownIds(IEnumerablestring ids) ids.Where(id !_presets.ContainsKey(id)); public static ExternalCliOptions Apply(ExternalCliOptions userOptions) { var result new ExternalCliOptions(); // 第一层全局选项复制 // 第二层预设导入CloneConnector 深拷贝 // 第三层用户覆盖MergeConnector 安全合并 return result; } }3.2 三层合并引擎详解Apply()是预设系统的计算核心三层依次叠加产生最终配置[3]。第一层将ExternalCliOptions中的全局字段复制到结果对象包括Enabled、Timeout、RedactSecrets及Presets[]数组[3]。这一层仅执行字段级浅拷贝不涉及合并逻辑。Presets[]的保留使下游组件能够追踪哪些预设被激活便于审计和调试输出。第二层遍历Presets[]中的每个 ID在静态字典查找对应预设通过CloneConnector()深拷贝后填入Connectors字典[3]。深拷贝是关键——它确保每个激活的连接器拥有独立实例后续用户覆盖不会反向修改静态字典中的原始预设定义。这一层体现双层启用机制的第一半预设导入填充命令模板与安全属性但Enabled继承默认值false[4]命令面仍未开放用户必须在第三层或后续配置中显式启用。第三层遍历用户配置的每个 Connector。若名称与第二层导入的预设匹配调用MergeConnector(preset, configured)执行安全保守合并否则CloneConnector(configured)深拷贝[3]。MergeConnector遵循安全保守原则——任何合并不降低预设的风险等级。规则如下字段合并规则安全语义Enabledpreset || configuredOR用户可显式启用RiskLevelMax(preset, configured)风险只升不降ReadOnlypreset configuredAND只读限制不放宽RequiresApprovalpreset || configuredOR审批不绕过ArgsTemplate用户非空则覆盖允许自定义命令Parameters逐字段合并可增改参数约束Tags集合并集保留双方标签RiskLevel、ReadOnly和RequiresApproval构成不可被用户配置削弱的安全基线[3]。例如github-copilot预设将RiskLevel设为High用户无法降为Low——Max(High, Low) High[5]。ArgsTemplate的非空覆盖策略则提供了灵活性留空继承预设非空则完全替换。3.3 安全属性的代数结构三层合并引擎的安全保守性并非偶然它建立在三个安全属性的代数结构之上。理解这些结构有助于预判自定义预设中安全字段的交互行为[6]。RiskLevel的取值空间Low Medium High构成全序集Max运算在该集合上形成半格semilattice——满足交换律Max(a, b) Max(b, a)、结合律Max(Max(a, b), c) Max(a, Max(b, c))和幂等律Max(a, a) a。最关键的单调性Max(a, b) a保证合并结果永不低于预设基线。ReadOnly的AND运算形成合取闭包conjunctive closure[6]。true AND false false意味着只要任一方允许写入结果即为可写。az和kubectl预设将全部命令的ReadOnly设为true[5]用户配置无法将其放宽为false AND true false。但false AND true false同时表明预设未要求只读时用户无法单方面施加只读限制。RequiresApproval的OR运算形成析取闭包disjunctive closure[6]。true OR false true确保审批要求单向强制——预设要求审批则用户无法关闭。三个 AI Agent 预设github-copilot、codex、gemini将RequiresApproval固定为truetrue OR x true确保审批门不可绕过[5]。// 安全属性的代数结构 public enum RiskLevel { Low 0, Medium 1, High 2 } public static class SecurityAttributeAlgebra { // 半格Max 运算单调不减 public static RiskLevel MergeRiskLevel(RiskLevel p, RiskLevel c) (RiskLevel)Math.Max((int)p, (int)c); // Max(High, Low) High — 用户无法降低风险等级 // 合取闭包AND 运算限制不放宽 public static bool MergeReadOnly(bool p, bool c) p c; // false AND true false — 预设未要求只读则用户无法单方面施加 // 析取闭包OR 运算审批不绕过 public static bool MergeRequiresApproval(bool p, bool c) p || c; // true OR false true — 预设要求审批则不可关闭 }三个结构的共同特征是单调性——安全维度上永不减弱[6]。Max保证风险单调不减AND保证只读限制单调不放宽OR保证审批要求单调不解除。双层启用机制将这一代数延伸至交互设计第一层预设导入后Enabled: false仍阻断命令面暴露。自定义预设开发者只需关注预设层的安全基线设定——合并引擎自动保证基线不被用户配置削弱。4. 实战为 Terraform CLI 编写自定义预设前三章从内置预设的全貌遍历到合并引擎的安全属性代数建立了一套可复用的分析框架。本章将这套框架应用于一个具体目标为 HashiCorp Terraform CLI 编写完整的自定义预设。Terraform 作为基础设施即代码IaCInfrastructure as Code领域的事实标准工具其命令面具有清晰的只读/变更型分界——plan和state list属于查询类操作apply则直接修改云资源状态——这使其成为演示预设开发全流程的理想样本。下文将依次完成命令面分析、预设代码编写、安全加固和测试验证四个阶段。4.1 准备工作4.1.1 分析目标 CLI 的命令面Terraform CLI 的命令树以子命令subcommand方式组织。在预设系统的语境中每条子命令对应一个独立的命令模板需要分别定义ArgsTemplate、Parameters和安全属性。以下是预设覆盖范围内的四条命令及其行为特征terraform plan生成执行计划execution plan对比当前配置与远程状态文件的差异输出拟创建、修改或销毁的资源列表。该命令不修改任何基础设施状态属于纯只读分析操作。-inputfalse标志可抑制交互式提示-json标志以结构化 JSON 输出计划摘要而非人类可读文本[1]。terraform apply是命令面中唯一具备破坏性副作用的命令。它根据 Terraform 配置创建或修改基础设施资源可能涉及资源创建、更新、删除和替换操作。-auto-approve标志跳过执行前的交互式确认提示-inputfalse抑制变量输入交互[1]。在预设系统中该命令必须标记为变更型ReadOnly: false并赋予最高风险等级。terraform state list列举 Terraform 状态文件中跟踪的所有资源地址用于审计和排查资源依赖关系。该命令仅读取本地或远程状态文件不触发任何 API 调用是最安全的查询操作之一。terraform output读取状态文件中的输出变量值output values常用于在 CI/CD 流水线中获取资源 ID 或端点地址。-json标志以 JSON 格式输出所有输出变量便于下游解析。四条命令中plan、state list和output均属于只读查询类操作apply是唯一可能改变基础设施状态的变更型命令。这一分布与第 2 章中az和kubectl预设的全只读设计形成对照——Terraform 预设保留了apply命令原因在于 IaC 工具的核心价值正是将配置变更自动化完全排除apply将使预设失去实用意义。4.1.2 确定风险等级风险等级的赋值遵循第 2 章归纳的最小必要风险原则。plan、state list和output三条命令均不修改基础设施状态潜在损害限于信息泄露风险等级设为Low。apply命令可导致云资源的创建、修改和销毁直接影响生产环境可用性风险等级设为High[2]。RequiresApproval的设置与风险等级直接关联。apply命令的High风险等级意味着每次执