dsh-stash —— DSH 链接外部资源库的钥匙串
30.5/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add danieldu168/dsh-stash该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · other
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 34 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装仓库 package.json 标记 private,未发布到 npm,需从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-stash(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/6 14:17:11
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 87 处,展示前 55 条)
- 写入剪贴板client/client.js:560
const done = navigator.clipboard.writeText(text); - 发起网络请求client/client.js:694
const response = await fetch(url, { - 发起网络请求client/client.js:741
const response = await fetch(url, { headers: { Accept: "application/json" }, cache: "no-store" }); - 发起网络请求client/client.js:768
const response = await fetch(url, { - 发起网络请求client/client.js:910
const response = await fetch(resolveEndpoint(ENDPOINT_PATH), { - 发起网络请求client/client.js:922
const response = await fetch(resolveEndpoint(ENDPOINT_PATH) + query, { - 发起网络请求client/client.js:1046
const response = await fetch(url, { - 写入本地文件lib/home.js:41
mkdirSync(dir, { recursive: true }) - 读取本地文件lib/portability-route.js:55
const parsed = JSON.parse(readFileSync(LAST_EXPORT_FILE, 'utf8')) - 读取本地文件lib/registry.js:501
if (!existsSync(SOURCES_FILE)) { - 写入本地文件lib/registry.js:503
writeFileSync(SOURCES_FILE, TEMPLATE, 'utf8') - 读取本地文件lib/registry.js:510
const stamp = statSync(SOURCES_FILE).mtimeMs - 读取本地文件lib/tools.js:90
const info = statSync(path) - 读取本地文件lib/tools.js:109
entries = readdirSync(dir, { withFileTypes: true }) - 读取本地文件lib/tools.js:592
info = statSync(candidate.path) - 读取本地文件lib/tools.js:600
content = readFileSync(candidate.path, 'utf8') - 写入本地文件lib/tools.js:1100
writeFileSync(probe, 'ok', 'utf8') - 删除本地文件lib/tools.js:1101
rmSync(probe, { force: true }) - 读取本地文件lib/tools.js:1108
cacheFiles = readdirSync(CACHE_DIR).length - 读取本地文件lib/tools.js:1133
missingPaths: source.paths.filter((path) => !existsSync(path)), - 读取本地文件lib/credential-store.js:40
const readText = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : '') - 读取本地文件lib/credential-store.js:40
const readText = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : '') - 写入本地文件lib/credential-store.js:43
mkdirSync(dirname(path), { recursive: true }) - 写入本地文件lib/credential-store.js:44
writeFileSync(path, text, 'utf8') - 读取本地文件lib/library-view.js:154
const info = statSync(path) - 读取本地文件lib/ledger.js:38
const parsed = JSON.parse(readFileSync(url, 'utf8')) - 读取本地文件lib/ledger.js:82
if (!existsSync(LEDGER_FILE)) return { records: [], broken: 0 } - 读取本地文件lib/ledger.js:85
text = readFileSync(LEDGER_FILE, 'utf8') - 写入本地文件lib/ledger.js:111
writeFileSync(LEDGER_FILE, `${kept.map((record) => JSON.stringify(record)).join('\n')}\n`, 'utf8') - 写入本地文件lib/ledger.js:159
appendFileSync(LEDGER_FILE, `${JSON.stringify(record)}\n`, 'utf8') - 读取本地文件lib/lessons.js:101
if (!existsSync(LESSONS_FILE)) return empty - 读取本地文件lib/lessons.js:105
parsed = JSON.parse(readFileSync(LESSONS_FILE, 'utf8')) - 写入本地文件lib/lessons.js:155
writeFileSync(LESSONS_FILE, `${JSON.stringify(sorted, null, 2)}\n`, 'utf8') - 读取本地文件lib/vault.js:239
const stamp = statSync(SOURCES_FILE).mtimeMs - 读取本地文件lib/portability.js:68
const readText = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : null) - 读取本地文件lib/portability.js:68
const readText = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : null) - 读取本地文件lib/portability.js:93
if (!existsSync(dir)) return out - 读取本地文件lib/portability.js:94
for (const entry of readdirSync(dir, { withFileTypes: true })) { - 读取本地文件lib/portability.js:105
return { path: rel, bytes: statSync(abs).size, sha256: hashFile(abs) } - 写入本地文件lib/portability.js:109
mkdirSync(dirname(path), { recursive: true }) - 写入本地文件lib/portability.js:110
writeFileSync(path, text, 'utf8') - 读取本地文件lib/portability.js:114
if (!existsSync(path)) return null - 写入本地文件lib/portability.js:236
mkdirSync(to, { recursive: true }) - 写入本地文件lib/portability.js:243
mkdirSync(dirname(to), { recursive: true }) - 写入本地文件lib/portability.js:244
writeFileSync(to, bytes) - 写入本地文件lib/portability.js:260
mkdirSync(to, { recursive: true }) - 删除本地文件递归lib/portability.js:835
rmSync(path, { recursive: true, force: true }) - 读取本地文件lib/shard.js:27
if (!existsSync(SOURCES_LOCAL_FILE)) return { sources: [], accounts: [], credentials: [], problems } - 读取本地文件lib/shard.js:31
parsed = JSON.parse(readFileSync(SOURCES_LOCAL_FILE, 'utf8')) - 写入本地文件lib/shard.js:57
writeFileSync( - 发起网络请求lib/handlers/objstore.js:203
response = await fetch(url, init) - 读取本地文件lib/http.js:71
if (!existsSync(path)) return undefined - 读取本地文件lib/http.js:73
const parsed = JSON.parse(readFileSync(path, 'utf8')) - 写入本地文件lib/http.js:83
writeFileSync(join(CACHE_DIR, fileName), JSON.stringify(value), 'utf8') - 发起网络请求lib/http.js:191
response = await fetch(url, init)
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
README
由 DeepSeek 最新模型翻译生成展开完整文档(README 剩余 1 节)
dsh-stash —— DSH 链接外部资源库的钥匙串
常用的电脑至少三台:家里的、公司/单位的、还有一台旧的。
常用的数据库、资料库,少说十几个起步。每换一台机器,就得重新交代一遍——哪个接口、要哪把钥匙、上次那个数字是从哪儿来的。交代完过两个星期,自己都记不清了。
这个插件把这些收在一处。登记一次,三台机器都知道。久而久之,它就成了你使用 DSH 时独有的链接外部数据的钥匙串。
它回答三个问题
你会问:DSH 已经能连 MCP、能调 API,也能上网查,再包一层,是不是重复建设?
连得上、调得通,不等于管得住取数。留下三问没有归属——能不能取、用哪把钥匙、这次取了什么。没有人回答这三问,代价就是每次会话从零交代一遍。
stash 就是这三问的归宿:
| 三问 | 答案在哪 | 一条硬约束 |
| 能不能取? | 库的 access 声明 | 两条硬门禁直接拒绝,不尽力而为 |
| 用哪把钥匙? | 钥匙台账 + 宿主凭据服务 | 值不进模型、不进会话记录、不进台账 |
| 这次取了什么? | 取数台账 | 只记指纹(ledgerId / contentHash),没有响应体 |
对模型暴露 11 个工具(取数 / 清单 / 台账 / 体检 / 登记 / 经验)。另有两条路不经过模型:/stash 命令同「设置 → stash」面板。
如何安装
pnpm dsh plugin --profile web add "github:danieldu168/dsh-stash#v0.11.2"
装完重启 Profile,浏览器那半边硬刷新。验一句:问「我登记了哪些库?」——没登记过,它回"本机尚未登记外部资源"。
升级就是带上新 tag 重装一次,再重启。DSH 没有 update 这个动作,管理器只有列举、启停、安装、移除四类。规格串一个字符都不变,它就认为什么都没装成,报 ambiguous-install,并把 package.json 同 pnpm-lock.yaml 一起还原——所以升级必须让 tag 动一下。
从本机目录装的(依赖里是 link:)没有版本可升:git pull 完重启就是最新。卸载:
pnpm dsh plugin --profile web remove dsh-stash
本包零依赖、零构建脚本,private: true,只发 GitHub。路径全部由 DSH_HOME / os.homedir() 派生,没有机器专属硬编码。
如何登记
注册表是 ${DSH_HOME}/stash/sources.mjs。一条库 = 数组里一个对象。改完不用重启,每次调用重读。
export default [
{
id: 'myapi', name: '我的接口', kind: 'remote', handler: 'http', access: 'public-api',
actions: { search: '按关键词检索' },
request: {
url: 'https://api.example.com/search',
query: { q: '{query}' },
pick: 'data.items', limit: 50, required: ['query'],
},
},
]
三点够用了:
- {参数名} 代入参数。可选参数写 {?参数名}——缺参时整个键被丢掉。不这么写,'eq.{category}' 缺参就代入成 eq.,看着合法,必被上游拒。
- 要钥匙就加 credentials: ['MY_API_KEY']。只写引用名,不写值。
- 六种形态照抄 sources.example.mjs。为什么是这六种,见 DESIGN-taxonomy.md。
不想写代码的两条路:
- 本机文档:{ id: 'mydocs', kind: 'files', paths: ['D:/papers'] }。只登记路径,检索走 stash_files,读内容走 read。
- 内置处理器:handler: 'trade_stats'。把容易算错的地方固化进代码——缺参在发请求前拦下、只读聚合字段而非明细、按 nbPages 自动拼全、合计绝不逐项求和(逐项舍入会差)。
改完直接说「用 myapi 查一下光伏」。模型先 stash_catalog 看边界同参数,再 stash_fetch。
边界写在注册表里
| access | 含义 | 行为 |
| public-api | 公开免登录接口 | 取 |
| official-api | 需官方 API 或授权凭据 | 取(钥匙在面板配) |
| export-import | 只允许人工在网页端导出后放进 corpus/ | 拒绝,返回 kind:"boundary" |
| unsupported | 明确不做 | 拒绝,同上 |
不写 access 就是未声明:取数放行,但 stash_doctor 会把这条列成提示。
拒绝不是静默失败——它同样写台账。所以"我试过、它拒绝了"同"我没试过"是两件事。
钥匙放在哪
值只有两条路进来:「设置 → stash」面板,或 /stash import。两条都不进 tool 参数、不进会话记录、不进模型上下文。
模型手里的 stash_credential_add 没有 value 参数,永远不会有;界面也显示不出已存的值——凭据服务没有回值方法。
值落在哪,由这把钥匙声明的 inject 决定:
| 消费者 | $DSH_HOME/.env | 「设置 → 钥匙」面板 |
| 走 ctx.credentials 的(stash 的 {credential:REF}) | 可以 | 可以,且优先级更高 |
| 读 process.env 的(如 MCP 行的 !!js 表达式) | 可以 | 无效,且不报错 |
env:NAME 落点的钥匙,在面板里贴一次会两处都写。但 .env 只在启动时读一次——读 process.env 的组件要重启 DSH 才看得到。
落点写错的表现是「面板一片绿、功能却是断的」。
工作面板
设置 → stash。三层:首页 → 类层 → 详情。逐条说明见 DESIGN-panel.md。
首页 类层(点任一格) 新建 / 编辑数据源 远端接口 新建什么 [远端接口 ▾] 12 个数据源 ● 就绪/阻塞 账号 2 个·3 把钥匙 [全部▾] 形态 [声明式 HTTP ▾] 6 远端接口 5 本机服务 资源 6 条·6 就绪 [全部▾] ID / 名称 / 地址 / 边界…
1 本机文件 整行点开 = 详情(编辑/删除) ▸ 高级设置台账 + 迁移(折叠) 新建账号 →(次要入口)
用词分两层:首页数的是数据源(三类合计),类层列的是这一类里的资源。同一个词只出现一次,免得问"资源到底指什么"。
三类是唯一一级分类:远端接口 / 本机服务 / 本机文件。归类自动派生,不用人填——files 同 SQLite 归本机文件,MCP 走 stdio、或走回环地址,归本机服务,其余归远端接口。本地反向代理把远端伪装成 127.0.0.1 是真实存在的反例,注册表字段 bucket 可以覆盖。
五个写入位置:
| 要写什么 | 面板 | 手写 | 模型 |
| 资源 | ✅ | sources.mjs | stash_source_add |
| 钥匙条目 | ✅ | sources.local.json | stash_credential_add |
| 钥匙的值 | ✅ | ~/.dsh/.credentials.yaml / .env | 写不了 |
| 经验 | ➖ 刻意没有入口 | lessons.json | stash_lesson_add |
| 语料 | ➖ 不搬文件 | 放进 corpus/ | stash_source_add |
面板的写走 stash_source_add 本体——URL 写错、SQL 不是只读单语句、引用名没声明,登记时就拒掉,报错落在字段上,不用等真去取才发现。它永不改写手写件:sources.mjs 对面板只读。
经验没有面板入口是刻意的。经验是判断,不是事实;自动生成的多半是噪音。系统只做一件能做的——把缺口点出来:哪条库失败过却没记经验。
如何迁移
三条命令,都不经过模型:
/stash export --out "D:\stash-export" # 默认不带值;--with-values 带明文,--corpus 带语料 /stash import "D:\stash-export" # 值覆盖、条目并集;--dry-run 先看结果 /stash wipe --confirm <校验码> # 清空本机数据,不卸插件
铁律一条:先导出 → 在新机器导入成功 → 再回旧机器清空。清空过两道门(本机有过导出记录、校验码对得上),但 host 验证不了"另一台真的导入成功了"——校验码是人工对账,不是密码学证明。
导入规则一句话:值覆盖,条目并集,台账只拼接不去重(两台各查过一次本身就是事实),同 id 不同定义报冲突并保留本机。冲突一律不自动决定。
凭据的值不随包走。 .credentials.yaml 装着本机所有密钥,别整拷。逐项规则、包结构、manifest.json 字段见 DESIGN-portability.md。
它不做什么
不做通用网页抓取。 一次性检索交给 web_search / web_fetch;需要长期盯的页面本质是语料,走 corpus/ + stash_files。
不做需登录数据库的 SSO 自动化。 不模拟登录拿会话态、不调加密接口。那条路会从"调用公开接口"越界到绕过访问控制,而且读者卡同商业许可数据库的条款普遍禁止脚本化访问,后果落在持卡人自己的账号上。正当路径:人工导出 → corpus/ 下该库的子目录 → stash_files → read。
不做值的存储,也不回读。 凭据服务本身没有回值方法,这是框架保证,不是代码自律。
经验也不自动写入:失败只提示,记不记由人定。
它做不到什么
数据新鲜度。 cacheTtlMs 不写就是永不过期,快变数据务必自己声明。
台账反查内容。 只记指纹,反查不到内容。文件超 2000 条裁到最近 1000 条,旧的会丢。
语义检索。 stash_files 的检索同经验库的 query 都是子串匹配。
接口连通性。 stash_doctor 只做本地检查、不联网,验不了接口通不通——要验就直接取一次。
按需线索的准头。 判据是关键词匹配,两个方向都会失手:说"那个 API"却不提库名,只得全景;说"数据结构"可能被当成取数意图。快照异步刷新,刚加完的库可能还没读到。
凭据的完整视图。 凭据服务的引用半边按设计没有列表接口,面板显示的是台账同注册表声明过的引用名的并集。/stash wipe 的删除范围同此限。
框架目录
${DSH_HOME}/stash/
├── sources.mjs 手写注册表 ├── sources.local.json 代写分片:{ sources, accounts } ├── ledger.ndjson 取数台账(只有指纹) ├── lessons.json 经验库(按库 id 分组) ├── cache/ 取数缓存(可删,会重建) ├── corpus/ 原始语料,你放 ├── backup/ 迁移前留的 .bak(可删) └── .last-export.json 最近几次导出的记录
两份注册表合并使用,同 id 以手写文件为准。
开发测试
零依赖、零构建,手写的 check() 断言,不引测试框架。
node test/host-assembly.mjs # 注册表 / 工具 / 三条路由 / 提示注入 —— 223 项
node test/client-runtime.mjs # 首页 + 类层 + 钥匙三层 + 表单 / 编辑 / 删除 —— 254 项
node test/portability.mjs # 导出 / 导入 / 清空 —— 87 项
node test/handlers-bc.mjs # SigV4 官方向量 / SQLite / S3 / WebDAV —— 42 项
node scripts/preflight-upload.mjs # 上传前自检(CI 也跑这一步)
606 项断言全部通过。 host 同 portability 跑在临时 DSH_HOME(os.tmpdir() 下),跑完自清理,既不读也不写你真实的 ~/.dsh/;client 测试只读 client/client.js 源码。
上传 GitHub 之前跑一次 scripts/preflight-upload.mjs。它扫整个仓库,命中就非零退出:数据文件、Windows、macOS、Linux 的用户目录绝对路径、邮箱、疑似密钥值、令牌、私钥正文。
设计取舍见 DESIGN-taxonomy.md(分类)/ DESIGN-vault.md(钥匙)/ DESIGN-lessons.md(经验)/ DESIGN-portability.md(迁移)/ DESIGN-panel.md(面板)。版本变化见 CHANGELOG.md。
许可
MIT。
十几条的时候,靠记性还撑得住。
上百条的时候,拿记性去对齐,不是差距,是代差。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
danieldu168/dsh-stash
dsh-stash —— DSH 链接外部资源库的钥匙串
相关插件 · 其他
模型破甲清洗HunYiSage/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 16,993已汉化DeepSeek 客户端
73
女娲蒸馏alchaincyf/nuwa-skill
输入名人名字,提炼其思维模型与决策方式供你调用
★ 37,308已汉化DSH
72
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 888已汉化DSH
71.6
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,759已汉化DSH
71.4
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,269已汉化DSH
70.8
这是你的仓库?认领条目