Windows Terminal Snippets:用 .wt.json 和 wt x-save 实现可分享、可发现的命令片段
Windows Terminal 的 Snippets(曾用名 Tasks)特性,让长命令不必再靠记忆或散落在 .bashrc、OneNote、shell 脚本里:它们可以被统一存储、按意图检索、随项目分享给整个团队。本文基于仓库中的规格文档 [doc/specs/#1595 - Suggestions UI/Snippets.md](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/Snippets.md) 展开,完整覆盖 sendInput 多行输入扩展、.wt.json 项目级片段文件、wt x-save 命令行保存语法与安全设计,并结合 AppCommandlineArgs.cpp、ActionArgs.idl 等源码印证各设计在仓库中的落地情况,帮助你从零搭建一套可复用的团队命令库。
动机:为什么不用 alias 或脚本文件
规格文档在 Background 一节给出了完整论证。命令行强大的前提是使用者记住具体命令、flag 与参数;对长命令或不常用的命令,回忆精确语法的心理开销很大,而团队内部又需要共享这些任务。
文档提出用"想做什么"(what do I want to do)取代"怎么做"(how do I do it)的检索方式。相比传统方案:
- Alias/脚本的老问题:即使用户创建了 alias 或
.ps1/.bat/.sh脚本,仍要记住"我创建过它"、"它存在哪"、"它怎么用"。 - Snippets 的差异:提供一个专属 UI 让命令始终触手可及,不需要回忆 alias 名,只需检索"想做的事"。命令不再散落在
.bashrc、.bash_profile、.profile各处,而是统一存放在 Terminal 配置或相关项目的旁边——任何人接手代码库都能立即获得可用任务集。 - 跨 shell 且面向新手:alias 倾向服务于资深 shell 用户;Snippets 通过 fragment 扩展机制,让工具可以随应用捆绑常见工作流,Terminal 可自动加载,即使是新手也能直接使用。
灵感来源上,规格文档追溯到了作者早年写的 #keep(用数字召回暂存的长命令与目录)、iTerm2 的 Command History 菜单、VsCode Tasks(工作区根目录共享任务文件、运行时可挑选参数),以及 Warp 的 "workflows" 与 Fig 的自动补全生态——整个生态对"更可发现的命令行工具"存在普遍需求。
核心实现:基于 sendInput 动作
规格文档 Implementation Details 的开宗明义是:"这部分绝大部分已经以 sendInput 动作实现"——sendInput 本来就会向终端发送文本,天然适合作为 snippet 的载体。在此之上规划了三处增强:
input支持字符串数组:当input是字符串列表时,Terminal 依次发送每个字符串,中间以 Enter 分隔,从而支持多行脚本;waitForSuccess参数(默认false):设为true且启用 shell integration 时,Terminal 会等待上一条命令退出后再发送下一条;description属性:为 Command 增加描述字段,供菜单展示,扩展作者也可提供更详细信息。
此外还有一个命名调整:SuggestionsSource 枚举中的 "source": "tasks" 更名为 snippets(出现旧值时优雅迁移),"tasks" 是该特性的早期名称,改用 "snippets" 可与 VsCode 侧的命名对齐。
多行 snippet 示例
文档用一个真实脚本演示问题:一段 PowerShell 同步 issue/bug 到项目看板的脚本。若作为单个 input 字符串写入 sendInput,由于 JSON 不支持多行字符串,每一行都要用 \r\n 拼成一行:
{
"command":
{
"action": "sendInput",
"input": "$s=Invoke-GitHubGraphQlApi \"query{organization(login:`\"Microsoft`\"){projectV2(number: 159) { id } } }\"\r\n$tasks = get-GitHubIssue -Labels \"Issue-Task\" -state open\r\n$bugs = get-GitHubIssue -Labels \"Issue-Bug\" -state open\r\n$issues = $tasks + $bugs\r\n$issues | ? {$_.labels.Name -NotContains \"Needs-Triage\" } | ? { $_.milestone.title -Ne \"Icebox ❄\" } | ? type -Ne \"PullRequest\" | select -expand node_id | % {\r\n $resp = Add-GitHubBetaProjectItem -ProjectNodeId $s.organization.projectV2.id -ContentNodeId $_ ;\r\n}"
},
"name": "Upload to project board",
"description": "Sync all our issues and bugs that have been triaged and are actually on the backlog to the big-ol project"
}
这份 JSON "基本完全不可用"。改用字符串数组后,每个字符串按序发送、中间插入 Enter:
{
"command":
{
"action": "sendInput",
"input":
[
"$s=Invoke-GitHubGraphQlApi \"query{organization(login:`\"Microsoft`\"){projectV2(number: 159) { id } } }\"",
"$tasks = get-GitHubIssue -Labels \"Issue-Task\" -state open",
"$bugs = get-GitHubIssue -Labels \"Issue-Bug\" -state open",
"$issues = $tasks + $bugs",
"$issues | ? {$_.labels.Name -NotContains \"Needs-Triage\" } | ? { $_.milestone.title -Ne \"Icebox ❄\" } | ? type -Ne \"PullRequest\" | select -expand node_id | % {",
" $resp = Add-GitHubBetaProjectItem -ProjectNodeId $s.organization.projectV2.id -ContentNodeId $_ ;",
"}",
""
]
},
"name": "Upload to project board",
"description": "Sync all our issues and bugs that have been triaged and are actually on the backlog to the big-ol project"
}
这比单行版本"稍微更易维护"。再配合 shell integration 设置 "waitForSuccess": true,脚本任一步失败,剩余部分就不会继续发送。
严格前提(脚注 1):shell integration 是
waitForSuccess生效的硬性要求。没有它,Terminal 只会发出脚本第一行,然后永远等待FTCS_COMMAND_FINISHED信号,脚本就卡死在第一行之后。
Fragment 动作
规格文档指出该能力已由 fragment 扩展机制(上游 PR #16185)支持:第三方开发者可以开发应用,向 Terminal 注入额外 snippet,需为每个动作添加 id,用户随后可将动作 id 绑定到键位。文档以 docker/docker-compose 命令系列为例说明这类内容可以直接加入 Terminal。
项目级 Snippets 文件:.wt.json
为了让团队成员共享命令,规格文档定义了 .wt.json 机制:Terminal 会自动在 shell CWD 的所有上级目录中查找 .wt.json,并把其中的动作一并加载。文件语法是标准 settings schema 的简化版,示例如下:
{
"$version": "1.0.0",
"snippets":
[
{
"input": "bx",
"name": "Build project",
"description": "Build the project in the CWD"
},
{
"input": "bcz",
"name": "Clean & build solution",
"icon": "\uE8e6",
"description": "Start over. Go get your coffee. "
},
{
"input": "nuget push -ApiKey az -source TerminalDependencies %userprofile%\\Downloads",
"name": "Upload package to nuget feed",
"icon": "\uE898",
"description": "Go download a .nupkg, put it in ~/Downloads, and use this to push to our private feed."
}
]
}
Schema 要点:
- 顶层键是
snippets而非actions; - 每个 snippet 对象是
Command的简化体:拥有与Command相同的name、description、icon属性,但没有任意action,而是直接内嵌sendInput动作的参数(如input)作为属性; - 支持
$version字段以便未来破坏性变更;缺省时假定版本为1.0.0,即本规格最初提案的 schema。
CWD 解析、缓存与目录分层
文档给出了一组明确的运行时规则,值得逐条对照实现:
- 默认 CWD 来源:
TermControl初始化时 CWD 总会被设为所属 profile 的startingDirectory。因此即使用户没启用 shell integration,Terminal 也能从 profile 的startingDirectory加载.wt.json;若 shell integration 配置了 CWD 上报,则用户切换目录时列表会随之刷新。 - 缓存 map:在
Terminal.Settings.Model中缓存 path→actions 映射,避免同一 CWD 下的多个 pane 重复读盘重建。 - 懒加载:CWD 变化时不需要控件主动发事件,等 UI(Suggestions UI / Snippets pane)首次请求时才按需加载。
- 多级
.wt.json分层:若 CWD 的祖先链上存在多个.wt.json(例如c:\a\b\c\d\下同时存在c:\a\.wt.json与c:\a\b\c\.wt.json),每个都会分别加入 path→actions 映射;实际取用 CWD 的动作时,深层目录的同名 snippet 优先于浅层——例如浅层定义input: "foo", name: "Build"、深层定义input: "bar", name: "Build",用户位于c:\a\b\c下选中Build时得到bar;位于c:\a下则得到foo。 - 解析失败策略:解析
.wt.json失败时直接忽略该文件,但要向用户显示警告——打开 Suggestions UI 时以 Toast 提示首次加载失败;Snippets pane 顶部可显示静态文本"failed to parse snippets found inpath/to/file"。
键位绑定与安全隔离
文档中两条关于键位绑定的设计值得特别注意:
- 本地 snippet 不可被键位绑定。即便
.wt.json中的动作带 ID,构建键位映射(keymap)时该文件尚未加载;且无人希望键位映射随 CWD 变化。本地动作存放在独立的 CWD→actions 映射中,主 keymap 也无法轻易触达它们。 - 本地 snippet 不进入 Command Palette。
sendInput动作在 settings 文件与 fragment 中仍会进入 Command Palette,但"本地" snippet 不会——因为 Command Palette 与自有 ActionMap 设置模型(defaults、fragments、用户设置层层叠加)紧耦合,运行时不易变更;而 Suggestions UI 与 Snippets pane 更擅长处理与TermControl相关的上下文动作,且能同时按Name和Input过滤(Command Palette 目前只能按名字过滤)。
安全一节(Security considerations)进一步解释了禁止惰性绑定键位的深层原因:malicious.exe 完全可以在 %HomePath% 创建 .wt.json,写一个 snippet 内容为 \u003pwn-your-machine.exe\r;任何应用都能读你的 settings 文件,恶意应用也可以把自己的动作 id 设置为与某个你已绑键的善意本地 snippet 相同的 ID。对应的缓解措施是:首次从 .wt.json 加载 snippet 时询问用户是否信任该目录(类似 VsCode 的工作区信任机制);接受后把该目录加入 state.json 永久保存的信任列表,否则忽略该文件,对话框中还提供"信任父目录"勾选框;并且要与安全团队合作,确认解析不可信 JSON 所需的额外措施。
从命令行保存 Snippets:wt x-save
规格文档描述的工作流是:"刚跑通的命令应该能一键存下来"——按 Up、Home,输入 wt x-save ,回车,不必打开设置手工复制粘贴。底层由 saveSnippet 动作驱动,但不从用户 settings 文件中解析该动作(在设置文件里放一个"保存 snippet 到设置文件"的动作本身就不合逻辑)。
x-save 子命令语法
x-save [--name,-n name][--description,-d description][-- commandline]
把给定命令行作为 sendInput 动作保存到 Terminal 设置文件,立即写入 settings 文件。
参数:
| 参数 | 说明 |
|---|---|
--name,-n name |
为保存的命令指定 name。省略时留空,菜单中显示自动生成的 "Send input:..." 名称 |
--description,-d description |
可选,为命令指定描述 |
commandline |
要保存为 sendInput 动作 input 的命令行 |
窗口行为:单独运行 save 子命令时,Terminal 隐含 -w 0 参数,把动作发送到当前窗口(除非命令行手动指定了 -w),避免弹出一个新窗口只为告知"命令已保存";与其他子命令一起运行时,动作就在执行这些子命令的同一窗口中运行。
实验性状态:文档中明确团队讨论后决定先按实验性(experimental)接受,主要顾虑是字符串转义的奇怪边缘场景太多,故命名为
x-save(x-前缀即"实验性"之意);将来若增加保存 snippet 的对话框,再将其转正。
源码印证:x-save 在仓库中的实现
规格描述与当前仓库代码可以互相印证。AppCommandlineArgs.cpp 中的 _buildSaveSnippetParser() 确实注册了名为 x-save 的子命令(资源字符串 SaveSnippetDesc 为 "Save command line as input action"),选项包括 --name,-n、--keychord,-k 与位置参数 command,(即命令行正文),并通过 positionals_at_end(true) 保证 -- 之后的参数按序归入命令行;解析回调构造出 ShortcutAction::SaveSnippet 动作与 SaveSnippetArgs,其中含空格的参数项会被引号包裹后拼接,避免保存时语义走样。
"单独 x-save 不弹新窗口"的行为同样有对应实现:ValidateStartupCommands()(AppCommandlineArgs.cpp)检测到 _startupActions 仅含一条 SaveSnippet 且未指定窗口目标时,把 _windowTarget 设为 "0",把动作导向当前窗口,并跳过自动插入 NewTab 逻辑。
动作参数模型定义在 ActionArgs.idl:
[default_interface] runtimeclass SaveSnippetArgs : IActionArgs, IActionArgsDescriptorAccess
{
SaveSnippetArgs();
SaveSnippetArgs(String Name, String Commandline, String KeyChord);
String Name;
String Commandline;
String KeyChord;
}
可以看出实现比规格文档更进一步:除了 Name 与 Commandline,还带了 KeyChord(与 x-save 的 --keychord,-k 选项对应),用于保存时直接指定键位。处理入口在 AppActionHandlers.cpp 的 TerminalPage::_HandleSaveSnippet,并受 Feature_SaveSnippet 特性开关保护。
Snippets pane:独立的浏览面板
规格文档规划了一个新的 pane 类型 "type": "snippets"("非终端内容"能力随 1.21 Preview 落地后,添加新 pane 类型变得简单)。设计要点:
- pane 内部是一个
TreeView加一个过滤文本框(类 Command Palette 体验); - 每个 TreeView 条目是
FilteredCommand,带一个"播放"按钮,点击即快速执行该命令; - 可支持 Suggestions UI 的各类建议源(如把
recentCommands从当前活动控件接入),并提供复选框过滤不同建议源。
该 pane 在当前仓库中已有对应实现痕迹:defaults.json 中内置了动作 Terminal.OpenSnippetsPane(splitPane 且 "type": "snippets"),并且 TerminalApp 工程包含 SnippetsPaneContent.h、SnippetsPaneContent.cpp 与 SnippetsPaneContent.xaml 源文件,说明 pane 的 UI 骨架已在代码库中成形。
UI/UX 设计
展示层面主要复用 [Suggestions UI](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/Suggestions-UI.md)——一个相对文本光标的 UI 表面,能在用户工作上下文中快速呈现动作。文档以 VsCode Tasks 与 Warp workflows 作为"这类菜单在业界已有的样子"的参照:
文档还附了两段原型演示:wt save foo bar --baz 保存命令后经 Suggestions UI 调用的流程(见文首配图),以及从 CWD 读取任务的流程(见多行 snippet 小节配图)。
设计权衡(Tenets)
规格文档用一张表记录了各维度的权衡,核心内容:
| 维度 | 结论 |
|---|---|
| 兼容性 | 曾考虑为 .wt.json 支持 YAML,因为 JSON 对命令行不友好(tab \t、换行 \r、转义字符还好,引号转义在 JSON 中配合各 CLI 工具各自的引号解析规则会变得很快失控);但支持 YAML 需要定义一套 YAML 语法并引入、实现 OSS YAML 解析器,成本远高于 JSON,最终选 JSON |
| 可访问性 | 无特别项,Snippets pane 需与其他 UI 表面一样进行 a11y 测试 |
| 可持续性 | 无显著环境影响,不使用昂贵计算资源 |
| 本地化 | 担忧社区贡献的 snippet 描述无法本地化;未来或需支持 description: { en-us: "", pt-br: "", ... } 的语言映射,暂列为未来事项 |
| 安全 | 见上文"键位绑定与安全隔离"一节:禁止本地 snippet 惰性绑键、目录信任对话框、state.json 持久化信任列表、与安全团队复核不可信 JSON 解析 |
已知限制与其他潜在问题
- 重定向陷阱:
wt save ping 8.8.8.8 > foo.txt不会按用户期望工作——shell 会先解析命令行,把wt的输出重定向到foo.txt,而不是把整条命令当作参数保存。 - 远程连接:本地 snippet 在 ssh 等远程连接下不可用,Terminal 只能读取本地文件系统,至多能读取用户 ssh 出发前所在目录的 snippet。
实施计划与未来展望
文档按 Crawl/Walk/Run 划分路线图(其中 ✅ Done 的用户故事 A/B/H 即:从菜单快速执行任务、fragment 可向用户设置提供任务、按已输入文本过滤 snippet)。当前仓库中可确认的已落地部分:Suggestions UI(含片段源)已实现、fragment 可向用户设置注入动作、Snippets pane 骨架代码存在、x-save 命令行已实现并由特性开关控制。
文档 Conclusion 指出:与用户交流后,所有人都能立刻理解 snippet 的价值。Future Considerations 罗列了后续方向:
save子命令增加保存位置参数:--local(存入 CWD 的.wt.json,没有则创建)、--parent(存入最近的祖先目录)、--settings(手动存入 settings 文件)、--profile(可能借助WT_SESSION_ID环境变量定位 pane 所属 profile,但依赖尚未充分设计的 per-profile actions);团队讨论后认为--local/--parent/--settings反响良好,"也许现在就做";- 长工作流更适合以 notebook 形态暴露(与 markdown notebook 体验合流),因为长脚本需要在命令间穿插富文本标注;
- 兼容 Warp 的 workflows YAML 语法:仓库附带 [dump-workflows.py](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/dump-workflows.py)(脚注 2 所指脚本),可将 Warp workflow YAML 转换为 Terminal 可加载的 JSON,"非常直接";这些命令以 Apache 2.0 许可发布,可被其他开源项目消费;
- 可发现性:Actions 页可加"只显示 snippets"的开关与
wt save提示文本;启用 shell integration 后,可把"Save command as snippet"放入 prompt 旁的 quick fix 菜单; - 终端内直接保存 snippet 的对话框:输入命令行、名称、描述;Snippets pane 上加 "Add new" 按钮;
wt save可改为打开预填好的对话框,甚至用 shell integration 的最近命令预填; - schema v2.0 可参考
.vscode/tasks.json,支持更复杂的任务定义(运行时向用户提示不同参数取值),与可提示输入段(prompt-able sections)方向合流; - 未来可加
shell属性声明 snippet 适用的 shell,从而按当前 shell 过滤(暂缓原因:目前没有可靠方式知道用户当前运行的 shell 应用); - 未来可考虑把
sendInput动作提升为 settings.json 顶层snippets数组,便于在用户设置与.wt.json之间互相迁移; - 社区 Snippets(最大的 stretch 目标):在公共仓库(仿 winget-pkgs 模式)托管社区维护的 snippet 列表,Terminal 自动拉取最新社区命令,可直接作为另一个 suggestion source;
.wt.json中的 Profiles:既然目录里已有.wt.json,是否也该动态增删 profile(例如 Terminal 仓库自身的 PowerShell 构建环境与 CMD 构建环境各放一个 profile)?文档坦承语义尚未想清楚,可能与 Dev Home 方向结合,或作为 winget DSC 创建 fragment profile。
小结
Snippets 特性的完整拼图是:以 sendInput 动作为执行底座(支持字符串数组多行发送与 waitForSuccess 条件续发)、以 .wt.json 为团队共享载体(含目录分层覆盖、缓存、信任机制与安全隔离)、以 wt x-save 为"刚跑通就保存"的快捷入口(实验性前缀 x- 如实反映转义边缘场景的未决风险)、以 Suggestions UI 与 Snippets pane 为两种互补的呈现面(前者上下文感知且可按 Name+Input 双字段过滤,后者是独立的 TreeView 浏览面板)。文档中每条设计都给出了取舍理由(YAML vs JSON、为什么不进 Command Palette、为什么禁止本地动作绑键),源码中 x-save 的完整解析链(AppCommandlineArgs.cpp → ActionArgs.idl 的 SaveSnippetArgs → AppActionHandlers.cpp 的 _HandleSaveSnippet)与 Snippets pane 源文件的存在,印证了这份规格并非停留在纸面,而是与仓库实现持续对齐的活文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00