// 安全研究 · 2026-06-07

CheatMD:让 Markdown 笔记变成可执行的交互式命令

一、项目概述

CheatMD 是一个用 Go 编写的开源命令行工具,它将普通的 Markdown 文件转化为可交互执行的“速查表”(Cheatsheet)。它的核心哲学极其简洁:你写下的 Markdown 文档,既是给人阅读的笔记,也是给机器执行的命令。

GitHub 仓库:https://github.com/cheatmd-dev/cheatmd
当前版本:v1.0.0-rc.3
许可证:MIT
要求:Go 1.26+

与传统的 navi、tldr、cheat.sh 等工具不同,CheatMD 不引入任何专有格式——你使用的只是最标准的 .md 文件,配合一个 HTML 注释块 <!-- cheat --> 来声明变量和元数据。这意味着你的速查表可以用任何 Markdown 编辑器打开、阅读、分享,同时又能通过 CheatMD 以交互方式运行。


二、核心设计理念

CheatMD 的设计围绕三个核心原则展开:

  1. 纯 Markdown 优先:不创造新语法,利用 Markdown 的标题和代码块作为命令结构,利用 HTML 注释隐藏机器元数据。
  2. 交互式变量注入:命令中的变量(如 $container)可以通过 Shell 输出动态生成候选列表,用户通过模糊搜索选择,而非死记硬背参数。
  3. 渐进式复杂度:从最简单的“标题+代码块”开始,按需引入变量、模块、条件分支、链式工作流等高级特性。
flowchart TB
    subgraph A["Markdown 源文件"]
        A1["标题 + 代码块"]
        A2["<!-- cheat --> 元数据"]
        A3["目录结构 → 自动标签"]
    end
    
    subgraph B["解析引擎"]
        B1["递归扫描 .md"]
        B2["提取标题/命令/变量"]
        B3["构建索引与标签"]
        B4["语法检查 (Lint)"]
    end
    
    subgraph C["交互层"]
        C1["模糊搜索 (Fuzzy)"]
        C2["变量选择器 (Picker)"]
        C3["历史记录 (Frecency)"]
        C4["Headless JSON-RPC"]
    end
    
    subgraph D["输出与执行"]
        D1["print → stdout"]
        D2["copy → 剪贴板"]
        D3["exec → 直接运行"]
    end
    
    subgraph E["生态集成"]
        E1["VS Code / Neovim"]
        E2["Obsidian"]
        E3["Bash/Zsh Widget"]
        E4["tmux / Zellij"]
    end
    
    A --> B --> C --> D
    C --> E
    E --> C

三、安装与快速开始

3.1 安装

CheatMD 作为单二进制 Go 程序发布,安装极为简单:

go install github.com/cheatmd-dev/cheatmd/cmd/cheatmd@v1.0.0-rc.3

3.2 初始化

第一次运行建议执行初始化,它会创建配置文件并引导安装 starter packs:

cheatmd init

初始化流程会:

  1. 在 ~/.config/cheatmd/cheatmd.yaml 创建带注释的配置模板
  2. 从远程 registry 拉取可选的 starter cheat packs 清单
  3. 通过交互式选择器让你勾选需要的包(如 git、docker、kubernetes)

3.3 基本使用

cheatmd                    # 浏览当前目录下的 cheats
cheatmd ~/cheats           # 浏览指定 cheats 目录
cheatmd -q "docker"        # 以搜索查询启动
cheatmd --lint ~/cheats    # 检查语法和引用问题
cheatmd packs install      # 安装/更新 cheat packs

四、核心功能详解

4.1 最小可用的 Cheat

一个 CheatMD 可识别的最小单元由两部分组成:Markdown 标题 + 带 title 属性的代码块。

## Docker: list containers

````sh title:"Show all running containers"
docker ps
````

这就是全部。不需要 <!-- cheat --> 块,不需要变量声明。CheatMD 会把 ## 后面的文字作为可搜索标题,把代码块内容作为命令模板。title:"..." 属性会在选择器中显示为描述文本。

4.2 变量系统:三种声明形式

当命令模板中出现 $name 或 <name> 引用时,CheatMD 会在执行前提示用户输入值。变量在 <!-- cheat --> 注释块中声明,有三种形式:

形式一:仅提示(Prompt-only)

<!-- cheat
var hostname --- --header "Enter target hostname"
-->

用户会看到一个空白输入框,上方显示 "Enter target hostname"。

形式二:从 Shell 输出获取(=)

<!-- cheat
var container = docker ps --format "{{.Names}}" --- --header "Select container"
-->

Shell 命令的输出决定交互行为:

  • 0 行输出:回退到手动输入
  • 1 行输出:预填充到输入框,用户可确认或编辑
  • 2+ 行输出:生成分页的可过滤选择列表

这是最强大的特性——你的命令参数可以动态来自当前系统状态。例如 Kubernetes 的 context 和 namespace:

<!-- cheat
var context   = kubectl config get-contexts -o name --- --header "Context"
var namespace = kubectl --context $context get ns -o name \
    --- --map "cut -d/ -f2" --header "Namespace"
-->

注意 $context 先被解析,其值会注入到 $namespace 的 Shell 命令中。

形式三:字面量(:=)

<!-- cheat
var user := admin
var url  := https://$host/api/v1
-->

不执行 Shell,直接赋值。:= 右侧的 $other_var 会被同一块中已解析的变量替换。常用于条件分支内的固定值设置。

4.3 选择器选项:控制交互体验

变量声明后可以通过 --- 追加选择器选项,精细控制 Picker 的行为:

选项 作用
--header "..." 选择器顶部显示的标题
--delimiter "X" 按分隔符切分列
--column N 用户看到的第 N 列(仅显示)
--select-column N 选中后返回的第 N 列
--map "cmd" 通过管道 cmd 转换选中值
--multi 允许多选,用空格切换复选框

经典场景:用户看到描述,命令拿到键值

<!-- cheat
var auth_method = printf 'key\tUse SSH key (default)\npassword\tUse password\n' \
    --- --delimiter '\t' --column 2 --select-column 1 --header "Auth method"
-->

用户看到 “Use SSH key (default)”,但变量实际获得值 key。

--map 做复杂转换

<!-- cheat
var bucket = aws s3 ls --- --map "awk '{print \$3}'" --header "Bucket"
-->

aws s3 ls 输出带时间戳的行,--map 提取出纯 bucket 名。

4.4 条件分支:动态调整命令

条件块允许根据前面变量的值,决定后续变量如何定义:

<!-- cheat
var env = printf 'dev\nstaging\nprod\n' --- --header "Environment"

if $env == dev
  var url := https://api.dev.example.com
fi

if $env == staging
  var url := https://api.staging.example.com
fi

if $env == prod
  var url := https://api.example.com
fi
-->

条件只支持 == 和 !=,不支持嵌套。如果变量仅在条件块中定义且无一匹配,该变量会被静默置为空字符串,避免无意义的提示。

4.5 模块系统:复用变量定义

模块通过 export / import 实现跨 Cheat 复用:

在 modules.md 中定义:

<!-- cheat
export docker_container
var container = docker ps --format "{{.Names}}" --- --header "Container"
-->

在任意 Cheat 中消费:

## Docker: tail logs

````sh title:"Follow container logs"
docker logs -f $container
````
<!-- cheat
import docker_container
-->

模块支持嵌套导入:模块 A 可以 import 模块 B,消费者只需 import A 即可获得全部依赖。模块名在整个 cheats 路径中必须唯一。

4.6 链式工作流(Chains)

链用于将多个 Cheat 组合成有序的多步骤流程,每个步骤是独立的 Cheat:

## Release: choose version

````sh title:"Show release version"
echo $version
````
<!-- cheat
chain release 1
var version --- --header "Version"
-->

## Release: build

````sh title:"Build release artifact"
make build VERSION=$version
````
<!-- cheat
chain release 2
var version --- --header "Version"
-->

## Release: publish

````sh title:"Publish release artifact"
make publish VERSION=$version
````
<!-- cheat
chain release 3
var version --- --header "Version"
-->

使用时搜索 /chain release,CheatMD 会执行下一个待执行的步骤然后退出。下次启动时自动恢复进度。完成最后一步后链自动重置。可以通过 cheatmd chain reset [name] 手动重置。

sequenceDiagram
    participant U as 用户
    participant C as CheatMD
    participant S as 链状态存储
    
    U->>C: cheatmd -q "/chain release"
    C->>S: 读取当前步骤 (step 1)
    C->>U: 提示输入 $version
    U->>C: version = 1.2.0
    C->>U: 执行 echo 1.2.0,退出
    Note over U,C: 用户继续其他操作...
    U->>C: cheatmd -q "/chain release"
    C->>S: 读取当前步骤 (step 2)
    C->>U: 提示输入 $version
    U->>C: version = 1.2.0
    C->>U: 执行 make build VERSION=1.2.0,退出
    U->>C: cheatmd -q "/chain release"
    C->>S: 读取当前步骤 (step 3)
    C->>U: 执行 make publish...
    C->>S: 重置链到 step 1

五、配置与个性化

配置文件位于 ~/.config/cheatmd/cheatmd.yaml,所有设置均为可选:

# 默认 cheats 路径
path: ~/cheats

# 最终命令的处理方式: print / copy / exec
output: print

# 运行变量命令和执行时使用的 shell
shell: /bin/bash

# 变量引用语法: dollar ($name) / angle (<name>) / both
var_syntax: dollar

# 是否将未声明的变量引用也作为提示
allow_undeclared_vars: false

# 快捷键绑定
key_substitute: "ctrl+t"   # 环境变量+历史替换搜索
key_preview: "ctrl+y"      # Markdown 预览
key_history: "ctrl+h"      # 执行历史
key_open: "ctrl+o"         # 打开源文件
key_widget: "\C-g"        # Shell widget 触发键

Substitute 搜索(Ctrl+T) 是一个非常实用的功能:在变量提示界面按 Ctrl+T,会弹出一个模糊搜索框,覆盖当前所有环境变量以及 Shell 历史中的变量赋值(如 VAR=value、export VAR=value)。选中即可填充到当前提示。

执行历史 保存在 ~/.local/share/cheatmd/history.jsonl,记录每次运行的最终命令、变量值和 Cheat 来源。主选择器使用 Frecency 算法排序——你最近频繁使用的 Cheat 会浮到顶部。


六、生态系统集成

CheatMD 的强大之处不仅在于独立的 TUI,还在于它与整个开发工具链的深度集成。

6.1 Shell 集成

Bash / Zsh Widget

将 CheatMD 嵌入 readline,选中命令后直接插入当前命令行:

# ~/.bashrc
eval "$(cheatmd widget bash)"

# ~/.zshrc
eval "$(cheatmd widget zsh)"

默认按 Ctrl+G 触发,选中命令后落在你当前的 Shell 提示符上,按 Enter 即可执行。

tmux

# ~/.tmux.conf
bind-key -n C-n split-window "$SHELL --login -i -c 'cheatmd --print | tr -d \"\\r\\n\" | tmux load-buffer -b tmp - ; tmux paste-buffer -t {last} -b tmp -d'"

按 Ctrl+N 在分屏中打开 CheatMD,结果粘贴回原 pane。

Zellij

bind "Ctrl n" {
    Run "sh" "-c" "content=$(cheatmd --print); zellij action toggle-floating-panes; zellij action write-chars \"$content\"" {
        floating true
        close_on_exit true
    };
}

在浮动 pane 中打开,结果直接输入到原 pane。

6.2 编辑器扩展

编辑器 功能
VS Code 语法高亮、诊断、自动补全、CodeLens 执行按钮
Neovim 语法高亮、异步诊断、补全、:CheatMDRun 命令
Obsidian 内联运行按钮、lint 状态、执行结果展示、变量自动补全

6.3 Headless 模式(JSON-RPC)

这是编辑器插件的底层协议。启动时加上 --headless 和精确查询:

cheatmd --headless -q "docker exec container"

CheatMD 通过 stdout/stdin 以 JSON-RPC 2.0 通信:

  • 需要用户输入时,发送 prompt 请求(含变量名、选项列表等)
  • 宿主应用(如编辑器)展示 UI 后,通过 result 响应返回值
  • 执行完成后发送 completed 通知(含最终命令、输出、退出码)

这使得 CheatMD 的核心引擎可以被任何支持 JSON-RPC 的客户端调用,无需绑定特定的 TUI 实现。


七、数据迁移:从其他工具导入

CheatMD 内置 convert 子命令,支持从主流速查表工具无痛迁移:

# 从 navi 导入
cheatmd convert navi ~/navi-cheats -o ~/cheats

# 从 tldr-pages 导入
cheatmd convert tldr ~/tldr/pages/common/tar.md -o ~/cheats/tar.md

# 从 cheat/cheatsheets 导入
cheatmd convert cheat ~/cheat/cheatsheets -o ~/cheats

转换器会尽量保留原有结构,将 navi 的 <name> 变量转为 CheatMD 的 $name,将 tldr 的 {{placeholder}} 转为带默认值的变量提示,将 cheat 的注释转为 title 描述。


八、与同类工具的对比

graph LR
    subgraph 传统工具
        T1["navi"]
        T2["tldr"]
        T3["cheat.sh"]
        T4["pet"]
    end
    
    subgraph CheatMD优势
        C1["纯 Markdown<br/>无专有格式"]
        C2["Shell 动态变量<br/>实时系统状态"]
        C3["模块+链式<br/>可复用可编排"]
        C4["Headless JSON-RPC<br/>编辑器原生集成"]
        C5["模糊搜索+历史<br/>Frecency 排序"]
    end
    
    T1 --> C1
    T2 --> C2
    T3 --> C3
    T4 --> C4
特性 CheatMD navi tldr cheat.sh
文件格式 纯 Markdown .cheat 专有格式 特定 Markdown 纯文本
变量交互 Shell 输出驱动 Picker 支持变量 静态占位符 静态占位符
模块复用 import/export @extends 无 无
链式工作流 原生支持 无 无 无
条件分支 if/fi 有限 无 无
Shell 集成 Widget + tmux + Zellij Widget 无 无
编辑器支持 VS Code + Neovim + Obsidian 有限 插件 无
Headless API JSON-RPC 无 无 无
数据迁移 内置 convert 无 无 无

九、总结

CheatMD 代表了一种“文档即代码”的极端优雅实现:它不要求你学习新格式,不强迫你维护两套资料(给人看的和给机器用的),而是让同一套 Markdown 笔记同时服务于两种场景。

对于运维工程师、DevOps、SRE、以及任何需要频繁操作命令行的开发者来说,CheatMD 的价值在于降低认知负荷——你不需要记住 docker exec 后面跟哪个容器 ID,不需要记住 kubectl 的当前 context,不需要记住 SSH 的特定端口。这些动态信息由 Shell 实时提供,你只需做选择。

随着 VS Code、Neovim、Obsidian 插件的成熟,以及 Headless JSON-RPC 模式的开放,CheatMD 正在从一个命令行工具进化为一个可嵌入任何工作流的命令编排引擎。如果你已经在用 Markdown 记录命令笔记,给它加上 <!-- cheat --> 注释,就是通往交互式执行世界的全部代价。

原文 https://blog.csdn.net/2301_79518550/article/details/161779044