免费AI编码代理实战:GUI操控、MCP协议与单文件部署
今年我在折腾日常编码自动化时写了一个免费的 AI 编码代理。它不是那种只能改代码的插件而是能真正替我去操作屏幕上 GUI 程序、通过 MCP 协议调用外部工具的代理整个项目最终打成了一个单文件拷到哪台机器上都能直接跑。今天想把这个项目的设计思路、实现细节以及我在实际开发中踩过的坑完整地写出来。如果你也在做类似的 AI Agent 工具或者正纠结怎么让编码代理具备“操作真实软件”的能力这篇应该能给你不少直接可用的参考。1. 先讲清楚它解决什么问题为什么普通编码代理不够用现在市面上的 AI 编码代理不少像 Cline、Aider、Codex CLI 这些我都用过。它们有一个共同点工作界面是终端、编辑器能力范围基本限定在文件读写、执行命令、搜索代码。一旦遇到“改完代码还得去 GUI 软件里点几下”“编译配置要在图形界面里调整”这种需求传统代理就断了链条——它只能生成一段文字教你怎么点不能替你去点。1.1 真实场景困住我的瞬间去年年底帮朋友处理一个项目CMake 配置改了之后需要在 CMake GUI 里重新指定构建目录、勾选选项再点 Generate。这种操作没有命令行参数纯靠鼠标。我当时想要是有一个 AI 代理能直接接管屏幕看着界面操作整个流程就闭环了。后来发现这类需求不是少数SAP GUI 里的菜单操作、STM32 嵌入式项目的图形化配置工具、甚至 Altium Designer 这类 EDA 工具里的 AI 接口调用都停留在“人手动点”的阶段。1.2 “单文件运行”这个硬指标做这个代理前我给自己定了几个硬指标免费、支持操控 GUI、支持 MCP、单文件运行。前三个好理解最后一个是很多人忽略的刚需。我之前用 Python 写过各种小工具最痛苦的不是写功能而是环境搭建什么pip install、虚拟环境、Python 版本兼容换一台机器就得全来一遍。Agent 工具如果还要别人装依赖、配环境那它再强也没人愿意用。所以我给这个项目定的底线是交付物就是一个可执行文件拖到任何 Windows/Linux 机器上直接运行不需要装运行时。1.3 免费与开源的选择为什么要免费因为编码代理这东西的本质是“信任”——你要把操作权交给它。免费、代码可见用户才能安心看它到底做了什么。我最初也想做成商业产品但实际做下来发现真正值钱的不是代理本身而是接入的各种 MCP 工具生态和用户针对自己工作流做的调优。所以工具本体完全免费开源靠业务方对接和定制做商业闭环这个思路也挺清晰的。2. 屏幕级操控的实现路线截图识别、坐标点击与状态回读GUI 操控是整个项目里技术难度最高的一块。很多 Agent 框架提“computer use”但落地到 Windows 这种桌面系统上实际情况比想象中复杂得多。我前前后后试了三套方案最终确定了“截图 坐标点击 状态回读”的组合。2.1 为什么放弃辅助功能 API第一反应是用 Windows UI AutomationUIA这类辅助功能接口它不需要图像识别直接读取窗口控件树定位精准。这套方案的问题在兼容性很多老软件、自绘控件、非原生界面根本没有暴露 UIA 信息比如 SAP GUI 的部分自绘表格在 UIA 里就是一团杂乱的空白节点。更别提某些游戏引擎或硬件工具的自定义界面了。我实测下来存量桌面软件里能拿到完整控件树的不到四成。所以最终还是用截图——只要“眼睛”看得见就能操作这才是通用 GUI 操控的出路。2.2 截图识别与模拟点击的代码实现核心流程是三步截屏、定位、点击。截屏我用的mss库比pyautogui.screenshot()快很多一次全屏截图大概 80ms。定位这块我先是让大模型直接“看”截图输出要点击的坐标。后来发现模型虽然在纯视觉任务上很强但涉及精确坐标输出时经常差几十像素——这对按钮点击是致命的。所以我在模型和实际点击之间加了一层“模板匹配”兜底模型看到截图后输出目标的文字描述比如“按钮Generate”我先用 OCR 在截图上找出所有文字及其坐标再用 OpenCV 模板匹配或文字检索定位最接近的区域最终合成精确坐标交给pyautogui.click()执行。import mss import numpy as np import cv2 from paddleocr import PaddleOCR import pyautogui ocr PaddleOCR(use_angle_clsTrue, langch) def grab_screen(): with mss.mss() as sct: img np.array(sct.grab(sct.monitors[0])) return img[:, :, :3] # drop alpha def find_text_position(text, img): result ocr.ocr(img, clsTrue) for line in result: for box, (candidate, conf) in line: if text.lower() in candidate.lower(): x_center int((box[0][0] box[2][0]) / 2) y_center int((box[0][1] box[2][1]) / 2) return x_center, y_center return None def click_by_text(text): img grab_screen() pos find_text_position(text, img) if pos: pyautogui.click(pos) return True return False这段代码执行链路的核心是“让大模型理解界面让程序保证精度”。千万不要直接让模型输出坐标就点击我测试时真实准确率大概只有 70%加了 OCR 定位后准确率提升到 97% 左右。2.3 能操作的真实场景这个 GUI 操控层通用性很高我实际验证过的场景包括CMake GUI 里的 Generate 按钮、STM32CubeMX 的配置界面、以及一些工业软件的自诊断窗口。基本逻辑都一样模型看截图理解当前状态决定下一步操作然后通过点击、输入、滚轮等原语完成交互。每次操作完成后我都会重新截图让模型确认界面是否产生了预期变化如果不变就重试或换路径这就是“状态回读”。3. 接入 MCP 生态一句话解释协议然后给出我的客户端实现MCPModel Context Protocol是这两年 Agent 生态里最重要的一件事。它不是某个大模型厂商专属而是开放的协议只要两端按协议走任何 LLM 工具都能通过它调用外部能力。对于我这份代理来说有了 GUI 操控还不够因为有些工作需要更底层的工具链协作比如反汇编器扫描结果、设计工具里的图层面板、代码仓库的复杂搜索——这些不适合用“看屏幕”来做最好直接走 API。3.1 为什么编码代理需要 MCP你可以把 MCP 理解成 Agent 世界的 USB 接口。以前每个 AI 工具要接入某个外部软件都得单独写一套对接逻辑现在外部软件只要实现一个 MCP Server所有支持 MCP 的客户端都能直接调用它。这样我的代理就不需要内置几百个软件的 API 对接而是按需加载用户的 MCP Server。我见过不少项目都在做这方向比如给 IDA 逆向工具加 MCP 插件、给 Figma 加 MCP 接口、给蓝湖做 MCP 适配甚至 Codex 接入 Figma MCP 这样的玩法都在验证这个趋势。对我这个单文件代理来说支持 MCP 的收益被进一步放大单文件本身不臃肿因为你想要的能力都可以通过 MCP 动态塞进来。3.2 MCP 客户端的连接方式与代码示意MCP 协议目前主流有两种传输stdio 和 HTTP含 SSE。stdio 适合本机工具启动一个 Python 脚本作为 Server通过标准输入输出通信HTTP 适合远程服务和多语言场景。我的代理里两种都支持了通过配置文件指定类型。# 伪代码示意MCP 客户端连接和调用 from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def call_mcp(server_cmd, tool_name, arguments): server_params StdioServerParameters( commandserver_cmd[0], argsserver_cmd[1:], env{MCP_CONFIG: str(server_cmd[-1])} ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.call_tool(tool_name, arguments) return result接入流程不复杂但有个关键点MCP Server 返回的数据结构五花八门有的返回 JSON有的返回图片路径有的直接返回文件。我在代理层做了一层标准化转换把所有返回都转成“文本摘要”和“附件列表”这样 LLM 处理起来不需要关心底层差异。3.3 与 GUI 操作结合的组合技MCP 与 GUI 操作结合后能力会出现乘法效应。举个例子在 Altium Designer 这类 EDA 工具里先通过 MCP 获取当前原理图的网络连接关系再由 GUI 操控层去执行工具菜单里的“布局更新”动作最后再通过 MCP 读取更新的状态。纯靠一套方案做不全整个流程。我目前接过的 MCP Server 里比较典型的有IDA MCP把反汇编器的反编译结果、函数列表暴露出来蓝湖 MCP / Figma MCP设计稿信息拉取数据库类 MCP直接查询业务库再根据结果操作界面。这套统一入口的设计让我不用为了每个新软件去专门写 Agent 流程只要对方有 MCP 接口我可以直接放到代理的“工具链”里。4. 单文件运行的关键拼图打包、内嵌资源与临时目录单文件运行听起来简单做起来坑很多。这个项目早期用 Python 写用 PyInstaller 打包。PyInstaller 可以把 Python 解释器、依赖库全部塞进一个 exe但实际操作下来有几个非常实际的问题必须处理妥当否则就是一个只能在自己电脑上跑的“假单文件”。4.1 选型依据Python 还是 Go初始版本我用 Python因为它生态里做 GUI 自动化pyautogui、mss、paddleocr和 MCP 客户端都极其方便。代价是最终文件体积大PyInstaller 打完差不多 120MB主要是 PyTorch/OpenCV 这类重量级依赖。后来我评估过用 Go 重写。好处是交叉编译简单、体积小、内存占用低坏处是 GUI 自动化生态不成熟OCR 还得外挂服务。所以最终决定继续用 Python但把重量级依赖抽出来做延迟加载能瘦则瘦。4.2 内嵌资源与临时解压策略PyInstaller 默认支持打包数据文件但对于一些需要动态加载的库比如 PaddleOCR 的模型文件直接打进去会导致启动慢。我的做法是把模型文件单独压缩运行时首次解压到系统临时目录之后做 MD5 校验如果存在就直接复用。这个策略在代理工具上特别好用因为大部分场景是“长期运行”首次启动的一次性开销换来的是每次启动快很多。# 打包时把模型资源放进来但标记成 data pyinstaller --add-data models/ocr_model.zip;models --onefile agent.py解压逻辑放在入口处判断temp/agent_runtime/ocr_model/是否存在不存在才解压。记得用临时目录加版本 MD5 做区分这样新版本发布不会覆盖旧版本导致模型加载异常。4.3 跨平台注意事项单文件运行还有一个隐含需求跨平台。我在开发时先用 Windows 做主力验证后来用 Wine 测 Linux 兼容性。PyInstaller 不能交叉编译只能在目标系统上打对应包。这就要求 CI 里配三个构建机Windows、Ubuntu、macOS每次发版自动产出三份单文件。真正费工夫的是路径分隔符和 GUI 自动化在不同系统上的差异——Linux 上mss依赖 X11macOS 需要屏幕录制权限。这些必须写清楚文档否则用户一跑就闪退体验非常糟糕。5. 踩坑实录从截图模糊到坐标漂移再到模型幻觉这是全文最想让你们借鉴的部分。所有功能看起来都挺好真正把这些功能稳定跑起来我经历了大概三周的“打地鼠”过程一直在各种小问题里翻车。5.1 截图识别层的坑分辨率和缩放首先是截图清晰度。我的目标场景大多是高 DPI 的 Windows 笔记本系统默认开了 125%、150% 缩放。这让mss截出来的尺寸和真实物理像素不一致导致 OCR 返回的坐标偏了整整一格缩放比例。解决方法是统一分辨率换算import ctypes # Windows 下获取缩放比例 try: ctypes.windll.shcore.SetProcessDpiAwareness(1) dpi ctypes.windll.user32.GetDpiForSystem() scale dpi / 96.0 except Exception: scale 1.0DPI 感知必须放在程序最前面比创建截屏对象更早。我一开始没加导致所有初版用户都出现点击偏移后来排查了很久才定位到是 Windows DPI 虚拟化搞的鬼。5.2 坐标点击层的坑窗口移动与遮挡第二个坑是窗口移动。截图识别的是当前像素坐标但用户可能在执行过程里拖动窗口或者弹出了一个对话框把目标遮住。我在执行点击前会重新截一次图对比关键参考点比如窗口标题栏的颜色分布是否偏移超过阈值超过则重新计算坐标。更麻烦的是弹窗中断。比如某个软件操作到一半突然弹出一个“是否保存”对话框模型如果不识别它就会继续点原本的坐标结果点到弹窗上的按钮。我的处理是每一步状态回读都做一个“异常界面检测”一旦发现当前屏幕里出现了之前没见过的模态对话框特征就暂停动作询问用户。5.3 模型决策层的坑幻觉操作不可忽略最痛的还是模型幻觉。LLM 在生成操作计划时经常会出现“觉得点击完就有反应”的想象。比如让模型点击“Generate”后它可能不重新截图确认就直接说“完成”。这不是模型故意的而是它的训练目标促使它生成连贯回答并不天然关注环境反馈。所以我在代理逻辑里强制加了一个机制任何操作后必须回读屏幕把实际画面和预期状态差异提供给模型模型必须解释为什么状态没变。如果连续三次回读无变化就中断并向用户报告。这个机制彻底解决了“看起来在工作实际早就卡死”的问题。5.4 稳定性数据与控制策略对比修复前后我拿 30 个 GUI 任务做过一轮跑批。修复前的成功率大约 61%其中大部分失败发生在模型没有回读、点到错误位置但继续前进修复后成功率提升到 89%。剩下的 11% 基本来自 OCR 识别不出稀有字体或者某些深色主题软件里截图对比度过低的情况。针对这些我加了“人工接管”模式模型操作前先生成计划用户批准后执行如果需要用户可以按 ESC 中断这样总能兜底。6. 安全边界与授权控制自由操作的前提是可控一个能点击键盘、能操纵鼠标、能运行外部命令的代理本质上就是一台机器上的“完全控制用户”。如果不加权限控制这个工具根本不敢推广给其他人用。所以我设计了一套精简但有效的安全机制。6.1 操作白名单与黑名单代理的每次 GUI 点击都不经过脚本逻辑过滤而是经过一层“界面区域分类”在截图左上角放一个非侵入式指示器只有我的模型看到指示器为“允许”时才允许执行。这个分类通过对系统进程名做匹配白名单外的程序一律不给操控权限。截图时如果发现当前激活窗口属于黑名单银行客户端、密码管理器等直接拒绝所有动作。6.2 敏感操作的二次确认我定义了几类“高风险操作”删除文件、修改注册表、提交代码、调用外部付费 API。这些操作在执行前会向用户弹出一个确认对话框里面展示完整参数必须点“允许”才执行。这不算完美方案但成本最低——我没有精力去做复杂的策略引擎就靠“高敏感即打断”来保证安全。6.3 日志审计所有操作都可以重放另一个我比较推荐的习惯是完整日志。代理的每一次鼠标移动、点击坐标、执行的命令、模型给出的决策理由全部写入本地 JSONL 日志。别看它简单真出问题时能省很多事。有一次代理误删了一个项目的构建缓存我通过日志马上定位到是哪条命令、是哪个 MCP 工具触发的然后改掉了工具描述避免再次发生。权限控制是这类工具的生死线。如果你的代理没有任何限制就能被任何人调用哪怕它本身没有传播只要用户误操作一次就会丧失信任。最后再分享一个我目前一直在用的小技巧在实际开发中不要追求“让 AI 一次性正确完成整个任务”。更好的方式是把这个代理当作“有自主操作能力的助手”让它先做计划每步操作有反馈允许随时被打断。你可以先从一个最小范围跑通让它打开计算器点几个按钮截图确认然后再逐步放开 GUI 和 MCP 的组合场景。这样既能把不确定性的影响控制在可接受范围内也能逐步积累操作模板。希望这篇能帮你少走我走过的那些弯路。

相关新闻

ENSP校园网组网全流程:从VLAN规划到路由器配置避坑指南

ENSP校园网组网全流程:从VLAN规划到路由器配置避坑指南

简介:这是一份面向高校计算机网络课程的校园局域网组网课程设计参考方案,以 ENSP 模拟器为载体,适合正在完成组网实训作业的学生,也适合初学 VLAN、子网划分与路由配置的入门者借鉴设计思路。资料以单个 PDF 文件呈现,…

2026/10/7 4:37:35 阅读更多 →
ZYNQ7020 LVDS差分信号实战:OBUFDS与IBUFDS原语详解

ZYNQ7020 LVDS差分信号实战:OBUFDS与IBUFDS原语详解

1. 为什么LVDS在ZYNQ7020上值得单独拎出来讲LVDS(Low-Voltage Differential Signaling)这个接口标准,做FPGA的人几乎都绕不开。它的核心思路很简单:用一对差分线,以大约350mV的摆幅、1.2V左右的共模电压来传输数据。摆…

2026/10/7 4:36:34 阅读更多 →
用MOS管搭建电压比较器:从原理到实战的简易指南

用MOS管搭建电压比较器:从原理到实战的简易指南

1. 从一个实际需求说起:为什么要用MOS管搭比较器前阵子帮朋友修一台老式充电器,故障现象是充满电后指示灯不灭。拆开一看,充电管理芯片已经停产,市面上买不到原型号。手头只有一堆MOS管和几个电阻电容,于是临时搭了一个…

2026/10/7 4:36:34 阅读更多 →

最新新闻

零依赖与WebRTC P2P:为网页小游戏打造点开即玩的联机方案

零依赖与WebRTC P2P:为网页小游戏打造点开即玩的联机方案

这几年我经常看到一类帖子:求网页版小游戏合集、希望复制链接浏览器就能打开、强调不用下载App。老实说,这类需求在玩家眼里是“免安装”,在开发者眼里却是另一道坎——你不仅要让游戏跑得足够轻,还得在实时互动场景下把网络体验做…

2026/10/7 5:47:22 阅读更多 →
D435i多相机时间同步实战:从硬件触发到Fast-LIVO对齐的完整方案

D435i多相机时间同步实战:从硬件触发到Fast-LIVO对齐的完整方案

多相机采集这件事,我最早是被机械臂上一个很奇怪的现象逼着往深处研究的。当时用两台 D435i 同时录一段贴在机械臂末端的棋盘格,回放时发现两个深度流重建出来的点云在快速摆动阶段错位非常明显,边缘像拖了条尾巴。当时的第一反应是外参标定出…

2026/10/7 5:47:22 阅读更多 →
构筑AI Agent实时安全防线:Harness与AgentCore Gateway集成实践

构筑AI Agent实时安全防线:Harness与AgentCore Gateway集成实践

AI Agent 这东西,跑通一个 demo 不难,真正难的是你敢不敢让它带上生产环境的权限。我见过太多团队把 Agent 的 API Key 直接发给大模型,让模型自己去调数据库、发邮件、查支付订单,结果线上环境连个像样的审计都没有。你问他们出了…

2026/10/7 5:47:21 阅读更多 →
车牌识别Python源码实战:定位、分割、识别与调参优化

车牌识别Python源码实战:定位、分割、识别与调参优化

简介:一份面向初学者的车牌识别Python源码压缩包,以课程案例形式串联开源计算机视觉库图像预处理、图像分割、边缘检测、形态学操作及光学字符识别等核心环节,适合计算机视觉或智能交通场景入门。压缩包共24个文件,约14.94MB&…

2026/10/7 5:47:21 阅读更多 →
AI代理自动生成可交互架构图:archify原理与实操指南

AI代理自动生成可交互架构图:archify原理与实操指南

1. 项目核心拆解:archify 到底是什么第一次在 GitHub 上刷到 archify 这个项目时,我其实有点怀疑——又是“AI 自动生成”系列?这类宣称能让 AI 替你干活的工具,十有八九是套壳,生成的图也就唬唬外行。但点进去看了 RE…

2026/10/7 5:47:21 阅读更多 →
Corundum网卡在国产FPGA上的四端口移植与性能调优实践

Corundum网卡在国产FPGA上的四端口移植与性能调优实践

1. 项目价值:Corundum是什么,为什么要在国产FPGA上做1.1 一个完全开放的高性能网卡参考设计做FPGA网络加速的同行,应该都听过Corundum。这个开源项目几乎是目前最完整的开源网卡设计方案,不是一块现成的PCIe网卡,而是一…

2026/10/7 5:46:20 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 6:26:51 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 4:21:51 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →