首页
/ Windows Terminal Snippets:用 .wt.json 和 wt x-save 实现可分享、可发现的命令片段

Windows Terminal Snippets:用 .wt.json 和 wt x-save 实现可分享、可发现的命令片段

2026-09-04 23:53:56作者:温艾琴Wonderful

Windows Terminal 的 Snippets(曾用名 Tasks)特性,让长命令不必再靠记忆或散落在 .bashrcOneNote、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.cppActionArgs.idl 等源码印证各设计在仓库中的落地情况,帮助你从零搭建一套可复用的团队命令库。

![使用 wt 保存命令后通过 Suggestions UI 调用片段的原型演示](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/img/save-command.gif)

动机:为什么不用 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 的载体。在此之上规划了三处增强:

  1. input 支持字符串数组:当 input 是字符串列表时,Terminal 依次发送每个字符串,中间以 Enter 分隔,从而支持多行脚本;
  2. waitForSuccess 参数(默认 false):设为 true 且启用 shell integration 时,Terminal 会等待上一条命令退出后再发送下一条;
  3. 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 信号,脚本就卡死在第一行之后。

![从当前工作目录加载 .wt.json 任务并调用的原型演示](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/img/tasks-from-cwd.gif)

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 相同的 namedescriptionicon 属性,但没有任意 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.jsonc:\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 in path/to/file"。

键位绑定与安全隔离

文档中两条关于键位绑定的设计值得特别注意:

  1. 本地 snippet 不可被键位绑定。即便 .wt.json 中的动作带 ID,构建键位映射(keymap)时该文件尚未加载;且无人希望键位映射随 CWD 变化。本地动作存放在独立的 CWD→actions 映射中,主 keymap 也无法轻易触达它们。
  2. 本地 snippet 不进入 Command PalettesendInput 动作在 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

规格文档描述的工作流是:"刚跑通的命令应该能一键存下来"——按 UpHome,输入 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-savex- 前缀即"实验性"之意);将来若增加保存 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;
}

可以看出实现比规格文档更进一步:除了 NameCommandline,还带了 KeyChord(与 x-save--keychord,-k 选项对应),用于保存时直接指定键位。处理入口在 AppActionHandlers.cppTerminalPage::_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.OpenSnippetsPanesplitPane"type": "snippets"),并且 TerminalApp 工程包含 SnippetsPaneContent.hSnippetsPaneContent.cppSnippetsPaneContent.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 作为"这类菜单在业界已有的样子"的参照:

![VS Code tasks 演示](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/img/vscode-tasks-000.gif)

![Warp workflows 演示](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#1595 - Suggestions UI/img/warp-workflows-000.gif)

文档还附了两段原型演示: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.cppActionArgs.idlSaveSnippetArgsAppActionHandlers.cpp_HandleSaveSnippet)与 Snippets pane 源文件的存在,印证了这份规格并非停留在纸面,而是与仓库实现持续对齐的活文档。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384