DeepSeek / KaTeX 复制粘贴净化器
把「网页上复制的公式」在进入剪贴板的那一刻就修好,而不是指望下游编辑器抢救。
适用平台:Windows(依赖 Win32 剪贴板 API)。零第三方依赖,Python 3.8+ 即可。
当前版本 v1.4.1。改完脚本后,务必用 --restart(= 启动监听.bat)重启监听 ——
旧进程会占着单例锁,不重启等于新代码没上,同一个 bug 会反复复现(见陷阱 r)。
| 版本 | 关键修复 |
|---|---|
| 1.0 | 从 annotation 提取 TeX 源码、重建定界符、抹掉 HTML |
| 1.1 | 中文控制台 emoji 崩溃 / stderr 被 DEVNULL 吞 / 互斥体假活(引入心跳) |
| 1.2 | 网页复制异步覆盖剪贴板(守护窗口)/ 启动补处理 / 写回读回校验 |
| 1.3 | 判据漏「HTML 已丢的重复裸文本」导致静默跳过 + 自愈 + 版本握手 + 僵尸清理 |
| 1.4 | 框选复制时 annotation 根本不在剪贴板里(mathml 被 clip 成 1px)→ 视觉层反推:分数不再塌成 "79",上下标 / 根号 / cases / 矩阵 / \binom / \vec 都能还原 |
| 1.4.1 | SkillHub 拒收 .bat(报「不允许的文件类型」)→ 发布包里不再放启动器,改为 --make-shortcuts 在本地生成(内容与原版逐字节一致) |
1. 先判断是不是这个病
用户贴了截图说「复制过去格式有问题」时,对照这三条:
| 现象 | 判定 |
|---|---|
| 公式出现两遍,且没有空格(15x+8y=4000015x+8y=40000) | ✅ 就是这个病 |
| 公式是裸文本,Obsidian 里不渲染 | ✅ 同上 |
| $y$、$$ 这些定界符在原文里看不见 | ✅ 同上(说明只拿到视觉层) |
| 界面按钮文字(复制、ds-icon)混进了正文 | ✅ 附带症状,本 skill 一并清理 |
如果用户是先粘到纯文本编辑器再转,或者用的是 Word/PPT,这个工具仍然可用 (它只输出干净的 Markdown),但收益不如 Obsidian 场景明显。
2. 病根(三层叠加,缺一层都不会这么难看)
-
公式在 DOM 里有两份。
<span class="katex-mathml"> 无障碍 MathML,内含 <annotation encoding="application/x-tex">TeX 源码</annotation> <span class="katex-html"> 真正显示的字形,被拆成一堆细碎 span浏览器「选择 + 复制」把两份都写进
text/html。Obsidian 的 HTML→Markdown 转换器不认识 KaTeX,于是两份都变成了文本 → 公式重复两遍。 -
定界符从来没进过剪贴板。
$...$/$$...$$只存在于annotation的属性值里,不在任何文本节点中。 所以粘过去必然是裸文本,渲染器无从下手。 -
空格是 CSS margin 摆出来的,不是字符。
katex-html里的15x + 8y = 40000转成文本时空格全丢 →15x+8y=40000。 不要试图靠正则补空格,唯一可信来源是第 1 条里的 TeX 源码。
结论:正确解法是放弃视觉层,只信
annotation。
3. 安装(在用户机器上部署)
scripts/ 里已经有全部代码(只有 .py,没有 .bat,原因见下),
复制到一个固定目录即可,例如 %USERPROFILE%\.workbuddy\tools\paste-cleaner\:
mkdir -p "$USERPROFILE/.workbuddy/tools/paste-cleaner"
cp "<本 skill>/scripts/"*.py "$USERPROFILE/.workbuddy/tools/paste-cleaner/"
cd "$USERPROFILE/.workbuddy/tools/paste-cleaner"
python ds_paste_cleaner.py --selftest # 内置回归,必须全绿
python ds_paste_cleaner.py --make-shortcuts # 生成双击启动器(.bat)
python ds_paste_cleaner.py --daemon # 无窗口后台常驻
python ds_paste_cleaner.py --status # 确认在跑
为什么包里不放
.bat,而要多一步--make-shortcuts? SkillHub 上传时会拒绝可执行脚本,报不允许的文件类型: scripts/_runner.bat。 所以发布包里只留.py,启动器在本地由ds_paste_cleaner.py自己写出来 (源码里的_RUNNER_BAT+SHORTCUTS就是那 11 个文件的内容,实测与随包发布 逐字节一致)。副作用是好的:启动器从此能跟着版本一起更新,不会再出现 「脚本升级了、启动器还是旧的」。
日常用法只有一句:复制完直接粘。监听进程会在 Ctrl+C 之后约 0.25 秒内 悄悄把剪贴板换成干净 Markdown。
⚠️ 如果 agent 跑在沙箱/非交互环境里:
--daemon派生的脱离进程会被回收, 表现为「刚起来几秒就没了」。这不是 bug,必须让用户自己双击启动监听.bat(没有的话先跑一次--make-shortcuts)。 同理cscript/wscript/.vbs常被安全策略拦截(LOLBin),所以自启走 注册表HKCU\...\Run,而不是启动文件夹里放 VBS。
4. 命令行参考
| 命令 | 作用 |
|---|---|
| --daemon | 无窗口后台常驻(推荐,用 pythonw.exe 派生)。已有实例在跑时会识别版本,版本不一致自动重启 |
| --restart | 先停旧进程再按磁盘代码起。改完代码后必用 —— 否则旧进程占着单例锁,新代码上不了岗 |
| --watch | 前台常驻,能看到日志(排障用) |
| --stop | 停止后台监听(会写 control.txt=stop,此后自愈逻辑不会擅自拉起) |
| --toggle-pause | 暂停 / 恢复净化(临时复制别的东西时用) |
| --once | 净化一次并打印剪贴板现状(含 HTML 还在不在)。这是判断「故障在净化侧还是粘贴侧」的利器,见陷阱 p |
| --once --dry | 同上,但只预览不写回 |
| --force | 即使没检测到公式也强制转换 |
| --file x.html / --text "..." | 离线转换,不碰剪贴板(最安全的调试入口) |
| --selftest | 跑内置回归测试(含 dedup 兜底、视觉层反推、启动器规范、零误伤) |
| --make-shortcuts | 在本地生成双击启动器(.bat)。发布包里不含 .bat,换机器后跑一次这个就恢复。加 --english-shortcuts 用英文文件名,加 --shortcut-dir DIR 指定输出目录 |
| --status | 查看自启 / 进程 / 心跳 / 版本 / 暂停状态。发现没在跑会自动拉起(自愈) |
| --diagnose | 一键体检:进程 / 心跳 / 剪贴板现状 / 三路径端到端活性自检(探针 A 文本路径 + 探针 B dedup 兜底路径 + 探针 C 视觉层反推)。排障首选,比 --status 信息全得多 |
| --interval 0.15 | 轮询间隔秒(默认 0.15;越小竞态窗口越小,API 调用本身极轻) |
| --heading-base 2 | 标题归一化到第几级(0 = 保持原样;默认 2,即 # → ##) |
--make-shortcuts 生成的 11 个文件与上表一一对应:
| 启动器 | 等价命令 |
|---|---|
| 启动监听.bat / start.bat | --restart 然后 --status(重启,保证按磁盘上的新代码跑) |
| 停止监听.bat / stop.bat | --stop |
| 暂停或恢复.bat / toggle-pause.bat | --toggle-pause |
| 立即净化剪贴板.bat / clean-now.bat | --once(净化并显示剪贴板现状) |
| 看剪贴板是什么.bat / peek.bat | --once --dry(只看不写,用来确认剪贴板干净了没) |
| 查看状态.bat / status.bat | --status |
| 运行自检.bat / selftest.bat | --selftest |
| 一键体检.bat / diagnose.bat | --diagnose(最该先双击这个) |
| 安装开机自启.bat / install-autostart.bat | --install-autostart |
| 取消开机自启.bat / uninstall-autostart.bat | --uninstall-autostart |
外加一个内部用的 _runner.bat(解释器探测核心,上面 10 个都 call 它),
它不面向用户双击。
默认生成中文文件名(中文 Windows 用户体验更好);
--english-shortcuts生成 ASCII 名。两种都行,内容一致。想要别的名字,生成后自己改名即可。
5. 实现要点(要复刻或改的时候看这里)
- 单文件、零依赖:
html.parser.HTMLParser自己搭 DOM 树(_TreeBuilder),ctypes直调user32.OpenClipboard / GetClipboardData / SetClipboardData。 不要去装 pywin32 / bs4 / lxml。 - 两条输入路径:
- 有 KaTeX 的
text/html→ 走 HTML→Markdown(信息最全,优先) - 只有纯文本 → 走
clean_plain_text(),把\[..\]→$$..$$、\(..\)→$..$, 并给裸的\begin{align}补$$包裹
- 有 KaTeX 的
- 判定是否该动手:
html_has_math(html) or text_has_latex(text)。 不满足就原样返回,绝不改动。这是「不误伤普通文本」的唯一保证。 - 框选复制时剪贴板里根本没有 annotation(1.4 新增):KaTeX 的
katex-mathml层被 CSSclip: rect(1px,1px,1px,1px)藏在公式角落,用户拖选公式时它不在选区里, 浏览器序列化出来的 HTML 只剩katex-html视觉层,连class="katex"外壳都没有。 这时只能按 KaTeX 的排版语义把视觉层「读」回 LaTeX(_KatexReverse):mfrac:同一 vlist 里top更负(位置更高)的是分子;没有frac-line的是\binommsupsub:|top| > pstrut 高度= 上标,否则 = 下标sqrt:root子树是指数,主 vlist 里非hide-tail的项是被开方内容mop:∑ ∫取字形;lim / sin / log还原成\lim等(\log_2的 "log" 会被字距拆成 "lo"+"g",必须拿整棵子树的文本判断,只看直接子节点会丢字)accent:accent-body里是字符(^˙)→\hat\dot;是 SVG 矢量(\vec箭头)→\vec\not:视觉层 = 「符号 + 覆盖斜线(字体私用区字形 PUA)」,且覆盖层在 DOM 里 排在符号前面,合并时方向不能搞反(\x01=→\ne)- 定界符 + 表格 →
cases/pmatrix/bmatrix/vmatrix;(...)^2要补成{(...)}^2,否则幂只绑到右括号上、渲染错位 - 视觉层里的
{ } _ % & # $是字形,回到 LaTeX 源码必须转义 —— 否则\left\{的{会被当成分组符吃掉,公式直接坏掉 - 判据全部来自几何量(
style="top:-3.677em"与pstrut高度),不依赖 具体行高/字号,所以放大缩小页面都不影响
- 必须抹掉 HTML 格式:只写回
CF_UNICODETEXT,EmptyClipboard()后 不复写 HTML。否则 Obsidian 又会去走 HTML 导入路径,前功尽弃。 - 标题里的行内公式要拍平成纯文本:Obsidian 不在标题里渲染数学,
留着
$y$只会更丑。 $$块前后各要一个空行:这是 Obsidian 渲染独立公式块的硬要求, 少了就退化成行内。- 写回自己的内容要记住序列号(
GetClipboardSequenceNumber), 否则监听进程会把「自己刚写的内容」当成新内容,无限循环。
6. 验证(改完必须跑,别只看代码)
cd "%USERPROFILE%\.workbuddy\tools\paste-cleaner"
python ds_paste_cleaner.py --selftest # 10 组断言(含 dedup 兜底 / 视觉层反推 / 零误伤)
python test_clipboard_roundtrip.py # 端到端:真写进 Windows 剪贴板再读回
python ds_paste_cleaner.py --diagnose # 三探针活性自检(A 文本路径 + B 兜底路径 + C 视觉层反推)
python test_katex_visual.py # 可选:68 个真实 KaTeX 样本的反推回归(需 Node + katex)
test_katex_visual.py 的判定标准不是「字符串相等」,而是把反推结果再渲染成
MathML、与原文的 MathML 逐字节比对(剥掉 mrow/mstyle 包裹层)—— 这样才能区分
「写法不同但语义相同」(x^{2} vs x^2,正常)和「真的推错了」。当前 68 个样本
全部通过语义等价。
还有两件必须验的事,自动化测试覆盖不到:
① 必须从 cmd 里跑一次,模拟用户双击的环境(剥掉 PYTHONIOENCODING / PYTHONUTF8,
让 Python 回落到 cp936)。这一步才能抓出 emoji 编码这类只在控制台下暴露的崩溃:
env = {k: v for k, v in os.environ.items()
if k not in ('PYTHONIOENCODING', 'PYTHONUTF8', 'PYTHONLEGACYWINDOWSSTDIO')}
r = subprocess.run(['cmd', '/c', '_runner.bat', '--selftest'],
capture_output=True, cwd=root, env=env, stdin=subprocess.DEVNULL)
assert r.returncode == 0, r.stdout.decode('gbk', 'replace')[-800:]
(stdin=DEVNULL 是为了让 bat 末尾的 pause 立刻返回,否则会挂住测试。
注意 _runner.bat 不在发布包里,跑这段之前先执行一次 --make-shortcuts。)
② 端到端活性自检——这是唯一能证明「它真的在干活」的证据:
python ds_paste_cleaner.py --diagnose
它往剪贴板写一个探针、等 1.2 秒、看是否被自动净化,然后恢复原内容。 不要用「进程状态显示正在运行」当作判据 —— 僵尸进程也会这么显示(见陷阱 k)。
再加两条手工实测(自动化测不出来的):
# 守护进程是否真的会自动改写
python -c "import sys,time; sys.path.insert(0,'.'); import ds_paste_cleaner as D, test_clipboard_roundtrip as T; T.put_dirty_clipboard(D.SELFTEST_HTML, T.DIRTY_PLAIN); time.sleep(1.5); print((D.read_clipboard()['text'] or '')[:46])"
# 期望开头: ## 快速解题步骤
# 普通文本不能被误伤
python -c "import sys,time; sys.path.insert(0,'.'); import ds_paste_cleaner as D; s='价格 \$100 和 \$200 两档。'; D.write_text(s); time.sleep(1.2); print((D.read_clipboard()['text'] or '')==s)"
# 期望: True
7. 已知陷阱
a. 发布包里绝对不能有 .bat(v1.4.1 的起因)。
SkillHub 上传时会逐个校验文件类型,遇到可执行脚本直接拒收:
提交失败
不允许的文件类型: scripts/_runner.bat
解法不是删掉启动器,而是把「打包」改成「生成」:_RUNNER_BAT +
SHORTCUTS 两个常量存在 ds_paste_cleaner.py 里,--make-shortcuts
在本地把它们写出来。这样包内只剩 .md / .py,而用户双击的体验一点没少。
同类平台大多也拦
.cmd/.ps1/.vbs/.exe。要做发布包时, 先只放.md+.py,其余可执行的东西一律运行时生成。
b. 生成的 .bat 必须是 CRLF 行尾。
LF-only 的文件会让 cmd.exe 把多行括号块和 rem 注释解析得乱七八糟,
典型报错是 'te-cleaner' 不是内部或外部命令(路径被从中间截断)。
不要手工转换 —— _write_bat() 已经统一做归一化,源码里存 LF 即可:
def _write_bat(path, text):
data = text.replace("\r\n", "\n").replace("\n", "\r\n").encode("ascii")
open(path, "wb").write(data)
注意那个 .encode("ascii"):它让「不小心塞进中文」当场抛异常,
而不是悄悄生成一个乱码启动器。
c. .bat 里不要用管道回读解释器路径。
for /f ... in ('python -c "import sys;print(sys.executable)"') 看起来聪明,
但子进程按 locale 代码页输出、cmd 按 OEM 代码页解码,用户名含中文时回来是乱码
(实测 娓╄壇鎭淇璁‐),于是「系统找不到指定的路径」。
正确做法:静默探测 + 直接调用(py -3 -c "..." >nul 2>nul 只判断 errorlevel)。
d. .bat 内部保持全 ASCII。
包括注释。中文注释在 OEM 代码页下会变成乱码甚至破坏语法。
所有面向用户的中文文案放在 Python 里打印(Python 走 Windows 控制台 API,不受代码页影响)。
中文只允许出现在文件名里(explorer 走 Unicode,没问题)。
e. 上面 b/c/d 三条都要有守卫,否则早晚复发。
规则写在注释里等于没写。--selftest 会逐条检查:10 个启动器一个不少、
内容全 ASCII、行尾全 CRLF、_runner.bat 里存在真正的调用行
"%PC_DIR%ds_paste_cleaner.py" %*。
(最后那条特意匹配调用行本身而不是 ds_paste_cleaner.py 这个字符串 ——
后者在注释里也出现,用它会「看起来有检查、实际拦不住」。)
f. 不要用 os.remove() 做状态标志。
本机安全策略会拦截删除操作。改用单文件覆写状态机:
一个 control.txt 里写 run / pause / stop,监听进程轮询它的内容。
不删任何文件。
g. 中文用户名会打崩一些原生扩展。
本机用户名是 温良恭俭让。如果哪天给这个项目引入了 C 扩展(比如某些解析库),
导入期可能直接 access violation 段错误 —— 段错误无法被 try/except 捕获,
会带崩整个进程。这也是坚持「只用纯标准库」的原因之一。
h. stdout 是行缓冲/块缓冲的坑。
后台进程用 -u 或 flush=True,否则日志看起来「什么都没发生」。
i. 中文 Windows 控制台 + emoji = 进程猝死(本 skill 实测踩到)。
程序里用 ✅ ❌ ℹ️ ⏸️ 做状态标记。它们在 cp936 里没有对应编码,
print 会抛 UnicodeEncodeError —— 而异常抛在 print 里会把整个进程带走
(退出码 1),现场只剩半截输出,stderr 还可能是空的。
必须在入口调用 make_stdout_safe():
sys.stdout.reconfigure(errors="replace") # 编不出的字符退化成 '?',中文照常
实测对比(_runner.bat --selftest,剥掉 PYTHONIOENCODING/PYTHONUTF8 还原双击环境):
修复前 rc=1,输出停在 ✅ 自检全部通过 的前一行;修复后 rc=0,✅ 显示为 ?。
注意这条同时影响 --daemon 的成功提示(✅ 监听已在后台运行)——
父进程会在打印那一刻崩掉,用户看到的就是「启动失败」。
j. 守护进程的 stdout/stderr 不要丢进 DEVNULL。
subprocess.DEVNULL 会把启动阶段的 traceback 一起吞掉,出问题时对着空日志无从下手。
改成追加写文件(paste-cleaner.out.log),代价是几 KB 磁盘,换来的是「崩了能查」。
k. 互斥体存在 ≠ 进程在干活。
被挂起/冻结的僵尸进程同样持有 mutex 句柄,于是判活函数返回「正在运行」,
而复制粘贴毫无反应 —— 这是最难查的一种假象(进程没死、句柄没放、CPU 不涨)。
解法:watcher 每秒写 heartbeat.txt,判活要求「互斥体存在 且 心跳新鲜(<15 秒)」。
实测有效:进程被冻结后,状态立刻从「正在运行」翻成「未运行」。
l. 主循环之外的裸调用要单独保护。
watch() 进入 while 之前有一行 clipboard_seq(),曾经没有任何 try 包裹。
它一旦抛异常,进程当场死掉(循环内的 try/except 根本轮不到)。
凡是「进主循环之前」的调用,都要自己兜住 —— 那是最容易被忽略的崩溃点。
m. 网页的「复制」是异步落板的 —— 净化结果会被覆盖(本 skill 最隐蔽的一条)。
浏览器在 copy 事件里同步写入富文本之后,往往还会走
navigator.clipboard.write 之类的异步路径再写一次。于是:
t=0.00 用户 Ctrl+C → 剪贴板写入 KaTeX 双层富文本(脏)
t=0.25 净化器写回 → 干净的纯文本,日志记 "cleaned" ← 看起来成功了
t=0.60 浏览器异步写入 → 剪贴板又被填回脏富文本 ← 被覆盖
t=2.0 用户粘贴 → 拿到脏数据,日志却写着「已净化」
症状极具迷惑性:日志、--status、端到端自检全部正常,唯独用户粘出来还是坏的。
解法是守护窗口:写回之后再盯 GUARD_SECONDS(5 秒)的序列号,
一旦发现被覆盖就再净化;若又被覆盖就继续后延(务必设硬上限,否则循环退不出去)。
本 skill 实测:连续两次覆盖全部被兜住。
n. 写回之后必须读回校验。
write_text(after) 的返回值如果没人检查,就等于闭着眼睛报成功。
剪贴板会被别的进程短暂占用(OpenClipboard 失败),也可能写完立刻被覆盖。
正确做法:写回后立刻读回,确认 HTML 真的被抹掉 且 纯文本与预期逐字一致,
不一致就退避重试几次。校验结果要进日志 —— 「verify=ok」和「verify=HTML 仍在」
是完全不同的两件事,日志里必须能区分。
o. 启动瞬间要补处理一次剪贴板。
主循环只认「序列号变化」,所以监听启动之前就已经躺在剪贴板里的内容
永远不会被碰。而用户的真实习惯恰恰是「先复制、再想起来去启动工具」。
在 watch() 里补一次 process_once()(只处理确实需要净化的,普通文本原样放过)即可。
p. 判断「剪贴板到底干净了没有」的最快办法。
别让人去开记事本做对照实验 —— 让工具自己说。--once 会打印剪贴板现状:
判定:已净化(来源 html)
剪贴板现状:纯文本 813 字符,HTML 无
---
## 快速解题步骤
设采购猕猴桃 $x$ 斤,苹果 $y$ 斤。
看到 $ / $$ 定界符 + HTML 无 → 剪贴板是干净的,故障在粘贴侧;
看到 xx、15x+8y=40000 出现两遍这种裸重复 → 剪贴板压根没被改写。
一刀切开「净化侧」和「粘贴侧」,不用再猜。
q. 判据漏掉「HTML 已丢的重复裸文本」→ 静默跳过(唯一不报错的失败路径)。
KaTeX 每个公式在 DOM 里有两份(katex-mathml 语义层 + katex-html 视觉层)。
浏览器复制时两份一起序列化;一旦 HTML 那一层掉了(某些浏览器/复制按钮只写
text/plain,或别的剪贴板工具重写后又覆盖回来),剪贴板里就只剩两份纯文本
首尾相连的产物:
15x+8y=4000015x+8y=40000 25-10=1525-10=15 x>500x>500
设采购猕猴桃 xx 斤,苹果 yy 斤。 ← 单字符公式同样成对出现
这种内容没有 $、也没有 \[ \(,而最初只写了两个判据:
def needs_cleaning(text, html):
if html_has_math(html): return True
return text_has_latex(text) # 只认 \[ \( \begin{...
→ 判「不含 LaTeX」→ 静默跳过:日志、状态、自检全绿,用户看到的还是脏内容。
这是最坑的一条,因为它不报错、不写日志。 修法是加第三判据
looks_like_katex_duplication():正则 ([" + 数学字符集 + "]{3,80})\1 找
相邻重复,且要求重复段里至少有一个数字或运算符(避开 abcabc、
ShareXShareX 这类噪音);命中走 dedup 降级路径去重。
注意降级路径的能力边界:\frac{40000-15x}{8} 这类分数,mathml 与 html 的
文本形态不同(40000-15x8 vs 840000-15x),去重救不回来 —— 只能靠 HTML 层
里的 annotation 还原。所以日志里出现 cleaned via dedup 是个信号:
说明 HTML 层丢了,该去查是不是有剪贴板助手 / 浏览器扩展在中间插手。
r. 单例锁 + 旧进程 = 「改完代码不生效」;僵尸进程还占着锁不放。 这条坑出现过两次,症状都是「明明重启过了,行为没变」:
--daemon见到已有实例就直接返回「已经在运行」,不比较版本。改完代码后 旧进程仍在值班 → 跑的还是老逻辑。- 更糟的是僵尸:进程被挂起/冻结时互斥体句柄不释放,新进程永远拿不到锁, 表现是「怎么点都起不来,且没有任何报错」。
解法三件套:
- 心跳文件里带上
时间戳|pid|版本,启动时比对版本,不一致自动重启; --restart:先stop再起,改完代码必用;kill_stale_watcher():按心跳里的 pid 用OpenProcess+TerminateProcess直接结束僵尸(不要去taskkill乱杀 python 进程,会误伤别的脚本)。
s. 让「工具没在跑」不再需要人来记。
守护进程类工具最真实的失败模式不是崩溃,而是根本没启动(用户忘了双击、
开机自启要下次登录才生效、测试时被顺手停掉)。给所有前台命令加自愈:
--status / --once / --diagnose 一开始就检查 watcher_state(),
不在运行就 ensure_running() 拉起来(但 control.txt == "stop" 时尊重用户意图,
不擅自拉起)。这样用户只要动过任何一个脚本,保护就已经在岗了。
8. 文件清单
deepseek-paste-cleaner/
├── SKILL.md ← 你正在读的文件
├── references/
│ └── troubleshooting.md ← 症状 → 根因 → 处置 速查表
└── scripts/
├── ds_paste_cleaner.py ← 全部逻辑(单文件,含 --make-shortcuts 与内置自检)
├── test_katex_visual.py ← 68 个真实 KaTeX 样本的「视觉层 → LaTeX」语义等价回归
└── test_clipboard_roundtrip.py ← 端到端剪贴板往返测试
包里刻意没有 .bat —— SkillHub 拒收可执行脚本。用户跑一次
python ds_paste_cleaner.py --make-shortcuts,会在脚本同级目录生成
_runner.bat + 10 个双击启动器(详见第 4 节的对照表)。
运行时会在脚本同级目录生成(都可安全清空,不含配置):
control.txt(状态机)、heartbeat.txt(心跳)、paste-cleaner.log(日志)、
paste-cleaner.out.log / paste-cleaner.crash.log(守护进程输出与崩溃)、
capture-last.txt / capture-prev.txt(最近两次处理的完整现场)。
作者:Primer 🧬
微信扫一扫