npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@moruteaven/dsh-key-panel

v1.0.1

Published

Managed secret store for DSH Desktop. Operator-owned keys, injected into the assistant's shell as $DSH_* variables, with a settings-page panel and three operator-controlled access modes.

Readme

dsh-key-panel

npm 上的 @moruteaven/dsh-key-panel · English · 简体中文 · 日本語

给 DSH Desktop 用的密钥管家。把 API 密钥集中放在一处, 以 $DSH_* 环境变量的形式注入助手的 shell,然后由你在设置页的面板里决定—— 助手到底能拿它们做什么。

它解决什么问题:助手要调 Cloudflare、OpenAI,或者你的数据库,就得有凭据。 直接粘进对话,就永久留在了对话记录里;存成文件,最后又散落到某个你早已忘记的 目录。这个插件把它收进一个文件、暴露成一个环境变量,而值本身从不进入对话。

助手要部署 Worker
  → 执行:wrangler deploy --api-token $DSH_CLOUDFLARE_TOKEN
  → shell 里有,模型的上下文里没有

功能

  • 设置页面板。增删改查、显隐、复制,都在你管理其它设置的地方,不用改配置文件。
  • $DSH_* 注入。每个密钥都是助手 shell 里的一个变量,改动下一条命令即生效,无需重启。
  • 三档权限,由你设定并持久保存:只读(默认)· 可写 · 可编辑。
  • 平台与账号。多数服务商要两个值(账号 id + 令牌),而一个服务商常配多个账号。按平台和账号归组后,每个槽位就是 DSH_<平台>_<账号>_ID 和 DSH_<平台>_<账号>_KEY。归组只是存储的组织方式——变量名仍是扁平的,读它的东西也都不用变。
  • 来源标记。密钥记录是你建的还是助手建的,且写入无法互相"洗白"。
  • 最近活动。记录每条 shell 命令实际拿到了哪些变量,以及助手主动申报的目的。两者并排展示,从不配对。只记名字和时间戳——任何值都不会写进去。
  • 名称作用域(暂未开放)。宿主支持把助手限定在 DSH_AGENT_* 之类的名字下,存储里已设置的作用域也照常生效,但本版本面板上的入口是隐藏的。
  • 两段式删除。删除需要第二次带令牌的确认调用——单次误调用删不掉东西。
  • 值永不进模型。任何模式下,没有任何工具会把密钥值返回给模型。
  • 幂等、可热重载。面板每次调用都重新读取当前权限。

安装

设置 → 插件 → 搜索 "dsh-key-panel" → 安装 → 重启 DSH Desktop

之后面板出现在 设置 → 密钥。

其它渠道:

  • npm —— npm i @moruteaven/dsh-key-panel,然后在你的 profile 的 package.json 里把裸包名 @moruteaven/dsh-key-panel 加进 dsh.profile.bundles。这个列表只接受裸包名,file: 或路径会被拒绝(file: 放在 dependencies 里是合法的)。
  • 从源码 —— 见 CONTRIBUTING.md。

使用

添加密钥

设置 → 密钥 → 添加密钥。

| 字段 | 说明 | | --- | --- | | 名称 | 必须匹配 DSH_[A-Z][A-Z0-9_]*,例如 DSH_CLOUDFLARE_TOKEN | | 用途 | 可选。会作为该变量的说明展示给助手 | | 值 | 密钥本体。明文存储,保存后只以掩码显示 |

然后直接告诉助手用它:

部署这个 worker,token 在 $DSH_CLOUDFLARE_TOKEN 里。

平台与账号

当一个服务商需要两个值(比如账号 id 和令牌),而你在它下面有不止一个账号时,平铺的列表会变得难读。用 添加平台 和 添加账号 把这些密钥归成一类,变量名由插件替你拼:

| 你填 | 得到 | | --- | --- | | 平台 CF、账号 WORK | DSH_CF_WORK_ID 与 DSH_CF_WORK_KEY |

提交前面板会先把两个名字显示出来,你能看到最终会落进 shell 的是什么。标识符只允许大写字母、数字和下划线;小写会被拒绝而不是悄悄转成大写——你没要求过的名字,比需要重打一次更糟。

密钥就在账号行里填。 账号建好之后,它的两个变量名已经定下来了——名字是从标识符拼出来的,不是你选的。所以账号那一行有一枚「填入密钥」按钮,点开是个弹出面板,一次收两个值,直接挂到这个账号下。弹出面板里会先把两个名字列出来,但不需要你敲——这才是重点:让你把一个插件算好的名字抄到顶部的「添加密钥」卡片、一个字段抄一次,那是誊写,不是输入。

每个槽位都显示自己有没有值,也可以先空着以后补。未分组的密钥仍然用「添加密钥」——它们没有账号可以填。

平台和账号都另有一个显示名,面板上显示的是它。它和标识符刻意分开:标识符会拼进变量名、之后不能改;显示名随时可改。如果你想让面板上写着「Cloudflare」而变量名保持 DSH_CF_*,这两个字段就是干这个的。

如果某个账号会生成的名字已经被占用——比如你在建账号之前就手工加过 DSH_CF_WORK_KEY——面板会先提示并列出涉及的密钥,等你确认。继续保存会覆盖它们原有的值,所以不会静默发生。

两点值得知道:

  • 归组是组织方式,不是安全边界。 变量仍是普通的扁平 DSH_* 名字——这正是关键,因为 shell 没有嵌套。归组给你的是一个好读的面板,顺带还带来一个正好对齐到单个账号的作用域(见下)。
  • 删平台或账号不会带走你的密钥。 只要还有密钥挂在它下面,删除就会被拒绝,你得先清空或改挂。如果你选择强行继续,那些密钥会被退回未分组,值一概不动。

从没归过组的密钥保持原样。这个功能不要求你重组任何东西才有用,已有的存储也照常可用、无需迁移。

最近活动

面板会保留两条记录,并把它们并排放在一起:

| 记录 | 谁写的 | 内容 | | --- | --- | --- | | 助手申报 | 助手调用 key_panel_intent | 它自己说的时间戳、目的,以及打算用的名字 | | 注入命令 | 插件自己,每次 shell 命令解析环境时 | 时间戳,以及那次命令实际拿到的名字 |

两者是并排展示,不是配对。列表里上下相邻的一条申报和一次注入,可能属于同一个任务,但数据本身没有任何东西这么说——把它们连起来就是把猜测当成事实呈现。

「注入命令」不意味着什么。 宿主在每条 shell 命令执行前解析整个 $DSH_* 环境,看不到命令拿它做了什么。所以一行只说明这些名字在那一刻在作用域里——不表示命令读了它们,更不表示用它们做了什么。把这份记录当作「哪些流程依赖了哪些密钥」的信号,不要当作审计凭证。

由此有两点实际影响:

  • 助手可能什么都不申报。 key_panel_intent 是可选的,跳过它没有任何代价,所以会有未申报的活动——这是预期内的。插件只在便宜的地方询问意图,但从不强制:每条命令前面加一道必填步骤,很快会变成肌肉记忆,也就不再携带信息。
  • 开销不在命令路径上。 记录先在内存里缓冲、再批量落盘,所以记账不会拖慢任何命令。文件有上限(超出后丢弃最旧的),写失败被吞掉——这份文件丢了尾部,损失的是趋势,不是密钥。

记录与密钥库放在一起,文件名 usage.jsonl,只保存名字和时间戳。任何值都不会写进去。

权限档位

| 档位 | 助手可以 | 助手不可以 | | --- | --- | --- | | 只读(默认) | 使用密钥 | 改任何东西——面向模型的工具一个都没注册 | | 可写 | 新增;覆盖自己建的 | 删除任何东西;改你建的密钥 | | 可编辑 | 增、改、删 | ——(删除仍需二次确认) |

在面板里切换,下一次工具调用即生效,不用重启。

从「只读」开始。 只有当你确实想让助手自己添加凭据时,再往上放——后两档就是 为这一个场景存在的。

按名称限定

本版本面板上暂未开放。 作用域功能本身是完好的——校验、持久化、每次模型调用时的判定都在,存储里已设置的作用域也继续生效。隐藏的只是设置它的那个输入框(lib/client.js 里的 SHOW_SCOPE_UI)。需要时可以直接在存储文件里设置;把该标志打开即可恢复入口。

在选档位之前,有个安全后果值得先知道:没有设置作用域时,限制助手能触到哪些密钥的只剩权限档位本身。 作用域未设置就等于没有限制。想要更窄的边界,就自己去设一个——目前只能写存储文件。

作用域用于收窄助手能碰的名字:

| 取值 | 效果 | | --- | --- | | (留空) | 不限制 | | DSH_AGENT_* | 只允许该前缀 | | DSH_CF_* | 只允许该平台的密钥 | | DSH_CF_WORK_* | 只允许某一个账号的凭据 |

只支持 * 这一个通配符,表达不了路径,也表达不了正则。

配置项

| 设置 | 位置 | 默认 | | --- | --- | --- | | 权限档位 | 面板 | readonly | | 名称作用域 | 仅存储(面板已隐藏) | 不限制 | | 存储位置 | $DSH_HOME/key-panel/keys.json | ~/.dsh/key-panel/keys.json | | 活动日志 | $DSH_HOME/key-panel/usage.jsonl | ~/.dsh/key-panel/usage.jsonl |

存储格式

{
  "version": 2,
  "policy": { "accessMode": "readonly", "scopePattern": null },
  "platforms": {
    "CF": { "label": "Cloudflare", "createdAt": 1758428400000 }
  },
  "accounts": {
    "CF/WORK": { "platform": "CF", "identifier": "WORK", "label": "工作", "createdAt": 1758428400000 }
  },
  "keys": {
    "DSH_CF_WORK_TOKEN": {
      "value": "…",
      "description": "Cloudflare Workers 部署令牌",
      "origin": "operator",        // "operator" | "model"
      "platform": "CF",            // 可选的归组字段
      "account": "WORK",
      "field": "key",              // "id" | "key"
      "createdAt": 1758428400000,
      "updatedAt": 1758428400000
    }
  }
}

写入走 临时文件 → fsync → rename。少了 fsync,崩溃可能留下一个已改名却 内容为空的文件——读回来就是「所有密钥都没了」。

version 1 的文件能原样加载:归组字段全是可选的,平台概念出现之前写入的密钥读回来 就是「未分组」。不需要迁移步骤。

usage.jsonl

活动记录放在另一个文件里,每行一个 JSON 对象:

{"t":1758428400000,"kind":"intent","names":["DSH_CF_WORK_TOKEN"],"note":"deploy staging"}
{"t":1758428450000,"kind":"use","names":["DSH_CF_WORK_TOKEN"]}

| 字段 | 含义 | | --- | --- | | t | epoch 毫秒 | | kind | intent(助手申报)或 use(注入命令) | | names | 涉及的名字,已去重。永远不是值。 | | note | 仅 intent——目的,截断到 500 字符 |

批量追加,并保留最近 1000 条,所以不会无限增长。与 keys.json 不同,它不是原子写入,也不逐行 fsync:进程被强杀可能截断最后一行,读取时跳过即可。这个取舍在这里是对的——这份文件是信号,不是密钥,为了持久性拖慢每条命令不值得。

两种记录由不同的一方在不同时刻写入,因此分开存储,展示时按时间对齐。插件从不把它们合并。

安全

密钥以明文存储。 这是刻意的取舍,完整说明(信任模型、不变量、非目标)见 SECURITY.md。

简要版:

  • 任何以你的 OS 用户身份运行的程序都能读到存储文件。请当成 .env 对待。
  • 任何模式下,助手都无法把密钥值读进对话记录。
  • 助手永远改不了权限档位和作用域——那是操作者专属的。
  • 「只读」档下写入工具不存在,而不是拒绝调用。

开发

npm test            # 两套一起跑 —— 427 条断言
npm run test:host   # 宿主侧:权限、存储、工具、网关(298)
npm run test:client # 客户端 bundle:契约、插槽、RPC、字典(129)

客户端测试按真实前端的加载方式跑 lib/client.js:伪造 window.__ModuleLoader__、require 只允许种子模块,然后断言 bundle 求值时 不产生副作用、只引用种子模块,以及面板调用的每个 RPC 端点都与宿主网关对得上。

没有构建步骤。 宿主侧是纯 ESM,浏览器侧是一个 CJS factory 字符串。

lib/
  policy.js   权限档位、作用域 glob、决策函数
  store.js    持久化、来源标记、原子写入
  tools.js    面向模型的工具、删除确认台账
  index.js    宿主侧 —— Typert 网关、shellEnv 注册
  client.js   浏览器 bundle —— 设置面板

本仓库要求遵守的约定、以及怎么跑测试,见 CONTRIBUTING.md。

兼容性

  • DSH Desktop 2.0.11+(dsh 0.1.5-rc.1)
  • Node 20+
  • 客户端部分仅支持 web 平台

许可

Apache License 2.0 · 署名信息见 NOTICE

选 Apache-2.0 而不是 MIT/BSD,是因为它对处理凭据的工具多了两条有意义的条款:

  • 专利授权(第 3 条)。MIT 和 BSD 完全没有这一条,而它同时要求贡献者做同样承诺。
  • 商标限制(第 6 条)。别人不能拿作者的名义为衍生分支背书——这正是 BSD-3-Clause 背书条款提供的同一层保护。

其余照旧宽松:可商用、可修改、可闭源分发。