最近用 DeepSeek Harness 写技术文档时,我发现了一个反直觉的事实:最耗时间的不是和模型对话,而是被三件琐碎小事反复打断——在输入框里重复粘贴常用提示词、在会话导出文件和项目目录之间来回切换、面对一篇没有目录的上千行 Markdown 长文靠手动滚动查找位置。
这些操作的共同点是:频率高、机械重复、几乎不需要动脑。而 Harness 最值得花时间研究的地方,恰恰就是把这些高频操作变成一次点击的插件机制。
这篇文章记录我给 DeepSeek Harness 写的 3 个实用插件:对话框快捷模板、文件管理归档、Markdown 目录展示。它们都不复杂,但正好覆盖了日常使用 Harness 时最容易被忽视的三个效率盲区。读完你可以直接复现这三个插件,也可以理解 Harness 插件设计的基本套路,然后按自己的需求去扩展。
1. 为什么要给 DeepSeek Harness 开发插件
先回答一个更基础的问题:Harness 本身已经有对话、文件上传和 Markdown 渲染能力,为什么还要写插件?
如果你只是偶尔用 Harness 问几个问题,确实不需要插件。但当你把它当作日常工作的主工具,比如写技术方案、整理会议纪要、管理一批文档时,你会很快遇到三个明显的边界:
第一,对话框没有“个人习惯记忆”。 模型不知道你写代码评审时喜欢用哪套标准,也不知道你生成周报时固定包含哪几个小节。每次都要重新把需求描述一遍,这部分重复劳动完全可以由模板插件接管。
第二,文件管理停留在手动层面。 Harness 可以读取文件、生成文件,但“把某次会话产生的文档归档到指定目录”这类动作没有内置的批处理能力。频繁导出、手动拖拽、跨目录移动,正是文件管理插件的用武之地。
第三,长文档的可读性被低估。 在 Harness 里打开一篇长篇 Markdown,如果文档结构复杂、包含大量代码块和表格,没有目录导航时定位章节非常低效。Markdown 目录展示插件能自动抽取标题,生成可跳转的目录,本质上是一个极简的文档导航工具。
这三件事如果每次都手动做,成本看似不高,但累积起来非常可观。插件化的真正价值不是“增加一个功能开关”,而是把高频操作封装成稳定的动作,让你把有限的注意力放在真正需要判断的事情上。
从技术实现角度看,Harness 插件并不要求你精通前端或熟悉复杂框架。多数实用的插件本质上是一个“配置声明 + 一段脚本逻辑”:配置决定它何时出现、如何触发,脚本决定它执行什么操作。这篇文章的三个插件都遵循这个模式。
什么样的读者最应该看完这篇文章?如果你已经在用 Harness 处理日常文档工作,或者你正准备把 Harness 纳入团队的知识管理流程,又或者你想了解桌面端 AI 工具的插件机制到底能玩出什么花样,这篇文章会给你一套可以直接落地的方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先理解 Harness 插件机制的核心概念
在写插件之前,需要建立三个基本概念:扩展点、插件清单、脚本入口。
2.1 扩展点:插件能“挂”在哪些位置
Harness 的插件体系可以理解为三个层面的扩展:
| 扩展层面 | 对应的界面或能力 | 典型用途 |
|---|---|---|
| 对话扩展层 | 对话框、快捷指令、上下文菜单 | 提示词模板、常用问题、命令快捷输入 |
| 工具扩展层 | 文件操作、外部命令、工作区动作 | 文件归档、批量重命名、目录结构管理 |
| 渲染扩展层 | Markdown 预览、文档后处理 | 目录生成、标题抽取、格式规整 |
这三个层面恰好对应我写的三个插件:对话框快捷模板插件挂在对话扩展层,文件管理归档插件挂在工具扩展层,Markdown 目录展示插件挂在渲染扩展层。
理解这个分层很重要,因为它决定了插件的入口方式和权限边界。比如对话框插件需要考虑光标位置和输入框状态,文件管理插件必须考虑路径安全和文件覆盖风险,而 Markdown 目录插件只需要关心文本解析逻辑。
2.2 插件清单:告诉 Harness 这个插件是谁、能做什么
Harness 的插件通常包含一个清单文件,用来声明插件名称、版本、入口脚本、触发位置和参数定义。把它理解成插件的“身份证”会更直观。
一个最小化的清单文件长这样:
json复制{
"name": "example-plugin",
"version": "1.0.0",
"entry": "src/index.js",
"triggers": ["composer", "context-menu"],
"description": "这是一个示例插件"
}
不同版本的 Harness 对清单字段的支持会有差异,但“声明入口脚本 + 声明触发位置”是这个机制的核心思路。你不需要在一开始就把所有字段都记住,只要知道每个插件最终都会落到一段可执行的脚本上就够了。
2.3 脚本入口:插件真正干活的地方
插件清单负责注册,脚本负责执行。Harness 插件最常见的实现方式是使用 JavaScript/Node.js 脚本,因为 Harness 本身对脚本生态的兼容性较好,而且 Node 自带文件系统能力,适合处理文件管理、文本解析这类任务。
我自己写插件时遵循一个原则:核心逻辑尽量写成独立脚本,不依赖 Harness 内部私有 API。这样做的好处是,即使 Harness 后续调整了界面或触发方式,脚本本身依然可以单独运行和测试,迁移成本很低。
这个设计思路会在后面的三个插件中反复体现。每个插件都由“配置 + 独立脚本”组成,先保证脚本本身能跑通,再考虑接入 Harness 界面。
3. 环境准备与插件最小骨架
开始写插件之前,先准备好环境。以下步骤以通用实践为准,具体版本号请以你实际安装的 Harness 版本为准。
3.1 基础环境
- 操作系统:Windows 10/11、macOS 或主流 Linux 发行版均可。
- 运行时:Node.js 14 或更高版本,因为插件脚本依赖
fs、path等内置模块。 - Harness 客户端:已安装可用的 DeepSeek Harness,并且能正常进入主界面。
- 编辑器:VS Code 或其他支持 JavaScript 的编辑器,用于编写和调试脚本。
如果之后插入代码并使用 Harness 场景,建议在一台非生产、非核心的测试机上先完成验证;如果你想用插件管理重要目录里的文件,必须先在带备份的测试目录中跑通,再切换到真实工作目录。
3.2 插件目录结构
我习惯把插件放到一个统一目录下,方便管理和备份。下面是一个推荐的插件项目结构:
text复制dsh-plugins/
├── manifest.json
├── tools/
│ ├── insert_template.js
│ ├── archive_files.js
│ └── generate_toc.js
├── templates/
│ └── code-review.json
└── README.md
tools 目录放插件脚本,templates 目录放对话框模板,manifest.json 统一声明这些脚本的触发位置。README 记录插件的用途、参数和注意事项,方便团队协作。
3.3 最小插件清单示例
先把一个基础版 manifest.json 写出来:
json复制{
"name": "dsh-productivity-pack",
"version": "1.0.0",
"description": "对话框模板、文件归档、Markdown 目录三个实用插件",
"tools": [
{
"name": "insert-template",
"entry": "tools/insert_template.js",
"trigger": "composer",
"params": ["templateName"]
},
{
"name": "archive-files",
"entry": "tools/archive_files.js",
"trigger": "context-menu",
"params": ["sourceDir", "archiveDir"]
},
{
"name": "generate-toc",
"entry": "tools/generate_toc.js",
"trigger": "markdown-view",
"params": ["filePath"]
}
]
}
这份清单声明了三个工具的入口脚本和触发位置:insert-template 挂在对话框区,archive-files 在上下文菜单中触发,generate-toc 用于 Markdown 预览。具体的字段名称在不同 Harness 版本中可能不同,但“声明入口 + 声明触发位置”的思想是通用的。
如果你在界面里找不到插件加载入口,优先查看 Harness 的设置页或插件市场;如果 Harness 支持手动指定插件目录,直接把 dsh-plugins 这个文件夹挂载上去即可。
4. 插件一:对话框快捷模板插件
4.1 解决什么问题
使用 Harness 对话时,以下场景非常高频:
- 让模型做代码评审,但每次都要重新说明“请从可读性、安全性、性能三个维度评审”。
- 生成技术周报,但每周都在重复描述周报结构。
- 把一段代码交给模型学习,但希望它按特定格式返回分析结果。
如果没有模板插件,每次都要手动输入这一大段引导语。时间一长就会发现,真正值钱的是模型给的结果,而不是你重复输入的那段指令。
对话框快捷模板插件做的事情很简单:在 templates 目录下维护一组 JSON 模板文件,通过一条命令按名称提取模板内容,然后插入到对话框输入框供你发送。
4.2 模板文件设计
以代码评审模板为例,先在 templates/code-review.json 中写入:
json复制{
"name": "code-review",
"description": "让模型以代码评审视角分析指定代码",
"content": "请从可读性、安全性、性能三个维度评审以下代码,并给出具体修改建议:\n\n```\n${code}\n```"
}
模板内容中可以使用 ${code} 这类占位符,实际使用时手动替换成目标代码即可。虽然这只是一个简单的字符串替换,但对比从零开始输入,它至少解决了两件事:一是保证了提示词的质量稳定,不会漏掉某个评审维度;二是把输入成本从“思考如何描述”降为“复制代码并替换占位符”。
如果你需要更多模板,比如“Bug 修复报告”“方案对比分析”“SQL 优化建议”,只需要在 templates 目录下增加对应的 JSON 文件,无需修改脚本本身。
4.3 脚本实现
创建 tools/insert_template.js:
js复制const fs = require('fs');
const path = require('path');
const templateDir = path.join(__dirname, '..', 'templates');
const templateName = process.argv[2];
if (!templateName) {
const files = fs.readdirSync(templateDir).filter((f) => f.endsWith('.json'));
console.log('可用模板:');
files.forEach((f) => console.log(' - ' + path.basename(f, '.json')));
process.exit(0);
}
const templateFile = path.join(templateDir, `${templateName}.json`);
if (!fs.existsSync(templateFile)) {
console.error(`模板不存在: ${templateName}`);
process.exit(1);
}
const template = JSON.parse(fs.readFileSync(templateFile, 'utf8'));
console.log(template.content);
这段脚本的逻辑很直接:没有传模板名时列出所有可用模板;传了模板名时读取对应 JSON 并输出 content 字段。脚本本身不依赖 Harness 的私有能力,随时可以用 Node 单独运行。
4.4 运行与验证
在命令行验证:
bash复制node tools/insert_template.js
预期输出:
text复制可用模板:
- code-review
再执行:
bash复制node tools/insert_template.js code-review
预期输出模板内容,且以 请从可读性、安全性、性能三个维度评审以下代码 开头。
脚本跑通后,接入 Harness 的方式是在对话框的快捷命令区域把它注册为一条命令,绑定参数为模板名。之后在 Harness 里输入这条命令,就能快速把模板内容填充到对话框。这里的关键验证点是:脚本能独立运行,说明模板逻辑正确;接入界面后,只是把“命令行输出”换成“插入输入框”,问题基本都会出在触发配置上,而不是脚本本身。
5. 插件二:文件管理归档插件
5.1 解决什么问题
Harness 在使用过程中会产出不少文件:导出的 Markdown、生成的报告、保存的代码片段。如果工作区没有整理,很快会变成一个堆满 final_v2.md、output(1).txt 的混乱目录。
文件管理插件解决的是“按规则归档”的问题。它做的事情是:从一个源目录读取符合条件的文件,移动到目标归档目录,并自动跳过已存在文件和不在白名单内的扩展名。
这里要特别强调安全边界:这个插件只移动文件,不删除任何文件。 即使目标目录已有同名文件,也会自动跳过,而不是覆盖。
5.2 脚本实现
创建 tools/archive_files.js:
js复制const fs = require('fs');
const path = require('path');
const ALLOWED_EXTENSIONS = new Set([
'.md', '.txt', '.json', '.csv',
'.png', '.jpg', '.pdf'
]);
function safeArchive(sourceDir, archiveDir) {
if (!fs.existsSync(sourceDir)) {
throw new Error(`源目录不存在: ${sourceDir}`);
}
if (!fs.existsSync(archiveDir)) {
fs.mkdirSync(archiveDir, { recursive: true });
}
const items = fs.readdirSync(sourceDir);
let moved = 0;
for (const item of items) {
const sourcePath = path.join(sourceDir, item);
const stat = fs.statSync(sourcePath);
if (!stat.isFile()) continue;
const ext = path.extname(item).toLowerCase();
if (!ALLOWED_EXTENSIONS.has(ext)) {
console.log(`跳过不支持的文件类型: ${item}`);
continue;
}
const targetPath = path.join(archiveDir, item);
if (fs.existsSync(targetPath)) {
console.log(`跳过已存在文件: ${item}`);
continue;
}
fs.renameSync(sourcePath, targetPath);
moved++;
}
console.log(`归档完成,共移动 ${moved} 个文件`);
}
const sourceDir = process.argv[2];
const archiveDir = process.argv[3];
if (!sourceDir || !archiveDir) {
console.error('用法: node archive_files.js <源目录> <归档目录>');
process.exit(1);
}
safeArchive(sourceDir, archiveDir);
几个关键设计点:
- 扩展名白名单只放行明确的文档和图片格式,避免把临时文件、可执行文件混入归档目录。
existsSync判断保证同名文件不会被覆盖。- 只移动不删除保证任何情况下数据都不会丢失,即使移动目标错误,也能从原目录找回。
5.3 运行与验证
先准备测试目录:
bash复制mkdir -p /tmp/dsh-test/source /tmp/dsh-test/archive
echo "# test" > /tmp/dsh-test/source/note.md
echo "data" > /tmp/dsh-test/source/temp.tmp
执行:
bash复制node tools/archive_files.js /tmp/dsh-test/source /tmp/dsh-test/archive
预期输出:
text复制跳过不支持的文件类型: temp.tmp
归档完成,共移动 1 个文件
检查归档目录:
bash复制ls /tmp/dsh-test/archive
应看到 note.md,而 temp.tmp 仍保留在源目录。这一步验证通过后,再在 Harness 的工作区中把源目录和归档目录作为参数传入。
如果 renameSync 报 EXDEV 错误,说明源目录和目标目录不在同一个磁盘分区,此时需要把 renameSync 改成“先复制,再确认,最后删除源文件”的流程。在生产实践中,跨盘移动必须用复制加校验的方式,避免移动过程中数据不完整。
6. 插件三:Markdown 目录展示插件
6.1 解决什么问题
Harness 的 Markdown 预览面对长文档时,缺少一个关键能力:目录导航。一篇几千行的技术方案,如果没有目录,读起来非常吃力。你可能有这样的经历:想跳到“性能优化”那一节,只能不断滚动,眼睛扫过一片又一片标题,效率极低。
Markdown 目录展示插件做的事情是:解析当前 Markdown 文件中的标题,生成一个带层级缩进的目录列表,展示在预览区域旁边,并且每个目录项都能跳转到对应位置。
6.2 实现要点:必须忽略代码块
写 Markdown 目录解析最容易踩的坑就是:把代码块里的 # 注释也当成标题。比如下面这段内容,如果直接按 # 开头来匹配,就会错误地生成一个“这不是标题”的目录项:
markdown复制## 配置说明
```bash
# 这不是标题,是注释
echo "hello"
```
因此 generate_toc.js 必须先识别代码块围栏,跳过围栏内的所有行,再对剩余行做标题匹配。
6.3 脚本实现
创建 tools/generate_toc.js:
js复制const fs = require('fs');
const path = require('path');
function stripCodeBlocks(lines) {
let inCode = false;
return lines.filter((line) => {
if (line.trim().startsWith('```')) {
inCode = !inCode;
return false;
}
return !inCode;
});
}
function slugify(title) {
return title
.toLowerCase()
.replace(/[^\w\u4e00-\u9fa5]+/g, '-')
.replace(/^-+|-+$/g, '');
}
function generateToc(markdownText) {
const lines = markdownText.split('\n');
const visibleLines = stripCodeBlocks(lines);
const toc = [];
for (const line of visibleLines) {
const match = /^(#{1,6})\s+(.*)$/.exec(line);
if (!match) continue;
const level = match[1].length;
if (level < 2 || level > 4) continue;
const title = match[2].trim();
const indent = ' '.repeat(level - 2);
toc.push(`${indent}- [${title}](#${slugify(title)})`);
}
return toc.join('\n');
}
const input = process.argv[2];
if (!input) {
console.error('用法: node generate_toc.js <markdown文件路径>');
process.exit(1);
}
const text = fs.readFileSync(input, 'utf8');
console.log(generateToc(text));
这个脚本允许自动提取二级到四级标题,忽略一级标题和五级、六级标题。这样的设计是因为在实际文档中,一级标题通常是文档总标题,放目录里意义不大;五级、六级标题太深,放到目录里会显得琐碎。如果你希望调整层级范围,修改 level < 2 || level > 4 这一行的数字即可。
6.4 运行与验证
准备一个测试文件:
markdown复制# 系统设计文档
## 架构总览
### 模块划分
## 性能优化
### 缓存策略
```bash
# 这不是标题,是注释
redis-cli ping
code复制
执行:
```bash
node tools/generate_toc.js /tmp/design.md
预期输出:
text复制- [架构总览](#架构总览)
- [模块划分](#模块划分)
- [性能优化](#性能优化)
- [缓存策略](#缓存策略)
注意输出中不包含 redis-cli 上方的那条注释“这不是标题,是注释”,说明代码块过滤逻辑生效。
接入 Harness 后,这个插件可以在 Markdown 预览区域触发,把生成的目录渲染在侧边。你甚至可以进一步把它做成“插入到文章开头”,这样导出的 Markdown 本身就自带导航目录。
7. 运行结果与效果验证
三个插件写完后,完整的验证流程可以总结为三步:
第一步:独立验证脚本。 每个脚本先用 Node 单独运行,准备好测试数据和预期输出。这一步能过滤掉大部分语法错误和逻辑问题。
第二步:接入 Harness 后验证触发逻辑。 在 Harness 里触发插件命令,看是否能在指定位置(对话框、上下文菜单、Markdown 预览)看到入口。如果入口不出现,优先检查 manifest.json 的 trigger 字段是否与当前界面环境匹配。
第三步:端到端验证。 用一个真实的文档工作流跑一遍:在对话中插入模板 → 生成文件 → 归档文件 → 在 Markdown 预览中生成目录。每一步都检查输出是否符合预期。
如果失败,按以下顺序排查:
| 现象 | 第一步检查 |
|---|---|
| 插件命令无法触发 | 看 Harness 插件设置是否加载了正确目录 |
| 脚本报“模板不存在” | 确认模板文件名与传入参数完全一致 |
| 文件没有归档成功 | 看源目录是否存在、文件扩展名是否在白名单内 |
| 目录丢失某个标题 | 看该标题是否在代码块内或层级超出 2-4 级 |
真实项目中,插件出问题的位置往往不是脚本本身,而是“参数传递错误”和“路径不符合预期”这两类。所以我的建议是让脚本先支持命令行参数运行,通过命令行把问题暴露出来,再接入界面。
8. 常见问题与排查思路
在使用这三个插件的实际过程中,你可能会遇到下面这些问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 插件列表里看不到插件 | manifest.json 目录未加载 |
在 Harness 设置中确认插件目录路径 | 重新加载插件目录或重启 Harness |
| 脚本执行报“找不到模块” | 当前工作目录不是插件目录 | 打印 process.cwd() 确认 |
在脚本中使用基于 __dirname 的绝对路径 |
| 中文文件名乱码 | 控制台编码不符合 UTF-8 | 检查命令行终端编码设置 | Windows 下执行 chcp 65001 切换为 UTF-8 |
| 模板插入后占位符未被替换 | 插件只做了原样输出 | 检查模板内容中的 ${code} 拼写 |
在模板插入前执行字符串替换逻辑 |
| 跨盘移动文件报 EXDEV | 源目录和目标目录不在同一磁盘 | 查看报错码和目录挂载点 | 改为“复制到目标 → 比对大小 → 删除源文件”三步流程 |
| 目录生成后无法跳转 | 跳转锚点与预览渲染器规则不一致 | 对比生成的 #锚点 与渲染后标题 ID |
根据 Harness 渲染规则调整 slugify 方法 |
这些问题的共性是:先确认脚本本身能不能跑,再怀疑 Harness 接入配置。千万不要一上来就改 Harness 配置或重装插件,那会浪费大量时间。
9. 最佳实践与安全建议
在把这三个插件从“自己用”推向“团队用”的时候,有几个工程层面的建议值得记住。
9.1 插件命名与目录管理
插件名称使用 短横线命名法,比如 dsh-productivity-pack,避免出现空格和特殊字符。一个插件项目对应一个目录,目录内同时包含 manifest.json 和 tools。如果你有多个插件,建议统一放在 dsh-plugins 父目录下,用子目录区分,而不是把多个插件的脚本混在一个目录里。
9.2 最小权限原则
文件管理类插件永远遵循最小权限原则:只处理你明确传入的目录,不递归扫描全盘;只移动白名单内的扩展名;不提供删除能力。插件需要的权限应该尽量收敛,避免脚本因为一个错误参数而影响整个工作区。
9.3 路径安全与参数校验
任何接受路径参数的脚本,第一步都必须是校验参数存在性。源目录不存在时立即报错,而不是默认创建一个空目录继续执行。目标目录存在同名文件时跳过,而不是覆盖。这些校验虽然简单,却能避免最危险的数据事故。
9.4 测试环境先行
所有插件脚本先在临时目录中跑通,再接入真实工作目录。尤其是文件管理插件,先在 /tmp/dsh-test 这类目录中验证归档逻辑,确认行为符合预期后,再切换到生产目录。涉及生产环境或重要数据时,必须先备份、再操作。
9.5 日志与可观测性
脚本执行时打印清晰的过程信息:“跳过不支持的文件类型”“跳过已存在文件”“归档完成,共移动 N 个文件”。这些输出不只是给人看的,也是排查问题的第一手线索。如果集成到 Harness 后界面没有回显,建议把脚本输出追加写入一个日志文件。
9.6 可回滚与幂等设计
好的插件操作应该是可回滚的。文件归档插件虽然只移动文件,但移动操作本身需要保证可追踪。更严谨的做法是:在移动前输出一份“待移动文件清单”,确认后再执行;执行后生成一份“归档记录”,记录源路径、目标路径和时间。这样即使误操作,也能根据记录快速还原。
10. 总结与后续方向
这三个插件看起来都很简单,但它们共同指向一个更重要的能力:把 DeepSeek Harness 从一个“对话窗口”改造成“工作台”。对话框模板解决了重复输入的问题,文件管理归档解决了文档混乱的问题,Markdown 目录展示解决了长文阅读的问题。单个插件解决的都是很小的痛点,但组合在一起,日常使用的流畅度会有明显提升。
如果你打算继续深入,有几个方向值得探索:
- 给模板插件增加动态参数替换,让 Harness 自动把当前选中的代码填入
${code},而不是手动粘贴。 - 给文件归档插件增加“按日期分目录”的归档规则,比如
archive/2026-05/、archive/2026-06/。 - 把 Markdown 目录插件扩展为“文档结构分析器”,不仅生成目录,还能统计各章节字数、识别未完成章节。
插件开发的核心不是写多复杂的代码,而是识别出你的工作流里哪些动作不值得重复。发现一个高频手动操作,就把它封装成一个插件,这个习惯比任何现成的插件列表都更值钱。建议把这篇文章提到的三个插件先收藏备用,然后从你自己的使用习惯出发,找到第一个值得自动化的动作。
