DeepSeek Harness Plugin

chenqianyu486-sys/dsh-zju-lecture-note

Stars ★ 3 Category Docs & Rendering Added 2026-11-02

Turns Zhejiang University classroom replays (classroom.zju.edu.cn) into Markdown study notes. Its scraping core comes from nzw806/zju-lecture-summerizer- (a standalone Python tool, not a DSH plugin), and the slide capture follows 8eoyw/zju-learning-cli (MIT), reimplemented here without copying code. It reads the public speech transcript by minute, sentence or chunk, can download the projected slide screenshots as a PDF and reference selected pages in the note, and writes a note named after the class date and section whose Markdown is checked so formulas and tables render. Transcript reading is public; the slide capture is opt-in (enable it in the plugin settings) and needs a ZJU login state (ZJU_COOKIE / ZJU_COOKIE_FILE or a local Edge profile).

Install

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:chenqianyu486-sys/dsh-zju-lecture-note

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).

README

This plugin publishes its README in Chinese only.

面向 DeepSeek Harness(dsh) 与任意 MCP 宿主:把浙大智云课堂(classroom.zju.edu.cn)的课程回放 变成一份可复习、结构化、能正常渲染的讲义。 语音字幕 + 本地课件是默认主链路;课堂投影 PPT 截图是可选模块,需要时在插件界面一键打开。 字幕接口公开免登录;核心只用 Python 标准库(PDF 课件另需可选的 pypdf)。

隐私优先:登录 Cookie 只进入本地进程内存,工具不收、不回显、不落盘、不写日志; 本地文件工具拒绝凭据与浏览器数据。细则见 SECURITY.md。

本仓库既是插件的能力核心,也是 dsh 原生插件包的发布源:Python 核心、MCP 服务器、 讲义 Skill,以及 dsh 用的 package.json / cordis.patch.yml / lib/ 都在这里。 npm 包名 dsh-zju-lecture-note;两种形态随包携带同一份 Python 实现, 工具名、参数、返回值完全一致,不会各自漂移。


这是什么

上游项目 nzw806/zju-lecture-summerizer- 用 Python 脚本 + 本地网页 + 写死的 DeepSeek key 完成"链接 → 讲义"。本插件保留它的抓取内核, 把模型调度交给宿主智能体(DeepSeek Harness / MiniMax Code / Claude Code / Cursor …), 并把整套流程封装成标准 MCP Server + Skill:

维度 上游(CLI + 本地 Web) 本插件(Agent 插件 / MCP)
交互 固定单次生成网页,无法追问 原生 Tool / Skill,可多轮单点答疑、生成自测题
模型 脚本内硬编码 DeepSeek API 宿主智能体直接感知材料,统一上下文与计费
生态 孤立工具 dsh 原生插件 / 标准 MCP,可与 Git、知识库、文档插件协同
依赖 常驻后台 Web 服务 按需 stdio 进程,零 pip 依赖
隐私 需要把 Cookie 交给脚本 Cookie 只在本地内存中解析;工具不回显、不落盘

复用与新增的具体边界见 ATTRIBUTION.md。

两种形态,一套内核

形态 载体 适合谁
dsh 原生插件 本仓库根目录的 npm 包(package.json + cordis.patch.yml + lib/),随包携带 Python 核心 DeepSeek Harness 用户
标准 MCP + Skill server.mjs 拉起一个 stdio MCP Server,附带 skills/ 里的讲义 SOP 任意 MCP 宿主(MiniMax Code、Claude Code、Cursor、Cline…)
  • dsh:默认注册 7 个工具(字幕主链路 + 登录辅助);在插件设置里打开 「启用 PPT 投影抓取」 后额外注册 zju_fetch_slides,共 8 个工具。
  • MCP:未设置 ZJU_ENABLE_SLIDES 时保持旧行为(8 个);设 0 关闭,仓库自带的 zju-lecture.mcp.json 默认就是 0(7 个)。

功能分层:字幕主链路 vs 可选投影抓取

v1.5.1 起,原本「仅依赖字幕、已实测跑通」的链路与「依赖投影 PPT 抓取」的链路彻底解耦:

分层 工具 依赖 默认
字幕主链路 zju_parse_url / zju_fetch_transcript / zju_extract_courseware / zju_check_note / zju_write_note 公开字幕接口 + 本地文件,无需登录态 始终开启
登录辅助 zju_get_video_url / zju_read_browser_cookie 本机 Edge 或 ZJU_COOKIE / ZJU_COOKIE_FILE 可用,但不会主动读取
投影抓取(可选) zju_fetch_slides 登录态 + 截图下载 + PDF 合成 关闭,需显式开启
  • dsh 插件设置项 enableSlides(默认 false)是投影模块的总开关;MCP 宿主用 ZJU_ENABLE_SLIDES(未设置时保持旧版行为,设为 0 即关闭)。
  • 关闭时:不注册 zju_fetch_slides、不加载 zju_lecture.slides 模块、不读浏览器 Cookie、 Skill 不追加投影附录;系统提示会明确告诉 Agent「投影抓取已关闭」。
  • 开启后才会追加 skills/zju-lecture-summarizer/SLIDES.md 的投影下载与插图规范, 讲义插图子开关 embedSlideImages(默认 false)才生效。

隐私与安全(先看)

  • Cookie 不是工具参数:zju_fetch_slides / zju_get_video_url 不接受 cookie 参数, zju_read_browser_cookie 也没有参数;登录态只能由本地配置(ZJU_COOKIE / ZJU_COOKIE_FILE) 或本机 Edge 提供,模型无法把它写进参数或从返回值里读出来。
  • Cookie 不回显:zju_read_browser_cookie 只返回名称、数量、长度与来源类别; 所有 stdout / stderr / 错误消息还会再经过一次 redactor,注册过的 Cookie 值统一替换为 [REDACTED]。
  • Cookie 不落盘:读 Edge 时优先只读打开数据库;必须复制时用 0600 私有临时文件,读完覆写并删除。 服务端缓存只存在于 Python 子进程内存,进程退出即消失。
  • TLS 不降级,也不靠运气:证书校验永远不会被关闭。信任库按 certifi → 随包 cacert.pem → 系统根证书 三级回落——dsh 自带的 Python 解释器不含 certifi,这份随包 Mozilla 根证书快照让"能不能抓字幕"不再取决于 用户碰巧 pip 装了什么。三级都不够时才失败,并把原因 + 下一步一起返回 (当前信任库、加载了几张 CA、HTTPS_PROXY 是什么、该装 certifi 还是把域名加进 NO_PROXY), 而不是丢一句 <urlopen error ...> 让人猜。
  • 瞬时故障有重试:建连与正文读取都在重试范围内(IncompleteRead / SSLEOFError / 超时 / 5xx, 共 3 次、指数退避 + 抖动);证书校验失败不重试——那需要修,不需要重跑。
  • 跨域重定向被拒绝:避免 urllib 在 302 时把 Cookie 头重发到第三方域。
  • 本地文件有边界:zju_extract_courseware / zju_check_note 只接受课件与讲义扩展名, 并拒绝 .env、凭据、私钥、浏览器 Cookie/Login Data 等敏感文件与目录;凭据词按分隔符整词匹配 (client_secret.json 仍拒绝,secret sharing 第5章.md 放行),通用文件名 config.json 不再误伤。
  • 不写用户目录:插件把随包 Skill 注册到 dsh 技能表,而不是复制进 $DSH_HOME;无遥测、无后台服务。
  • 可机器验证:python tools/privacy_scan.py 扫描工作区,--history 扫描 git 历史, --tarball 扫描 npm 发布产物。

完整威胁模型与已知边界见 SECURITY.md。

工具清单

工具 功能 鉴权 隐私处理
zju_parse_url 解析回放链接,取 sub_id / course_id / tenant_code 公开 纯字符串解析
zju_fetch_transcript 抓整节课或指定时间段的语音字幕,支持按分钟/逐句/分块/JSON 四种视图与字符上限 公开免登录 无 Cookie
zju_fetch_slides (可选模块,默认关闭) 抓取课堂投影 PPT 截图清单;可选下载截图并合成 PDF(接口不返回文字层/OCR);可开关是否把截图引用进讲义 需登录态(Cookie 需含 _token) 不收/不回显 Cookie
zju_extract_courseware 提取本地 PPTX / DOCX / TXT / MD / CSV / JSON / PDF 文本 本地文件 拒绝敏感路径
zju_check_note 检查讲义 Markdown 的公式规范与渲染(一键修复机械问题;报告按问题类型聚合) 本地文件 只接受 .md / .markdown
zju_write_note 写入讲义:文件名由 Agent 按「上课日期 + 本节内容」命名(如 知识总结-2026-10-28-复变函数-保角映射.md)、拒绝静默覆盖、写前修正 + 写后校验 本地文件 只写工作区内的 .md
zju_get_video_url 解析回放 MP4 直链(供下载或 Whisper 重识别) 需登录态(服务端本地解析) 直链可能带签名,勿外传
zju_read_browser_cookie 读取本机 Edge 登录态并缓存到服务端内存 本机读取 只返回名称与长度

智云投影 PPT 是截图,不是文字:search-ppt 返回的是投影画面截图(pptimgurl)。 zju_fetch_slides 默认只给截图清单与页数;传 download=true 时会把截图下载到 <工作区>/ppt-assets/<sub_id>/images/,并合成 PPT-<sub_id>.pdf(JPEG 直嵌、PNG 解码, 连续字节相同的截图折叠为一页)。只传纯 sub_id 时必须同时给 course_id; 鉴权使用 Cookie 父域 .zju.edu.cn 中的 _token(Authorization: Bearer)。

启用投影抓取与讲义插图(可选,默认关闭)

投影模块有两层开关,先开总开关,再决定要不要插图:

  1. enableSlides(dsh)/ ZJU_ENABLE_SLIDES(MCP)——总开关,默认关闭。 打开后才会注册 zju_fetch_slides,并加载截图下载/PDF 合成代码;关闭时整个投影模块 不存在,字幕主链路不受任何影响。
  2. embedSlideImages(dsh)/ ZJU_EMBED_SLIDES(MCP)——插图子开关,默认关闭。 仅在总开关打开时有意义;打开后 Agent 会把截图下载到 ppt-assets/<sub_id>/images/, 并从中挑 3–8 张关键页,以 Markdown 相对路径插进讲义对应小节。

总开关关闭(默认)时只走字幕,不下载截图、不读浏览器 Cookie、不追加投影 Skill 附录,成本最低。

  • dsh:打开 设置,在左侧导航选择 「浙大智云课堂讲义」(独立一栏,不占通用设置), 打开「启用 PPT 投影抓取」(enableSlides);需要插图再打开「把截图引用进讲义」 (embedSlideImages)。设置通过 dsh 的 configForms 写回 profile 配置,保存后插件会按 新配置重新加载。首次安装或更新后如果看不到这一栏,请完全重启 DeepSeek Harness (不是只刷新页面):dsh 在启动时缓存每个包的 dsh.client 元数据,新增的浏览器半边 只有在重启后才会进入 Client 模块图。

  • MCP / MiniMax Code:在 server env 中设 ZJU_ENABLE_SLIDES=1,需要插图时再加 ZJU_EMBED_SLIDES=1(仓库自带的 zju-lecture.mcp.json 两个都是 0):

    "env": { "ZJU_ENABLE_SLIDES": "1", "ZJU_EMBED_SLIDES": "1" }
    
  • CLI:python -m zju_lecture.cli slides <url> --download 只负责下载截图与 PDF; 是否把图片写进讲义由 Agent 按上面的开关决定。

开启后如果宿主模型不能查看本地图片,Agent 只会在讲义末尾列「课堂投影截图索引」(页码 + 相对路径), 不会编造截图内容。图片路径按讲义所在目录调整:讲义在工作区根目录用 ppt-assets/..., 在 notes/ 里用 ../ppt-assets/...。这些相对路径只在工作区内有效;要离线分享或换机器, 直接发 PPT-<sub_id>.pdf 更稳妥。

插件本身只负责下载、索引与相对路径,不提供 OCR;模型能否「看懂」截图取决于宿主是否支持本地图片输入。 不支持时,Agent 会退化为截图索引 + 字幕/课件总结,不会把截图当成已识别的文字。

安装

1. DeepSeek Harness(dsh)

dsh 插件是 pnpm 包:dsh plugin 会把参数原样转给 profile 目录里的 pnpm, 所以来源写法与 pnpm 一致(npm 包名 / github: / file:)。

# 从 GitHub 仓库(就是本仓库)—— 当前可用的安装方式
dsh plugin --profile desktop add github:chenqianyu486-sys/dsh-zju-lecture-note

# 从 npm registry(包发布到 npm 之后才可用,包名 dsh-zju-lecture-note)
# dsh plugin --profile desktop add dsh-zju-lecture-note

# 从本地路径(开发 / 验证)
dsh plugin --profile desktop add file:/absolute/path/to/zju-lecture-agent-plugin

本包目前还没发布到 npm registry(dsh-zju-lecture-note 在 npm 上查无此包), 请用上面的 github: 写法安装;等发布后再换回包名写法。

desktop profile 由 DeepSeek Harness 桌面端持有,管理插件时请使用 DeepSeek Harness 自带的 dsh 命令(resources/runtime/cli/bin/dsh.cmd), 而不是 npm 全局安装的 dsh;web / headless 等 profile 用哪个都可以。

不要只把 GitHub 的 zip 解压出来让 dsh 加载。 插件是 Node ESM 包, lib/index.js 里的 @deepseek-ai/* 全部是 peerDependencies(与 dshmarket 一致), 由 pnpm 在 dsh plugin add 时装进 profile 的 node_modules(当前是 @deepseek-ai/schemastery 3.18.x); 解压出来的目录本身没有 node_modules,启用时会报:

Cannot find package '@deepseek-ai/schemastery' imported from ...\lib\index.js

若一定要用本地目录,先给它补一个能被解析的 schemastery(其余 @deepseek-ai/dsh-* 仍由 dsh 运行时提供),再让 dsh 加载:

cd /absolute/path/to/zju-lecture-agent-plugin
npm install --no-save --no-package-lock @deepseek-ai/schemastery@^3.18.1
dsh plugin --profile desktop add file:/absolute/path/to/zju-lecture-agent-plugin

--no-save --no-package-lock 让这次安装只落进目录的 node_modules,不改 package.json (@deepseek-ai/dsh-* 是 dsh 提供的 peer,不要在这里重复安装)。

装完后在 dsh 里直接说:

帮我把这节课整理成讲义:https://classroom.zju.edu.cn/livingroom?course_id=58133&sub_id=123456

版本要求:dsh 会按 peerDependencies 中的 @deepseek-ai/dsh-* 范围校验运行时版本; 本插件声明 >=0.2.0-rc.2 <0.3.0-0,不匹配时需要显式的版本豁免。 Python 探测顺序:插件配置项 → 环境变量 ZJU_PYTHON → DeepSeek Harness 自带 Python → PATH 上的 python3 / python / py -3。 投影抓取开关:dsh 设置项 enableSlides(默认 false,投影模块总开关); MCP 宿主用环境变量 ZJU_ENABLE_SLIDES=1。需要把截图插进讲义时,再打开子开关 embedSlideImages / ZJU_EMBED_SLIDES=1。关闭时只走字幕,不注册投影工具、不读登录态。

插件市场(收录审核中):dsh 应用内市场读取 awesome-dsh-plugin 的条目列表。 本仓库的收录 PR #11087 目前仍是 open(held: author,等维护者复核描述);合并前在 market 里搜不到是预期行为, 合并后站点自动重建,届时可直接搜 zju-lecture。在那之前请用上面的 github: 命令安装。

登录态(可选,仅投影 PPT / 视频直链需要):优先自动读本机 Edge;Edge 正在运行时数据库被锁, 且 Edge 127+ 若启用了应用绑定加密(ABE),退出 Edge 也无法在本地解密其 v20 Cookie,这时会直接 提示改用 ZJU_COOKIE_FILE。可在宿主配置里设置环境变量,不要把 Cookie 粘贴到对话里:

ZJU_COOKIE="name=value; name2=value2"        # 直接给 Cookie 头
ZJU_COOKIE_FILE="/absolute/path/cookie.txt"  # 或从本机文件读(一行,权限建议 600)

智云接口用 Cookie 会话中的 _token 做 Authorization: Bearer 鉴权;这个会话 Cookie 通常设在父域 .zju.edu.cn。插件从 Edge 读课堂域 Cookie 时会一并读取该父域 Cookie;手工配置 ZJU_COOKIE / ZJU_COOKIE_FILE 时请确认里面包含它,否则 PPT 接口可能返回 401/403。

工作区与备份位置(可选):

ZJU_WORKSPACE="/absolute/path/to/workspace"  # 相对路径(如 知识总结.md)按它解析;dsh 插件会自动传入
ZJU_BACKUP_DIR="/absolute/path/to/backups"   # zju_check_note / zju_write_note 的备份目录,默认系统临时目录

相对路径的解析顺序是:绝对路径 → ZJU_WORKSPACE → PWD → 服务进程自己的 cwd;都找不到时, 错误信息会列出尝试过的绝对路径,而不是只回一句"找不到"。

2. 任意 MCP 宿主(MiniMax Code / Claude Code / Cursor …)

本仓库自带 stdio MCP Server,配置里只要一个 PATH 上的 node:

{
  "mcpServers": {
    "zju-lecture": {
      "type": "stdio",
      "command": "node",
      "args": ["/绝对路径/dsh-zju-lecture-note/server.mjs"],
      "timeout": 120000
    }
  }
}

server.mjs 会在运行时解析 Python 解释器(依次尝试 ZJU_PYTHON → python3 / python / py -3,要求 3.9+), 因此配置里只需要一个在 PATH 上必然存在的 node。需要登录态时,把 ZJU_COOKIE 或 ZJU_COOKIE_FILE 放进该服务器的 env(部分宿主不支持 env,可用系统环境变量)。

Claude Code 一行接入:

claude mcp add zju-lecture node /绝对路径/dsh-zju-lecture-note/server.mjs

或把本仓库作为 Claude Code 插件目录使用(清单见 adapters/claude_code/plugin.json)。

3. MiniMax Code(MCode)插件包

python tools/build_plugin.py --install          # 校验 + 安装到 ~/.minimax/plugins/zju-lecture
python tools/build_plugin.py --install --zip    # 同时产出 dist/zju-lecture-<version>.zip

安装后 MCode 的插件自动扫描会识别该包,启用后即可直接说:

帮我把这节课整理成讲义:https://classroom.zju.edu.cn/livingroom?course_id=58133&sub_id=123456

卸载:删除 ~/.minimax/plugins/zju-lecture 目录。

4. 命令行

python -m zju_lecture.cli parse "https://classroom.zju.edu.cn/livingroom?course_id=58133&sub_id=123456"
python -m zju_lecture.cli fetch 123456 --format min -o 字幕.txt
python -m zju_lecture.cli fetch 123456 --start 20 --end 45
python -m zju_lecture.cli slides "https://classroom.zju.edu.cn/livingroom?course_id=58133&sub_id=123456" --download
python -m zju_lecture.cli courseware ./课件.pptx
python -m zju_lecture.cli cookie            # 默认只显示名称与长度

CLI 的 cookie 子命令默认不回显明文;确实需要时加 --reveal(会进入终端历史)。 slides 子命令优先用 --cookie-file / ZJU_COOKIE;--cookie 仍可用,但会出现在进程列表里。 slides --download 输出到 <工作区>/ppt-assets/<sub_id>/(可用 --out-dir 改),并生成 PPT-<sub_id>.pdf;--no-dedup 关闭连续重复截图折叠。

目录结构

dsh-zju-lecture-note/
├── package.json                   # npm / dsh 插件包清单(包名 dsh-zju-lecture-note)
├── cordis.patch.yml               # dsh bundle patch(把插件插进 cordis)
├── lib/                           # dsh 插件实现(ESM)
│   ├── index.js                   # 插件入口:注册 8 个 dsh 工具 + 内存注册随包 Skill
│   ├── bridge.js                  # 拉起随包 Python 核心(JSON-RPC over stdio)
│   ├── tools.js                   # 生成物:dsh 工具定义
│   └── tools.schema.json          # 生成物:工具定义快照(供测试比对)
├── .minimax-plugin/plugin.json    # MiniMax Code(MCode)插件清单
├── zju-lecture.mcp.json           # MCP 服务器定义(stdio → node ./server.mjs)
├── server.mjs                     # 启动器:解析 Python 解释器并拉起 MCP 服务
├── mcp_server.py                  # MCP stdio 服务器(JSON-RPC 2.0,隐私边界在这里)
├── zju_lecture/                   # 核心库(纯标准库)
│   ├── client.py                  # 智云课堂 API 客户端(TLS 校验 + 同域重定向)
│   ├── cookies.py                 # Edge Cookie 读取与安全清理
│   ├── security.py                # Cookie 校验/redactor/本地文件边界
│   ├── courseware.py              # 课件文本提取(带路径校验)
│   ├── note_lint.py               # 讲义 Markdown 公式规范检查 / 自动修复
│   ├── formatter.py               # 时间戳 / 分钟聚合 / 分块
│   └── cli.py                     # 命令行入口
├── skills/zju-lecture-summarizer/SKILL.md         # 讲义生成 SOP(五段式模板 + 公式规范)
├── skills/zju-lecture-summarizer.manifest.json    # Skill 文件清单(sha256 审计)
├── SECURITY.md                    # 威胁模型 / Cookie 生命周期 / 已知边界
├── adapters/                      # 其它宿主的接入示例(不进入插件包)
├── tests/                         # 回归测试(含隐私扫描与插件形态测试)
├── tools/                         # 打包、schema 生成、隐私扫描、冒烟测试
├── icon.png / icon-dark.png       # 插件图标
├── LICENSE / ATTRIBUTION.md
└── README.md

隐私设计(工作原理)

Cookie 从哪里来、到哪里去

  1. 来源只有本地:内存缓存 → ZJU_COOKIE → ZJU_COOKIE_FILE → 本机 Edge;
  2. 校验:拒绝换行(HTTP 头注入)、空 name、超长;不合法只返回原因,不回显内容;
  3. 使用:只放进发往 classroom.zju.edu.cn 的请求头;跨域重定向拒绝;
  4. 缓存:仅 Python 子进程内存;
  5. 回显:没有;工具 schema 中没有 cookie / reveal / domain 参数;
  6. 日志:所有输出经过 SecretRedactor;
  7. 临时文件:只读打开优先;必须复制时连同 -wal / -shm 一起复制(刚登录的行可能还在 WAL 里), 权限 0600,读完覆写并删除。

本地文件

zju_extract_courseware 和 zju_check_note 只接受指定扩展名,并拒绝敏感文件名/目录 (.env*、*credential*、*.pem、Cookies、Login Data、.ssh、.dsh 等)。 凭据类单词按分隔符整词匹配(my-secret.txt 拒绝、secret sharing 第5章.md 放行), config.json 这类通用名不再被拒。符号链接会先解析真实路径再判定。 确有必要时可用 ZJU_ALLOW_SENSITIVE_PATHS=1 显式关闭保护。

数据会离开本机的部分

字幕与课件文本会作为工具结果进入宿主模型上下文,由你选择的模型提供方处理——这是使用大模型 整理讲义的固有代价。插件本身不收集、不额外上传、不写遥测。

讲义不进仓库

讲义是给自己复习用的本机学习材料,不是代码。开发者自己在这个仓库里跑插件时也一样, 默认就不会被提交:本仓库的 .gitignore 已经挡掉任意层级的 知识总结*.md 与 /notes/ (git check-ignore -v 知识总结-2026-10-28-复变函数-保角映射.md 可以自查)。

在你自己的项目仓库里用这个插件时,建议把这几行加进该仓库的 .gitignore:

# 智云课堂讲义(本机产物,不入库)
知识总结*.md
notes/

Agent 侧也写明了:不要 git add / 提交讲义,需要归档就把文件换个名字留在本地—— zju_write_note 的同名拒绝覆盖已经保证新讲义不会吃掉旧讲义。

讲义的公式规范

生成的讲义要能渲染,而不只是"公式写对了"。插件把这件事做成了两道闸门:

  1. 规范写进 Skill(skills/zju-lecture-summarizer/SKILL.md §4④)——行内 $...$ / 独立 $$...$$、 分式 \frac{分子}{分母}(分子分母都带花括号)、绝对值 \lvert ... \rvert(不写裸 |)、 虚数单位 \mathrm{i}、中文不塞进 \text{}、<details> 写成多行。
  2. 产出后自检——zju_check_note 静态扫描讲义,捕获"表格列被裸竖线截断""行内公式没闭合" "块级公式没独立成行""\frac 缺花括号""单行 <details>"这类会让整篇散架的问题。
python tools/check_note.py 讲义.md            # 只检查
python tools/check_note.py --fix 讲义.md       # 自动修正机械问题(单行 $$ 块、$$ 前后缺空行、
                                              # \dfrac、\frac 补花括号、表格裸竖线、单行 <details>)

自动修复只做语义无歧义的改写,且只作用于围栏代码块与行内代码之外(讲义里"这样写是错的"示例 不会被改坏);回写前会把原文件备份到备份目录(ZJU_BACKUP_DIR,默认系统临时目录下的 zju-lecture-backups/,报告里给出备份的绝对路径),再原子替换——备份不会再落在讲义同目录污染 git status。需要判断的(未闭合的 $、\text{} 里的中文、裸虚数单位)保持原样并继续报出来。

检查报告按问题类型聚合:59 处同类错误只打印一次 [错误] display_not_isolated × 59,附行号列表与 少量示例(max_samples 可调),长讲义不会再把工具返回刷成一屏。

开发与测试

python -m unittest discover -s tests -v     # 全部回归测试(含插件形态与隐私测试)
python tools/build_plugin.py --validate     # 按插件规范校验包结构
python tools/check_note.py 讲义.md           # 讲义渲染规范检查
python tools/privacy_scan.py                # 工作区隐私扫描(Cookie/密钥/个人路径/邮箱)
python tools/privacy_scan.py --history      # 连同 git 历史一起扫
python tools/export_schemas.py              # 改动 TOOLS 后重新生成适配器 schema
python tools/export_dsh_tools.py            # 改动 TOOLS 后重新生成 dsh 工具定义
python tools/build_skill_manifest.py        # 改动 Skill 后重新生成 sha256 清单
node tests/bridge_smoke.mjs                 # 端到端:拉起 Python 核心并调用一次工具
node tests/bridge_error_smoke.mjs           # 端到端:确认 MCP 的 isError 会冒泡成异常
node tests/bridge_workspace_smoke.mjs       # 端到端:相对路径按 session workspace 解析
python tools/refresh_ca_bundle.py --check   # 检查随包 CA 是否完整(≥50 张根证书)
python tools/smoke_test.py --sub-id <真实 sub_id>               # 对已安装包跑端到端冒烟(--dir 可指定包目录)

测试覆盖:URL 解析、时间戳格式化、分块时间区间回归、课件提取、工具 schema 健全性、 MCP 协议往返(initialize / tools/list / tools/call / 通知不响应)、 FIPS-197 AES-128 已知向量、Edge Cookie 失败原因分类、Cookie 主机精确匹配、Cookie 不回显与错误 redaction、 敏感本地文件拒绝、TLS 不降级与三级信任库回落、瞬时故障重试与证书失败不重试、isError 冒泡、 跨域重定向拒绝、讲义公式规范检查与自动修复、隐私扫描器自测、review 批次回归 (行内代码不被改写、修复前 .bak 备份、DOCX 段落保留、chunked/json 截断、切片时长、 Cookie WAL 回放、ABE 探测、PDF 报错带解释器路径、bridge 超时语义、系统提示适用范围)。

测试中的 subprocess / 管道 / 临时文件在受限沙箱里可能被拒绝;这是环境限制,不是插件缺陷。 在普通终端或给予足够的文件/进程权限后运行即可。

发布 dsh 插件包

本仓库根目录就是 npm 包(包名 dsh-zju-lecture-note),发布即发布这个仓库:

npm login                 # 首次发布前登录(交互式,需本人操作)
npm pack --dry-run        # 先看一眼会打进包里的文件清单
python tools/privacy_scan.py --tarball <生成的 .tgz>   # 发布前隐私闸门
npm publish               # 发布到 npm registry

files 字段是逐文件枚举的运行时清单(lib/、mcp_server.py、zju_lecture/*.py、skills/、 SECURITY.md……),不要改成整目录——否则 __pycache__/*.pyc 会混进包里 (files 的优先级高于 .npmignore)。

dsh 按 peerDependencies 校验运行时版本。若升级 dsh 后插件被跳过,请检查 @deepseek-ai/dsh-* 的 peer 范围,或按 dsh 提示使用 dsh plugin allow-version 做精确版本豁免。

1.5.1 字幕主链路与投影抓取解耦

  • 新增总开关:dsh 插件设置 enableSlides(默认 false)与 MCP 环境变量 ZJU_ENABLE_SLIDES。关闭时 zju_fetch_slides 不注册、不读登录态、不下载截图; 字幕主链路(zju_fetch_transcript 等)完全不受影响。MCP 宿主未设置该变量时保持旧行为 (开启),设 0 即关闭;仓库自带的 zju-lecture.mcp.json 默认 0。
  • 代码层解耦:zju_lecture/__init__.py 用 PEP 562 懒加载 download_slides / build_slides_pdf / SlideError;mcp_server.py 只在进入 zju_fetch_slides 分支时才导入 zju_lecture.slides。纯字幕进程的 sys.modules 里不会出现截图/PDF 模块。
  • 协议层解耦:tools/list 与 initialize.instructions 按开关动态输出;关闭时即使客户端 直接调用 zju_fetch_slides,也会得到可执行的「投影抓取未启用 + 如何开启」错误。
  • Skill 拆分:SKILL.md 只保留字幕主链路;投影下载与插图规范移到 skills/zju-lecture-summarizer/SLIDES.md,dsh 插件仅在 enableSlides=true 时追加到内存 Skill。 关闭时 Agent 不会看到投影工具的操作指南。
  • 两层次开关:enableSlides 是总开关,embedSlideImages 是「截图是否入讲义」子开关; 总开关关闭时子开关无效,避免出现「开了插图却抓不到投影」的误解。
  • 图形化设置:dsh 不会自动渲染插件 Config。新增浏览器半边 lib/client.js,通过 settings.section 在设置左侧导航注册独立的 「浙大智云课堂讲义」 一栏(不塞进通用设置); enableSlides / embedSlideImages 标记为 Schemastery .volatile(),通过共享 configForms 服务读写并带 revision 校验。package.json 增加 dsh.client (./client 导出、platform: web)。
  • 新增 tests/test_feature_decoupling_v151.py:覆盖环境变量、工具过滤、RPC 错误、 字幕链路在关闭状态下可用、纯字幕进程不加载 slides 模块、Skill/清单/MCP 配置一致性; 另含浏览器独立设置栏的注册冒烟(tests/client_settings_smoke.mjs)。

1.5.0 智云投影 PPT 读取与讲义插图

  • zju_fetch_slides 按真实接口修正:从 Cookie 父域 .zju.edu.cn 会话中解析 _token 并加 Authorization: Bearer;按 course_id + page/per_page 分页并按截图 URL 去重;正确解包 content.pptimgurl(以前把 JSON 字符串当文字输出)。
  • 新增 download / output_dir / dedup 参数与 CLI slides --download:截图存到 ppt-assets/<sub_id>/images/,用纯标准库合成 PPT-<sub_id>.pdf(JPEG DCTDecode、 PNG FlateDecode),连续字节完全相同的截图折叠为一页。
  • 明确「接口返回截图,不是文字层/OCR」:Skill 不再暗示能读取幻灯片文字;模型不能看图时只用字幕与本地课件, 需要幻灯片原文时让用户提供原始 .pptx 走 zju_extract_courseware。
  • 安全:截图只允许 https://*.zju.edu.cn;ZJU 的 http:// 自动升级 HTTPS;Cookie 只发给 ZJU 图片主机、 不发送 Bearer;跨主机重定向剥离凭据;写盘限制在工作区,最多 2000 张、单张 ≤32 MiB。
  • 新增 dsh 设置 embedSlideImages(默认 false)与 MCP 环境变量 ZJU_EMBED_SLIDES=1: 开启后 Agent 会下载投影截图,并挑 3–8 张关键页以 Markdown 相对路径插进讲义; 关闭时只走字幕,避免额外下载与视觉 token。
  • zju_fetch_slides:省略 download 时跟随上述开关;输出新增「讲义插图模式」、每页工作区相对路径 与可直接粘贴的 Markdown 引用示例;下载结果带 workspace_rel / note_example。
  • Skill 新增「投影截图入讲义」工作流:模型能看图时挑关键页内联,不能看图时只在文末列截图索引, 不臆造内容;图片路径按讲义所在目录调整。
  • 参考 8eoyw/zju-learning-cli 的公开实现思路(MIT), 详见 ATTRIBUTION.md;其基于 numpy/Pillow 的近似笔画去重未移植,保持零第三方依赖。
  • 完善致谢:补充 PeiPei233/zju-learning-assistant、8eoyw/zju-learning-cli 及其上游参考项目的逐项说明。
  • dsh 单独把 zju_fetch_slides 的超时提高到 600s(下载整节课截图用),其他工具仍用配置值。
  • 新增 tests/test_slides_v150.py,覆盖 token / 分页 / 父域 Cookie / 截图下载 / PDF / 工作区边界 / 插图开关与相对路径引用。

1.4.2 修正

  • @deepseek-ai/schemastery 从 dependencies 改为 peerDependencies(^3.18.1), 与插件市场的推荐写法、dshmarket 保持一致:profile 里不会出现第二份运行时, peer 由 pnpm 在 dsh plugin add 时自动装好;本地目录安装只需补这一颗依赖。
  • 文档口径统一到 8 个工具:zju_write_note 在 1.4.1 加入后,README.md、ATTRIBUTION.md 与 lib/index.js 的注释还写着 7。

1.4.1 修复了哪些问题

针对 实地运行反馈 的 8 项问题(真实一节课 sub_id=1988057 跑下来):

# 修复
P1-1 相对路径按会话工作区解析(ZJU_WORKSPACE → PWD → 进程 cwd);找不到时回显已尝试的绝对路径。dsh 插件把 session workspace 传给 Python 子进程(同时作为子进程 cwd)。
P1-2 fix=true 新增块级 $$ 前后补空行修复器:区分开块/闭块(不往公式体里插空行)、跳过代码块、保留列表缩进。
P2-1 检查报告按 code 聚合:[错误] display_not_isolated × 59 + 有界行号列表 + 少量示例,新增 max_samples(默认 3)。
P2-2 PPT / 视频直链失败分支改为单一结论 + 单一动作(reason 枚举 abe / locked / no_rows …),去掉错乱的编号与"退出 Edge 后重试;但 ABE 退出也无效"的自相矛盾。
P2-3 新增 zju_write_note:文件名由 Agent 按「上课日期 + 本节内容」起名(name 参数,如 知识总结-2026-10-28-复变函数-保角映射.md),缺省才回退 知识总结-<sub_id>-<课程简称>.md;同名拒绝静默覆盖(返回 exists,要覆盖须显式 overwrite=true)。
P3-1 备份改写到备份目录(ZJU_BACKUP_DIR,默认系统临时目录),报告给出绝对路径;不再污染 git status。
P3-2 Skill 清单新增 contentHash;插件启动告警打印两边的 sha256;Skill 增加「版本与副本核对」章节;新增防回归测试禁止"让用户提供 cookie"这类过期文案。
P3-3 字幕返回 ASR 可靠度(噪声比 / 碎句比 / 平均句长 / 高频口头禅 + 处置建议),chunked / json 视图给 quality 对象;Skill 要求可靠度低时复算校核并在讲义顶部写明复核点。

1.4.0 修复了哪些问题

针对 issue #1 的 11 项审查:

# 修复
1 fix=true 不再改写行内代码/围栏代码里的示例(讲义里"这样写是错的"不会被改坏)
2 回写前先备份 <文件名>.bak,再原子替换;工具卡类型从 read 改为 edit(1.4.1 起备份写到 ZJU_BACKUP_DIR,默认系统临时目录)
3 DOCX 按 token 流解析,恢复段落、制表符、<w:br/> 与 XML 实体
4 chunked / json 遵守 max_chars,超限时给 truncated / next_start_minute
5 切片时同时报告"课程总时长"和"本次区间",不再把片段时长当整课
6 检测 app_bound_encrypted_key(ABE),命中时直接引导 ZJU_COOKIE_FILE
7 Cookie 兜底复制带上 -wal / -shm 并回放 WAL,不再误报"未登录"
8 config.json 移出敏感名单;凭据词改整词匹配,学术文件名不再误伤
9 PDF 解析失败时汇总每种库的失败原因,并给出当前解释器的绝对路径
10 isError 已能冒泡(1.3.3);超时仅在无其他在途调用时才回收 Python 子进程
11 系统提示段落声明适用范围,只对智云课堂相关任务生效

已知限制

  • 投影抓取默认关闭:dsh 的 enableSlides 默认 false,所以升级后默认看不到 zju_fetch_slides;需要投影时在插件设置里打开,或给 MCP server 设 ZJU_ENABLE_SLIDES=1(仓库自带的 zju-lecture.mcp.json 保持 0)。字幕主链路不受影响。
  • 只解压 zip 就启用 → Cannot find package '@deepseek-ai/schemastery': 它是 peer 依赖,由 pnpm 在 dsh plugin add 时装进 profile 的 node_modules, 解压出来的目录里没有,所以直接加载会失败。 用 dsh plugin --profile desktop add github:chenqianyu486-sys/dsh-zju-lecture-note 安装, 或在本地目录先 npm install --no-save --no-package-lock @deepseek-ai/schemastery@^3.18.1 再 dsh plugin ... add file:<路径>。 插件现在会把这类错误翻译成「缺少依赖 + 该怎么装」,不再只抛裸的模块找不到。
  • Edge 登录态的两条死路:Chromium 会对 Cookie 数据库加独占锁;Edge 127+ 若启用应用绑定加密(ABE), v20 Cookie 连 DPAPI 也解不开。插件会读 Local State 里的 app_bound_encrypted_key 判定 ABE, 并在错误信息里直接指向 ZJU_COOKIE_FILE(而不是反复让你退出 Edge);读库兜底复制会带上 -wal / -shm 并让 SQLite 回放 WAL,避免"刚登录却被判未登录"。
  • PDF 课件需要可选依赖 pypdf(pip install pypdf);其余格式零依赖。
  • SSL / 代理:信任库按 certifi → 随包 cacert.pem → 系统根证书 回落,所以 dsh 自带的无 certifi 解释器也能正常握手;真正会失败的是代理做了 HTTPS 解密或系统时间偏差过大。错误消息会给出当前 信任库、CA 张数、代理环境变量与具体修法(把 classroom.zju.edu.cn 设为直连或放进 NO_PROXY)。 插件不会为了"能跑"而关闭证书校验。瞬时抖动(截断读包、握手 EOF、超时、5xx)已内置 3 次退避重试。
  • 同名 Skill 的磁盘副本会抢名字:若 $DSH_HOME/skills/zju-lecture-summarizer/ 下还留着一份旧副本, 一个名字只保留一份、重复的仅打 warning,Agent 可能一直读那份过期 SOP。SKILL.md 的 front matter 里 没有版本字段,所以只能比内容:清单里的 contentHash(sha256:...)是权威值,插件启动时会在 stderr 打印插件内置版与磁盘副本的 sha256,并给出删除路径。
  • 讲义文件名不再固定:1.4.1 起用 zju_write_note,文件名由 Agent 按「上课日期 + 本节内容」起名 (知识总结-2026-10-28-复变函数-保角映射.md),缺省才回退 知识总结-<sub_id>-<课程简称>.md。 固定的 知识总结.md 会让下一节课静默覆盖上一节,而讲义通常被 .gitignore 忽略、覆盖后无法恢复。
  • git 历史:历史中已有的个人路径/密钥不会因为当前文件干净而消失; 用 python tools/privacy_scan.py --history 检查,必要时重写历史并轮换凭证。
  • 形态别选错:tools/build_plugin.py 产出的是 MCode 插件包;装进 dsh 用的是本仓库根目录的 npm 包(dsh plugin ... add)。两者是两套安装方式,不要混。
  • 字幕接口公开,但请遵守平台条款:一次一节课,不要批量抓取。

许可与致谢

本插件以 MIT 许可证发布,改编自 nzw806/zju-lecture-summerizer-(MIT), 原项目的版权声明保留在 LICENSE。

特别感谢这些项目与同学:

逐文件来源、第三方许可证与「参考但未拷贝」的边界见 ATTRIBUTION.md。

仅供个人学习使用:只抓取本人有权限访问的回放,Cookie 属个人凭证,不要外传。

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.