picgo-for-md
把 Markdown 文档引用的本地图片批量上传到图床,并生成一份链接已替换为远程 URL 的新文档。
入口示例
用户直接说人话即可,无需记命令、无需提 PicGo、无需说明语法。
| 场景 | 说法 |
|---|---|
| 转换单篇 | 「把 D:\docs\架构.md 里的图片传到图床」<br>「这份 md 的配图上传一下,生成远程链接版」<br>「帮我把它里面的本地图片转成外链,原文件别动」 |
| 批量 | 「D:\docs\ 下所有 md 的图片都传到图床」 |
| 只说症状(由 Skill 判断) | 「md 传到 ima 之后图片全丢了」<br>「文档发给别人,图片全是本地路径打不开」 |
| 英文 | 「upload the images in this md to my image bed」<br>「convert local image paths in report.md to remote urls」 |
| 首次使用(未检测到 PicGo) | 「我想把 md 里的图片传到图床」→ Skill 先探测;探测不到时问装命令行版还是桌面版,装完引导填图床,再继续。 |
| 追加指令 | 说法后加「全部上传,不用问我」→ 跳过清单确认。<br>不加时按 S3 给出清单等你勾选。 |
执行纪律(命中即做,最小输出)
识别到意图后立即执行,把过程留在工具调用里,不要搬到对话中。
不做的:
- 不写开场白、不做确认(意图明确时)——用户说"把这份 md 的图传到图床",直接开做
- 不逐步播报——「我先扫描一下」「接下来探测端口」「现在开始上传」这类句子一律不出现
- 不复述规则、不解释设计、不展示推理与方案权衡
- 不预告后续步骤、不转述脚本原始 JSON、不列步骤清单或进度树
只输出三样:
| 场合 | 内容 | 篇幅 |
|---|---|---|
| 授权点 | 确认清单(S3 格式) | 按清单 |
| 真失败 | 按 references/troubleshooting.md 翻译的成因 + 修复动作 |
1–3 行 |
| 收尾 | 成功 / 复用 / 跳过 / 失败四档计数 + 输出路径 | ≤6 行 |
中间状态——通道探测结果、端口号、哈希值、缓存命中、临时文件路径——一律不汇报。
多文件批量时,每份文档只出一行进度,不重复完整清单。
铁律(不可违反)
- 对 PicGo 配置完全黑盒:不读取、不解析、不修改 PicGo 的任何配置文件或字段(含
data.json、config.json、picBed.current、图床名、token)。图床是哪个由 PicGo 自己决定。 - 凭证零暴露:任何环节——日志摘录、错误报告、终端回显、缓存文件——都不得输出 token / secret / key / Authorization 等凭证。从 PicGo 日志取材时必须先脱敏(见 scripts/upload.py 的
redact())。 - 原文档不动:只输出
<原名>.bed.md到原目录。 - 可用性判定只靠实传:不 ping、不查图床接口、不读配置。取待传清单第一张真实图试传,成功即判定可用。
- 不擅自拉起或安装进程:GUI 未运行只做引导;安装前必须取得明确授权。
权限分级
必须取得明确授权才能执行(不可假定同意):
| 动作 | 授权方式 |
|---|---|
| 把图片上传到公网图床 | 图片清单确认环节(用户确认即视为授权该批次) |
写入 .bed.md 与缓存文件 |
同上,确认时须明示输出路径 |
| 安装 PicGo(命令行版或桌面版) | 单独一次显式询问,说明将改变的内容 |
无需额外询问:读取文档、解析图片引用、计算哈希、探测本地端口、读取 PicGo 日志尾部(脱敏后)。
入口确认
意图明确(用户已说清"上传/转换/发布某文档的图")→ 直接进入扫描,不做开场确认,清单确认即为授权点。
意图模糊(只是提到图床或图片丢失、未指明文档与动作)→ 用一句话确认:"要我把这份 md 里的本地图片上传到图床并生成远程链接版吗?" 得到肯定再继续。这一句之外不追加任何说明、铺垫或选项罗列。
流程
S1 通道探测
python scripts/probe.py
返回 {"channel": "gui"|"cli"|"none", "port": N}。
- gui:
36677-36680端口扫描,POST /heartbeat返回success:true即命中。最快路径,直接用。 - cli:受管 node workspace 与 PATH 中找到可执行
picgo。 - none:进入安装引导(见下)。
若 GUI 已安装但服务未运行——不要启动进程。改为提示:
> 没连上 PicGo 的上传服务。打开 PicGo,在设置里勾选开机自启,服务就会常驻后台;现在也可以手动启动 PicGo 后告诉我。
S1-fallback 安装引导
必须取得授权后执行。用非技术语言问一次(不提 npm / winget):
> 没检测到 PicGo。它负责把图片传到图床,需要先装一下。
> 1 - 命令行版(推荐):体积小,后台运行,适合批量处理
> 2 - 桌面版:有图形界面,可以手动传图和查看历史
> 装哪个?
- 选 1:
npm i picgo装进受管隔离目录~/.workbuddy/binaries/node/workspace/(禁止npm i -g)。 - 选 2:
winget install PicGo.PicGo。
装完不代配:告知用户自行在图床设置中填好凭证并设为默认,完成后说一声,Skill 重新探测。Skill 不读取、不代填任何配置。
S2 扫描与归类
python scripts/transform.py scan <md文件或目录>
解析  与 <img src="path">(属性原样保留),相对路径按 md 所在目录解析。输出清单 JSON,每张图标为:
| 类别 | 判定 | 处理 |
|---|---|---|
cached |
哈希 + 域名命中缓存 | 复用旧 URL,不占上传名额 |
pending |
本地文件存在 | 待上传 |
remote |
已是 http/https | 跳过不动 |
base64 |
data:image/... |
解码落临时文件后上传 |
missing |
路径不可解析 | 保留原样,报告标红 |
S3 确认清单
≤20 条全列;>20 条给汇总 + 前 20 条,其余折叠。格式:
待上传 13 张(缓存复用 4 张,跳过 3 张,缺失 1 张)
1 [复用] diagrams/a.png 128 KB
2 [待传] diagrams/b.png 92 KB
3 [跳过] https://.../c.png
4 [缺失] diagrams/d.png
输出:./doc.bed.md
> 回车确认;输入要排除的序号(空格分隔)取消部分;q 放弃
用户确认即授权上传与写文件。支持 --all 跳过本环节。
S4 探活
取 pending 清单第一张真实图试传。成功则该 URL 直接采用(不浪费),进入 S5。失败则整批中止,读日志尾部、脱敏、按 references/troubleshooting.md 翻译成人话并给出修复动作。
不传 1×1 探针图——不往图床留垃圾。
S5 批量上传
python scripts/upload.py --channel gui --port 36677 --image <绝对路径>
逐张调用。单张失败跳过继续,末尾汇总。失败项在 .bed.md 中保留原本地引用。
S6 输出与报告
python scripts/transform.py apply <md> --map <mapping.json> --out <原名>.bed.md
缓存写入 ~/.workbuddy/picgo-for-md/uploaded.json,键为 sha256(内容) + URL 域名(域名从上传结果提取,不读配置;换图床后旧 URL 不复用)。
收尾报告:成功 / 复用 / 跳过 / 失败 四档计数、失败明细、输出文件路径。
关键约束
- 路径统一转绝对路径,UTF-8 提交(应对中文与带空格文件名)。
- GUI 通道上传失败时 HTTP 状态仍是 200,必须判
success字段,不能只看状态码。 - 端口冲突时 PicGo 会自增,故扫 36677–36680 一段而非写死一个。
/heartbeat只证明进程存活,不证明图床可用,不可作为可用性判据。
运行器
脚本用 Python 3 标准库实现,零第三方依赖。执行时优先使用受管解释器的绝对路径(Windows 形如 %USERPROFILE%\.workbuddy\binaries\python\versions\<版本>\python.exe),其次 python3。脚本位于本 Skill 目录下的 scripts/,一律用绝对路径调用。
安装与更新
python install.py # 安装或更新到 ~/.workbuddy/skills/
python install.py --target <技能根目录> # 装到指定位置(如项目级 .workbuddy/skills)
python install.py --dry-run # 只比对差异,不写盘
python install.py --skip-tests # 跳过安装前自检
三步串行:frontmatter 校验 → 测试自检 → 幂等同步。任一步不过即中止,不写盘。
- 幂等:只覆盖有差异的文件、清理目标端多余文件;已是最新时不做任何写入。默认目标就是本技能所在目录时会识别为原地校验,不自我覆盖。
- 拦截项:
name与目录名不一致;纯量值含": "或" #"(YAML 会截断成嵌套映射或当成注释);description超 200 字符、或前 170 字符内无中文(触发词会落到技能清单的截断区之外)。 - 生效时机:技能清单在应用运行期是快照。改完
description后需重启 WorkBuddy 或新开会话才会加载新版本,否则改的是运行期看不到的那一份。
测试
python tests/run_all.py
四套测试、89 项用例,全部不产生公网上传:解析测试纯离线,通道与端到端测试打在本地 mock 服务(tests/mock_picgo.py)上,安装测试全程在系统临时目录内进行。
| 套件 | 覆盖 |
|---|---|
test_suite.py |
扫描分类、替换语法保真、目录批量、大文件性能、去重 |
test_upload.py |
GUI/CLI 双通道全分支(成功、HTTP 200 假成功、500、非 JSON、空 result、超时、无服务)、请求报文形状、中文路径编码、脱敏、脚本入口退出码、端口探测 |
test_e2e_mock.py |
扫描→去重→上传→缓存→替换全链路、二次运行零上传、换图床不复用、失败传播、缓存文件结构 |
test_install.py |
首次安装、幂等重装、差量更新与多余文件清理、dry-run 不写盘、三类 frontmatter 拦截、自安装识别、自检通过/失败两条路径 |
自检存在递归风险:install.py 会跑本套件,而本套件会再调 install.py。护栏是环境变量 PICGO_SKIP_INSTALL_TEST——由 install.py 在执行自检时注入,test_install.py 检测到即立即退出。
改动任何脚本后先跑测试再发包。
参考
references/picgo-facts.md— 已核实的接口事实与坑位references/troubleshooting.md— 日志特征 → 人话 → 修复动作