dsh-postman
一个 dsh(DeepSeek Harness) 插件: 给 AI 一套访问线上 Postman 的工具——查询、新增(单个或批量)、修改、删除接口,导出导入集合。
- 9 个模型可见工具:
postman_list/postman_tree/postman_add/postman_add_many/postman_edit/postman_rm/postman_pull/postman_push/postman_config /postman-skill技能:像/browser-skill一样,一句话把整套用法注入当前会话- 配置在 dsh 的「设置 → Postman」:自带客户端半边注册设置分区,
apiKey标了role('secret'),明文不会回传浏览器 - 只管 Postman,不管源码:插件里没有任何语言、框架或路由约定的知识。
要让 AI 按代码批量加接口,就让它自己读代码(Java / Python / C# / 随便什么),
整理成一份清单交给
postman_add_many一次写入——怎么读、加什么是模型和使用者的事, 插件只负责跟 Postman 说话 - 只依赖
@deepseek-ai/schemastery(dsh 自带的配置 schema 库),其余全用 Node 内置模块
安装
插件目录本身就是一个 dsh bundle,用 dsh 自带的插件命令安装(从 GitHub):
dsh plugin --profile web add github:LiuJiaCheng11/dsh-postman
装的是仓库 main 分支的最新提交。想固定到某个提交:
dsh plugin --profile web add github:LiuJiaCheng11/dsh-postman#<commit-sha>
安装后 dsh 会自动把 dsh-postman 追加进 ~/.dsh/profiles/web/package.json 的
dsh.profile.bundles。重启 dsh(重新 dsh web)后插件生效 —— 实测插件代码
不会热重载,刷新浏览器或开新会话都不够。
注意仓库归属:GitHub 上还有另一个叫
dsh-postman的仓库 (zhousun55-byte/dsh-postman),那是一个文件上传插件,与本项目无关。 两者包名都是dsh-postman,装错会互相覆盖。认准LiuJiaCheng11/dsh-postman。
验证装配:
dsh --profile web --dump-config | grep -i postman
开发者:从本地目录安装(改代码即时生效,不用重装)dsh plugin --profile web add "E:/Code/DshPlugins/dsh-postman"
这会装成软链(link:),源码改动即已「安装」,只需重启 dsh。
适合开发调试;发布给别人用的命令是上面那条 GitHub 形式。
配置 Postman API Key
推荐:在 dsh 里打开「设置 → Postman」,直接填进表单。因为标了 role('secret'),
读取时值被抹掉、页面只显示「已配置 / 未配置」,明文不回流浏览器。
在 Postman 账号设置 → API Keys 生成 Key(PMAK- 开头)。
保存后如果当前会话仍读到旧 Key(用
postman_config看来源:那行就知道): 换一个新会话,或重启 dsh。原因见下方「保存为什么可能不马上生效」。
留空时的回退顺序:
- 设置表单里的
apiKey(写入 profile 的 patch 层,即~/.dsh/profiles/<profile>/cordis.patch.yml) ~/.dsh/dsh-postman/config.json(备用页写入,权限 0600)~/.postman_api_key(历史文件)POSTMAN_API_KEY环境变量
postman_config 的 status 会直接告诉你当前用的是哪一条来源,排查时先看它。
备用页在 http://127.0.0.1:<port>/dsh-postman,功能与设置表单等价,多了「保存并验证」。
它写的是上面第 2 条(~/.dsh/dsh-postman/config.json),每次取 Key 都重新读文件,
所以改完立刻生效、不受会话新旧影响 —— 设置页遇到怪问题时可以用它兜底。
设置页是怎么来的(关键:光导出 Config 不够)
dsh 里并存着两套设置服务实现,插件对两套都做了适配,且不会重复出页:
| dsh 版本 | 实现 | describe() 的数据来源 |
本插件的适配 |
|---|---|---|---|
| 0.2.x | SettingsForms |
遍历 profile 条目,取 entry.fiber.runtime.Config 当 schema;volatileForm() 会丢掉没有任何 volatile 字段的条目 |
导出 Config(src/index.js 顶部) |
| 0.1.x | SettingsProvider |
只列显式注册过的命名空间 | 调 settings.register('dsh-postman', Config, { base }),并 watch() 变更 |
两条路径互不干扰:0.2.x 的服务没有 register 方法,插件会直接跳过;0.1.x 不读 Config。
apiKey 必须标 volatile(这里用 .extra('volatile', true),见 src/index.js 的注释):
在 0.2.x 上,一个 volatile 字段都没有的条目会被 volatileForm() 整个滤掉。
但这两件事只让命名空间「有 schema 可渲染」,并不会让设置里出现一行。
Web 客户端的设置导航行只来自客户端注册的 settings.section 槽位
(ui-settings-general 的 nav 投影就是 ctx.slots.entries('settings.section'));
宿主侧那个 autoGenerate 字段在 Web 客户端没有任何消费方(桌面端才用)。
所以本插件自带了一个客户端半边 —— client/client.js,注册 id 为 postman 的设置分区,
页面直接编辑 dsh-postman 命名空间的 apiKey:
- 接线靠
package.json的dsh.client(platform: 'web'+inject)与exports["./client"]; 宿主把该文件经/plugins发给浏览器,不需要构建步骤(手写 JS,只用require("react"))。 - 页面通过
ctx.configForms.get('dsh-postman')读写,与官方设置页共用同一套 「读取脱敏 / 写入走 profile user 层 / revision 冲突栅栏」。 - 「Key 是否已配置」读的是
describe()视图的secrets(role('secret')字段的值被抹掉, 只回一个set布尔),所以页面永远拿不到明文。 - 用
configForms.whileServed(['dsh-postman'], …)包住注册:命名空间没被服务时不出现这一页。
客户端这一页只在 0.2.x 上出现。
settings.section槽位与configForms服务都是 0.2.x 的ui-settings才有的(在已安装的 0.1.1-rc.1 里全仓搜不到这两个名字)。 插件对这种情况做了兜底:apply()发现configForms缺失就静默退出,不抛错。 0.1.x 上请用备用页/dsh-postman配置,或直接写cordis.patch.yml。
保存为什么可能不马上生效
设置页的写入链路是:configForms.set() → remote.settings.mutate → 宿主
settings.update(ns) → configEditor.edit(entry)。0.2.x 里 configEditor 的文档就是
profile 的 patch 文件(documentPath === profileContext.patchPath),写完后它会
显式 reconcileProfilePatches(..., [entryId]) 重载该条目,所以插件配置本身是热更新的。
但要注意两点:
- 外部直接改
cordis.patch.yml不会被热加载(实测:运行中改文件、等 12 秒, 插件仍报旧来源)。只有走设置页/configEditor这条通道才会 reconcile。 - 已经跑起来的长会话可能仍持有旧的工具闭包(工具对象在会话建立时就固定了),
于是新 Key 要等新会话才被读到。这就是「设置里存了 Key,
postman_config却 还显示历史文件」的成因 —— 不是没存进去,是那个会话还没换上新配置。
想立刻生效、且不想管会话新旧:用备用页 /dsh-postman,它每次取 Key 都重读文件。
自检(四层,缺一层就会漏掉一种失败):
node test/settings-page.js—— 复刻 dsh 已构建产物里的表单投影逻辑跑真实Config, 并带一条反向对照(去掉 volatile → 条目必被丢弃)。node test/client-bundle.js—— 在假浏览器里加载client/client.js,验证 bundle id、槽位注册与写入路径,以及「0.1.x 没有 configForms 时不抛错」。bash test/client-served.sh—— 真启动,从 index.html 的启动图里读出该插件的 bundle 地址再取回来校验(dsh.client接线错了宿主不会报错,只会让那一页消失)。bash test/probe-key.sh—— 真启动后直接GET /dsh-postman/api/config, 看插件实际认的是哪一条 Key 来源(排查「我明明配了却读不到」最快的一招)。想直接看宿主侧的判定:
dsh --profile web --dump-config-schema,dsh-postman应指向真实 schema(如#/$defs/config101)而不是unknownConfig。
技能:/postman-skill
插件注册了一个运行时技能,在输入框敲 / 就能看到(与 browser-skill 并列):
/postman-skill
它把这套工具的完整用法(工具表、典型流程、破坏性操作的前置确认规则)注入当前会话。 不用它也行——系统提示里已带精简说明,直接说「帮我在 Postman 的 App 集合里加个接口」即可。
工具用法
| 工具 | 作用 |
|---|---|
postman_list |
列出全部集合(名称 + uid) |
postman_tree |
展示某集合的目录树与接口清单;可只 path 某个目录,输出过长会显式报截断 |
postman_add |
在指定目录新增一个接口(可自动建目录、继承集合 auth) |
postman_add_many |
批量新增多个接口;整批只 GET/备份/PUT 一次集合,同名跳过、可 dryRun 预演 |
postman_edit |
就地修改已有接口:改名 / URL / 方法 / 请求头 / 请求体 / 说明 / auth |
postman_rm |
按名称删除接口 |
postman_pull |
导出集合为 Postman v2.1 JSON |
postman_push |
用本地 JSON 整体覆盖集合 |
postman_config |
查看/设置/清除 API Key |
对话示例:
列出我的 Postman 集合
在 App 的「测试」目录下加一个 GET 接口 http://localhost:8080/api/v1/models
把 App 集合里「健康检查」的地址改成 /healthz
把 Converter 集合导出到 D:/tmp/converter.json
扫一下 D:/src/MyApi,看哪些接口还没进 Postman
把 D:/src/MyApi 里没同步的接口写进 App 集合
postman_edit:改接口别用「先删再加」
postman_edit 是就地 patch:只动你传进去的字段,其余原样保留——包括
event(Pre-request / Tests 脚本)、response(示例响应)、protocolProfileBehavior
这些工具本身不认识的字段。
| 参数 | 说明 |
|---|---|
collection / name |
必填。集合 + 要改的请求名(按名称递归定位第一个匹配) |
newName |
改名字;同层已有同名会报错 |
url / method |
新的 URL / HTTP 方法(方法自动转大写) |
headers |
整份替换(不是合并),每项 "Key: Value";无冒号的行会被忽略 |
body / language |
新的 raw 请求体 / 语言;不传 body 就不动它 |
clearBody |
true 时彻底移除请求体(优先级高于 body) |
description |
新说明;传空字符串即删除说明 |
auth |
true 继承集合级 auth;false 移除请求级 auth |
为什么不能用 postman_rm + postman_add 代替:add 只构造
method / header / url / body / auth 五个字段,rm + add 走一遍会把上面那些
字段静默丢掉(不会报错、不会提示),而且两次 PUT 之间请求在一段时间内是不存在的。
test/edit-and-tree.js 里有一条用例专门把这件事钉住。
postman_rm 只用于真的删接口。
postman_tree:大集合怎么不被截断
| 参数 | 说明 |
|---|---|
collection |
必填。集合名或 uid |
path |
只看某个目录的子树,如 "Discipline/EvaluateIndicator"(多级用 / 分隔)。目录不存在时报错并列出顶层目录 |
limit |
最多渲染多少行,默认 400 |
输出被截断时不会静默:末尾会追加
⚠️ 输出已截断:只渲染了前 400 行(该范围共 523 项)。
请用 path 参数只看某个目录再继续,或调大 limit。
顶层目录:测试、Attendance、Discipline
postman_add_many:批量新增接口
这个工具不理解任何代码。 它只接受一份已经整理好的清单,一次写进集合。 读代码、推导路由、决定加什么,都是模型自己的事——插件不参与,也不做任何语言假设。
| 参数 | 说明 |
|---|---|
collection |
必填。集合名或 uid |
items |
必填。请求数组,每项字段与 postman_add 同名同义 |
dryRun |
为 true 时只报告会新增/跳过哪些,不写入。默认 false |
createFolder |
目录不存在时自动创建,默认 true(postman_add 默认是 false) |
items 每一项:
| 字段 | 说明 |
|---|---|
name |
必填。接口名称(同目录下不可重名) |
url |
必填。完整 URL |
method |
HTTP 方法,默认 GET |
path |
目录路径,如 "Attendance/Leave";留空 = 集合根层 |
headers |
数组,每项形如 "Key: Value" |
body / language |
raw 请求体与其语言(不传按内容猜 json/text) |
description |
接口说明(可选) |
auth |
true 时继承集合级 auth |
行为:
- 整批只 GET / 备份 / PUT 一次集合——这是它存在的全部理由。循环调
postman_add加 50 条就是 50 次整体 PUT,集合大时会慢得没法用。 - 同目录同名会跳过,不覆盖,并在结果里单独列出跳过了哪些。 所以「这条早就有了」和「我名字写错了」在报告里是能分开看的。
- 目录不存在会自动建,新建了哪些目录也会列出来——目录名打错在这里就能发现。
- 缺
name或url的项会被跳过并报出第几项,不影响其余项。 - 一条都没写成功时不会发起 PUT,集合不受影响。
典型用法是先预演再写:
postman_add_many({ collection: "App", items: [...], dryRun: true }) # 先看报告
postman_add_many({ collection: "App", items: [...] }) # 确认后再写
已知限制
这些是真实使用中观察到的摩擦点(在一次 500+ 请求的集合上实测),目前不改 API, 知道怎么绕就行:
| 现象 | 原因 | 绕过办法 |
|---|---|---|
| 每次写操作都会 PUT 整个集合 | Postman API 的 PUT 是整体覆盖 | 工具已自动先 GET 最新版本并备份到 ~/.postman_backup/,正常使用无感 |
postman_edit 改请求头是整份替换,不是按 key 合并 |
保持语义简单、可预期 | 要保留原有 header 就把它们一并写进 headers |
postman_edit 按名称定位,重名时只改第一个匹配 |
名称是唯一对模型友好的标识 | 集合里避免同名请求;必要时先用 postman_tree 确认 |
postman_add_many 遇到同目录同名请求会跳过,不更新它 |
批量覆盖已有内容风险太高,不做隐式更新 | 要改已有的用 postman_edit;跳过哪些会在结果里列出 |
API 端点默认是 https://api.getpostman.com |
这是 Postman 的全局默认端点 | 只有 Enterprise 的 EU 数据驻留需要换 —— 用环境变量 POSTMAN_API_BASE,见下方说明 |
关于 API 端点(为什么不做成配置项)
浏览器里那个 <团队名>.postman.co 地址不影响 API:它是 Postman 给每个团队自动生成的
teamDomain(团队名的 slug,GET /me 里就能看到),只决定浏览器打开哪个团队的界面。
API 走的是 https://api.getpostman.com,用 API Key 认证,按账号返回数据 ——
同一个 Key 直接就能拿到该账号下的全部集合,和团队域名无关。
唯一真正会换端点的情况是 Postman Enterprise 的 EU 数据驻留
(官方文档:
The Postman API uses the api.eu.postman.com subdomain),需要联系 Postman 销售购买、
仅企业版可用,且部分端点不可用。普通/团队/企业(US)账号一律用默认端点。
所以这里没有做 UI 配置项:为极少数 EU 企业用户加一个设置字段,成本大于收益。
但留了一个不改代码的逃生口 —— 环境变量 POSTMAN_API_BASE:
POSTMAN_API_BASE=https://api.eu.postman.com dsh web
postman_config({ action: "status" }) 会把这行直接打出来,你能一眼确认当前用的是哪个端点:
已配置:PMAK-650…4d18
来源:历史文件 ~/.postman_api_key
端点:https://api.getpostman.com(默认)
0.4.1 及以前有两条限制(
postman_tree输出过长会被静默截断、没有「编辑」动作只能 先删再加)已在 0.5.0 解决:前者改为带path/limit且截断时显式提示, 后者新增postman_edit就地 patch。技能正文与系统提示的工具清单已同步。
安全说明
- PUT 是整体覆盖:每次写操作前都会先
GET最新云端版本,并备份到~/.postman_backup/<uid>.json。 - API Key 只存本机,不会外发;设置页里标了
role('secret'),明文不回流浏览器。 - 配置接口挂了与 dsh 其它 host 路由一致的信任栅栏(
connection.requestRejection),拒绝跨站调用。 - 破坏性操作(删除、覆盖、批量写入)在技能里被要求先向用户确认。
目录结构
dsh-postman/
package.json # 声明 dsh.bundle.patch 与 dsh.client
cordis.patch.yml # 插件装配补丁(insert 一行)
README.md
CHANGELOG.md
PUBLISHING.md # 发布指南(GitHub / npm / 插件市场)
LICENSE
client/
client.js # 客户端半边:注册「设置 → Postman」那一页(手写 JS,无需构建)
skill/
SKILL.md # /postman-skill 的正文
src/
index.js # 宿主入口:Config schema + 工具 + 技能 + 备用配置页
postman-api.js # Postman API 客户端
schemastery.js # 稳健加载 @deepseek-ai/schemastery(设置页 schema)
page.js # 备用配置页 HTML
test/
smoke.js # 冒烟测试(假 ctx 加载 + 契约校验 + 真实 list 调用)
edit-and-tree.js # postman_edit 的 patch 语义 + postman_tree 的 path/limit(22 项断言)
settings-page.js # 命名空间回归:复刻 dsh 的表单投影逻辑跑真实 Config
client-bundle.js # 客户端 bundle 单测(假浏览器 + 假 React)
_runtime.sh # 三个真启动脚本共用的运行时选择 / 起实例 / 取 token
realstart.sh # 真启动:探宿主路由 + 客户端 bundle + 查致命报错
client-served.sh # 真启动:确认客户端 bundle 被编入启动图并正常发出
probe-key.sh # 真启动:探 /dsh-postman/api/config,看 Key 实际来源
livereload-probe.sh # 真启动:验证「外部改 patch 文件不会热加载」(会临时改 profile,自带备份还原)
scripts/
setup-publish.sh # 一键填仓库地址 + 生成市场投稿文件
.github/workflows/
publish.yml # 打 tag 自动发 npm
开发
cd E:/Code/DshPlugins/dsh-postman
npm run check # node --check 全部源文件(含 client/client.js)
npm test # 下面五个单测一起跑
node test/smoke.js # 导出契约 + Config + 工具 + 技能 + 设置命名空间 + 路由
node test/edit-and-tree.js # postman_edit 的 patch 语义 + postman_tree 的 path/limit/截断提示
node test/settings-page.js # 命名空间会不会进 describe()(含反向对照)
node test/client-bundle.js # 客户端 bundle 的注册契约与写入路径
npm run test:real # 真启动:宿主路由 + 客户端 bundle 都要过
npm run test:client # 只跑客户端 bundle 的真启动验证
bash test/probe-key.sh # 真启动后看 Key 实际来源(排查配置问题的第一招)
test:real会在空闲端口拉起真实 dsh 实例,做两件事:
realstart.sh探/dsh-postman是否返回 200(对照组必须 404),并检查启动日志里有无unsupported JSON schema之类的致命错误;client-served.sh从 index.html 的启动图里读出本插件的 bundle 地址再取回来校验。dsh.client接线错了宿主不会报错,只会让「设置 → Postman」那一页凭空消失, 所以这一步不能省。改完工具 schema 或
Config一定要跑它——--dump-config只组合 YAML、不 import 插件,测不出这类问题。默认运行时就是源码仓库那份 0.2.0-rc.2(本机是以
node --expose-internals --import tsx/esm apps/cli/src/bin.ts web从源码起的), 也就是你真实在跑的那一份。它同时具备settings.section槽位与configForms, 即「设置 → Postman」那一页能出现的那一套。想换成别的运行时(做兼容性回归):
# 已安装的 0.1.1-rc.1(只保证工具与备用页可用,没有设置分区) DSH="C:/Users/ljc/.dsh/dsh-browser/node_modules/@deepseek-ai/dsh/lib/bin.js" bash test/realstart.sh 3096 # 源码仓库的构建产物(等价于默认,但走 lib/ 而不是 src/) DSH="E:/software/deepseek-harness/apps/cli/lib/bin.js" bash test/client-served.sh 3095 # 完整自定义命令 DSH_CMD="node --expose-internals --import tsx/esm apps/cli/src/bin.ts" DSH_SRC="E:/software/deepseek-harness" bash test/realstart.sh 3094两者发 bundle 的 URL 格式不同,
client-served.sh两种都认: 0.2.x 是 combo(/plugins/??dsh-postman/client.js&rev=…), 0.1.x 是单资源(/plugins/dsh-postman/client.js?rev=…)。命名空间 schema 单独验证:
dsh --profile web --dump-config-schema | grep -A5 '"dsh-postman"'
实测参考
一次真实会话里的调用分布(从 dsh 会话记录统计,共 450 次工具调用):
postman_add 7 postman_tree 2 postman_pull 2 postman_rm 2
postman_config 1 postman_list 1 ← 共 15 次,0 次报错
那次任务是在一个 518 请求的集合里往空目录加 5 个接口:先 config 确认 Key 来源 →
list 找集合 → tree 定位空目录 → 连加 5 个 → tree/pull 复核 → 发现两个请求体是
占位符 → rm + add 修正 → 再 pull 终验。集合请求数 518 → 523,全程无误报。
这次实测直接催生了 0.5.0 的两个改动:
- 那 2 次
rm全部是为了改请求体(占位符换真值),也就是 100% 可以被postman_edit消掉;而且那条路径会把 Tests 脚本/示例响应一起丢掉。 tree有 1 次输出被截断,模型当时只能改用pull落盘复核——所以现在截断会 显式报出来,并提示用path缩小范围。
(同会话另有 20 个报错,全部来自 browser-skill,与本插件无关。)
发布
要推到 GitHub / 发 npm / 上架 dsh 插件市场,见 PUBLISHING.md。
仓库地址与市场投稿文件已经准备好(github.com/LiuJiaCheng11/dsh-postman,
marketplace/LiuJiaCheng11__dsh-postman.yml),剩下的主要是 npm 发包 + 提市场 PR。
⚠️ 发包前务必再查一次包名是否被占 —— GitHub 上已有一个同名的
zhousun55-byte/dsh-postman(文件上传插件,包名也叫dsh-postman)。详见 PUBLISHING.md 第 2 节。
变更记录见 CHANGELOG.md。
No comments yet. Be the first to write one.