🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

本地插件定制 ishanshmalviya5/dsh-local-plugins

DeepSeek 客户端v0.3.0
ishanshmalviya510 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

把插件改成自己的版本,更新重装都不丢改动

34.2/100
综合分
6
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/23 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/ishanshmalviya5/dsh-local-plugins.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 10 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

engines.node 要求 >=26,不满足 Node 22.19.0

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-local-plugins @ 0.3.0
✗Node 引擎要求 >=26 · 基线 Node 22.19 不满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/23 15:53:59

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 148 处,展示前 116 条)
  • 发起网络请求
    client/client.js:41
    const res = await fetch(`${API}/${action}`, {
  • 写入剪贴板
    client/client.js:179
    await navigator.clipboard.writeText(text);
  • 读取本地文件
    lib/env.js:22
    return JSON.parse(readFileSync(file, 'utf8'));
  • 读取本地文件
    lib/env.js:26
    try { lstatSync(p); return true; } catch { return false; }
  • 读取本地文件
    lib/env.js:34
    if (existsSync(pj)) {
  • 读取本地文件
    lib/env.js:95
    return readdirSync(scopeDir).filter((n) => !n.startsWith('.')).map((n) => `@deepseek-ai/${n}`).sort();
  • 读取本地文件
    lib/actions.js:145
    if (existsSync(env.repoDir(name))) throw fail(`A folder for ${name} already exists at ${env.repoDir(name)} (from an earlier add, or moved back from th
  • 读取本地文件
    lib/actions.js:180
    if (lstatSync(loc.path).isSymbolicLink()) throw fail(`${loc.path} is already a symlink (something else manages it), so it cannot be migrated. Nothing
  • 删除本地文件递归
    lib/actions.js:189
    rmSync(dir, { recursive: true, force: true });
  • 写入本地文件
    lib/actions.js:206
    mkdirSync(env.root, { recursive: true });
  • 写入本地文件
    lib/actions.js:214
    renameSync(tmp, env.repoDir(name));
  • 删除本地文件递归
    lib/actions.js:220
    rmSync(tmp, { recursive: true, force: true });
  • 删除本地文件递归
    lib/actions.js:233
    rmSync(env.repoDir(name), { recursive: true, force: true });
  • 读取本地文件
    lib/actions.js:291
    try { stat = lstatSync(p.core.path); } catch { }
  • 删除本地文件递归
    lib/actions.js:338
    rmSync(join(work, 'node_modules'), { recursive: true, force: true });
  • 删除本地文件递归
    lib/actions.js:340
    rmSync(work, { recursive: true, force: true });
  • 写入本地文件
    lib/actions.js:377
    mkdirSync(env.workDir, { recursive: true });
  • 删除本地文件递归
    lib/actions.js:414
    rmSync(join(work, 'node_modules'), { recursive: true, force: true });
  • 读取本地文件
    lib/actions.js:549
    const pkg = JSON.parse(readFileSync(file, 'utf8'));
  • 写入本地文件
    lib/actions.js:561
    writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`);
  • 写入本地文件
    lib/actions.js:672
    mkdirSync(env.trashDir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/actions.js:677
    renameSync(env.repoDir(name), trash);
  • 读取本地文件
    lib/actions.js:697
    if (!existsSync(path)) return 0;
  • 读取本地文件
    lib/actions.js:708
    const listDir = (d) => { try { return readdirSync(d); } catch { return []; } };
  • 写入本地文件
    lib/ops.js:14
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/ops.js:15
    try { if (statSync(file).size > LOG_MAX) renameSync(file, `${file}.1`); } catch { }
  • 读取本地文件
    lib/ops.js:15
    try { if (statSync(file).size > LOG_MAX) renameSync(file, `${file}.1`); } catch { }
  • 写入本地文件
    lib/ops.js:16
    appendFileSync(file, `${JSON.stringify({ id: op.id, kind: op.kind, plugin: op.target, action: op.request?.action ?? null, commit: op.result?.deployedS
  • 读取本地文件
    lib/registry.js:109
    raw = readFileSync(file, 'utf8');
  • 读取本地文件
    lib/registry.js:137
    try { const v = JSON.parse(readFileSync(file, 'utf8')).version; return Number.isInteger(v) ? v : null; } catch { return null; }
  • 写入本地文件
    lib/registry.js:145
    if (!existsSync(bak)) copyFileSync(file, bak);
  • 读取本地文件
    lib/registry.js:145
    if (!existsSync(bak)) copyFileSync(file, bak);
  • 写入本地文件
    lib/registry.js:151
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/registry.js:154
    writeFileSync(tmp, `${JSON.stringify({ ...reg, version: CURRENT_VERSION }, null, 2)}\n`, { mode: 0o600 });
  • 写入本地文件
    lib/registry.js:157
    renameSync(tmp, file);
  • 写入本地文件
    lib/registry.js:158
    try { chmodSync(file, 0o600); } catch { }
  • 写入本地文件
    lib/registry.js:178
    if (existsSync(file)) { kept = `${file}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`; copyFileSync(file, kept); }
  • 读取本地文件
    lib/registry.js:178
    if (existsSync(file)) { kept = `${file}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`; copyFileSync(file, kept); }
  • 执行外部命令固定命令
    lib/registry.js:185
    const gitq = (dir, ...args) => { try { return execFileSync('git', ['-C', dir, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).tri
  • 读取本地文件
    lib/registry.js:194
    try { dirs = readdirSync(env.root, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith('.')); } catch { return out; }
  • 读取本地文件
    lib/registry.js:197
    if (!existsSync(join(dir, '.git')) || !gitq(dir, 'rev-parse', '--verify', '--quiet', 'local') || !gitq(dir, 'rev-parse', '--verify', '--quiet', 'upstr
  • 写入本地文件
    lib/repo.js:16
    await mkdir(infoDir, { recursive: true });
  • 写入本地文件
    lib/repo.js:17
    await appendFile(join(infoDir, 'exclude'), '\n# dsh-local-plugins\nnode_modules\n');
  • 读取本地文件
    lib/repo.js:22
    for (const entry of await readdir(dir)) {
  • 删除本地文件递归
    lib/repo.js:24
    await rm(join(dir, entry), { recursive: true, force: true });
  • 写入本地文件
    lib/repo.js:30
    await mkdir(dir, { recursive: true });
  • 读取本地文件
    lib/repo.js:86
    for (const entry of await readdir(dir)) if (!skip(entry)) await rm(join(dir, entry), { recursive: true, force: true });
  • 删除本地文件递归
    lib/repo.js:86
    for (const entry of await readdir(dir)) if (!skip(entry)) await rm(join(dir, entry), { recursive: true, force: true });
  • 删除本地文件递归
    lib/repo.js:105
    await rm(tmp, { recursive: true, force: true });
  • 读取本地文件
    lib/sources.js:62
    return existsSync(dir) ? dir : null;
  • 读取本地文件
    lib/sources.js:70
    return (await readdir(dir)).filter((f) => f.endsWith('.tgz')).map((f) => f.slice(0, -4)).sort(compareVersions);
  • 读取本地文件
    lib/sources.js:138
    try { pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); } catch (err) { throw Object.assign(new Error(`the package has no readable pa
  • 读取本地文件
    lib/sources.js:166
    for (const name of await readdir(dir)) {
  • 读取本地文件
    lib/sources.js:168
    const st = await lstat(p);
  • 读取本地文件
    lib/sources.js:170
    const target = resolve(dirname(p), await readlink(p));
  • 写入本地文件
    lib/sources.js:201
    await mkdir(ex);
  • 删除本地文件递归
    lib/sources.js:212
    await rm(tmp, { recursive: true, force: true });
  • 读取本地文件
    lib/deploy.js:41
    const scripts = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')).scripts ?? {};
  • 读取本地文件
    lib/deploy.js:49
    try { entries = readdirSync(nm); } catch { return found; }
  • 读取本地文件
    lib/deploy.js:52
    if (e.startsWith('@')) { try { for (const s of readdirSync(join(nm, e))) check(join(nm, e, s), `${e}/${s}`); } catch { } } else check(join(nm, e), e)
  • 读取本地文件
    lib/deploy.js:59
    const missing = Object.keys(pkg.dependencies ?? {}).filter((d) => !existsSync(join(dir, 'node_modules', d, 'package.json')));
  • 读取本地文件
    lib/deploy.js:95
    const missing = [...wanted].filter((f) => !existsSync(join(dir, f)));
  • 读取本地文件
    lib/deploy.js:100
    for (const e of readdirSync(d, { withFileTypes: true })) {
  • 写入本地文件
    lib/deploy.js:137
    renameSync(tmp, link);
  • 删除本地文件
    lib/deploy.js:139
    if (!isCrash(err)) rmSync(tmp, { force: true });
  • 写入本地文件
    lib/deploy.js:167
    mkdirSync(dst, { recursive: true });
  • 删除本地文件
    lib/deploy.js:171
    rmSync(dst, { force: true });
  • 写入本地文件
    lib/deploy.js:181
    mkdirSync(target, { recursive: true });
  • 删除本地文件递归
    lib/deploy.js:238
    rmSync(join(dir, 'node_modules'), { recursive: true, force: true });
  • 写入本地文件
    lib/deploy.js:278
    mkdirSync(env.deployedDir, { recursive: true });
  • 写入本地文件
    lib/deploy.js:281
    writeFileSync(join(snap, READY), `${sha}\n`);
  • 删除本地文件递归
    lib/deploy.js:285
    rmSync(snap, { recursive: true, force: true });
  • 写入本地文件
    lib/deploy.js:293
    mkdirSync(env.deployedDir, { recursive: true });
  • 删除本地文件
    lib/deploy.js:323
    try { unlinkSync(env.stableLink(plugin.name)); } catch { }
  • 读取本地文件
    lib/link.js:24
    if (!lstatSync(path).isSymbolicLink()) return false;
  • 删除本地文件递归
    lib/link.js:53
    rmSync(installed, { recursive: true, force: true });
  • 写入本地文件
    lib/link.js:54
    mkdirSync(dirname(installed), { recursive: true });
  • 删除本地文件递归
    lib/link.js:81
    rmSync(join(env.profileDir, 'node_modules', plugin.name), { recursive: true, force: true });
  • 写入本地文件
    lib/link.js:92
    renameSync(from, to);
  • 删除本地文件递归
    lib/link.js:96
    rmSync(from, { recursive: true, force: true });
  • 删除本地文件递归
    lib/link.js:104
    rmSync(nestedDst, { recursive: true, force: true });
  • 读取本地文件
    lib/link.js:105
    if (existsSync(nestedSrc) && !lstatSync(plugin.core.path).isSymbolicLink()) {
  • 读取本地文件
    lib/link.js:105
    if (existsSync(nestedSrc) && !lstatSync(plugin.core.path).isSymbolicLink()) {
  • 写入本地文件
    lib/link.js:106
    mkdirSync(dirname(nestedDst), { recursive: true });
  • 删除本地文件
    lib/link.js:120
    if (st?.isSymbolicLink()) unlinkSync(path);
  • 写入本地文件
    lib/link.js:124
    mkdirSync(env.backupDir, { recursive: true });
  • 删除本地文件递归
    lib/link.js:125
    rmSync(backup, { recursive: true, force: true });
  • 读取本地文件
    lib/link.js:142
    if (plugin.core.backup && existsSync(plugin.core.backup)) {
  • 写入本地文件
    lib/link.js:146
    mkdirSync(path, { recursive: true });
  • 读取本地文件
    lib/link.js:154
    try { return lstatSync(p); } catch { return null; }
  • 读取本地文件
    lib/gitstate.js:18
    if (!existsSync(join(dir, '.git'))) return null;
  • 写入本地文件
    lib/txn.js:40
    mkdirSync(env.txnDir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/txn.js:42
    writeFileSync(tmp, JSON.stringify(txn, null, 2), { mode: 0o600 });
  • 写入本地文件
    lib/txn.js:45
    renameSync(tmp, file(env, txn.id));
  • 删除本地文件
    lib/txn.js:64
    rmSync(file(env, txn.id), { force: true });
  • 读取本地文件
    lib/txn.js:70
    try { names = readdirSync(env.txnDir).filter((n) => n.endsWith('.json')); } catch { return []; }
  • 读取本地文件
    lib/txn.js:72
    try { return { ...JSON.parse(readFileSync(join(env.txnDir, n), 'utf8')), file: join(env.txnDir, n) }; } catch (err) { return { id: n, corrupt: true, e
  • 读取本地文件
    lib/recovery.js:21
    const lstatSafe = (p) => { try { return lstatSync(p); } catch { return null; } };
  • 读取本地文件
    lib/recovery.js:34
    try { return readFileSync(join(dir, READY), 'utf8').trim() || null; } catch { return null; }
  • 读取本地文件
    lib/recovery.js:40
    return existsSync(t) ? t : null;
  • 读取本地文件
    lib/recovery.js:50
    if (!existsSync(d)) { if (d === env.root) mkdirSync(d, { recursive: true, mode: 0o700 }); else continue; }
  • 写入本地文件
    lib/recovery.js:50
    if (!existsSync(d)) { if (d === env.root) mkdirSync(d, { recursive: true, mode: 0o700 }); else continue; }
  • 读取本地文件
    lib/recovery.js:51
    const st = statSync(d);
  • 写入本地文件
    lib/recovery.js:52
    if ((st.mode & 0o077) !== 0) { chmodSync(d, 0o700); fixed.push(d); log?.(`made ${d} private (0700)`); }
  • 读取本地文件
    lib/recovery.js:72
    try { names = readdirSync(dir); } catch { continue; }
  • 删除本地文件
    lib/recovery.js:78
    if (st.isSymbolicLink() || st.isFile()) { unlinkSync(p); note(`stale temp ${n}`); }
  • 删除本地文件递归
    lib/recovery.js:86
    if (m && Date.now() - Number(m[1]) > 10 * 60_000) { rmSync(join(env.root, n), { recursive: true, force: true }); note(`abandoned download ${n}`); }
  • 删除本地文件递归
    lib/recovery.js:108
    rmSync(join(p, 'node_modules'), { recursive: true, force: true });
  • 删除本地文件递归
    lib/recovery.js:110
    rmSync(p, { recursive: true, force: true });
  • 删除本地文件
    lib/recovery.js:140
    rmSync(t.file, { force: true });
  • 删除本地文件
    lib/recovery.js:210
    unlinkSync(path);
  • 写入本地文件
    lib/recovery.js:277
    writeFileSync(join(target, READY), `${p.deployedSha}\n`);
  • 执行外部命令来自配置/变量
    lib/run.js:51
    const child = spawn(file, args, {
  • 写入本地文件
    lib/restart.js:52
    mkdirSync(logDir, { recursive: true });
  • 执行外部命令来自配置/变量
    lib/restart.js:54
    const helper = spawn(process.execPath, ['-e', helperCode({ file: process.execPath, args, cwd: process.cwd(), logFile, port })], {
  • 读取本地文件
    lib/syntax-check.mjs:18
    try { src = readFileSync(file, 'utf8').replace(/^#!.*/, ''); } catch (e) { bad.push({ file, message: `unreadable: ${e.message}` }); continue; }
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

把编辑过的 dsh 插件保存为本地 git 仓库,通过原子符号链接切换部署已提交的快照。

README

由 DeepSeek 最新模型翻译生成
dsh-local-plugins 定制任意 dsh 插件——内置插件也行——更新、损坏或重装时都不丢改动。 设置 → Local Plugins:列出你定制的每个插件、用大白话写的状态,以及你接下来大概率想做的操作 能拿它做什么 | 你想…… | 你点…… | 你得到…… | | 修好或微调插件,并留住改动 | + Add → Migrate,编辑,Commit,Apply | 改动在 dsh 里跑起来,更新和重装都不丢 | | 拿到作者的新版本,又不丢自己的微调 | Update | 新版本加上你的改动;两边改了同一行会先问你,期间不会出问题 | | 改动出问题时回退 | Deploy older commit…
展开完整文档(README 剩余 1 节)
dsh-local-plugins

定制任意 dsh 插件——内置插件也行——更新、损坏或重装时都不丢改动。

设置 → Local Plugins:列出你定制的每个插件、用大白话写的状态,以及你接下来大概率想做的操作

能拿它做什么

| 你想…… | 你点…… | 你得到…… |
| 修好或微调插件,并留住改动 | + Add → Migrate,编辑,Commit,Apply | 改动在 dsh 里跑起来,更新和重装都不丢 |
| 拿到作者的新版本,又不丢自己的微调 | Update | 新版本加上你的改动;两边改了同一行会先问你,期间不会出问题 |
| 改动出问题时回退 | Deploy older commit… | 立刻回到早先的版本 |
| 定制 dsh 内置功能(比如某个模型服务商) | 同样的步骤,作用在内置插件上 | 你的版本跑起来;Unlink 还原原版 |
| 让 AI 智能体安全地改插件 | Work on it | 智能体在该插件目录里打开,上下文已填好;它不能开启任何东西——只有你能 |
| 整理提交历史,让更新更省事 | Squash my changes | 智能体把你的提交全部压成一个,叠在原始发布版之上;先建备份分支,并检查代码没变 |
| 把依赖固定到更新的版本 | Dependency overrides → Apply override | 覆盖项保存、部署并检查:真正装上了才显示 ✔ |
| dsh 升级或重装后恢复 | Reapply all | 你的定制重新开启 |
| 试某个确切版本 | + Add,name@1.2.3(或 …#v1.2.0) | 就是那个版本,而且固定不动 |
| 看自己相对原版改了什么 | 看卡片 | 「相对原版改了 3 个文件」,以及当前正在跑的是什么 |
| 不再定制某个插件 | Unlink,想的话再 Delete… | 原版回来了;你的成果留在回收站文件夹里,可以恢复 |
| 把成果也存到别处 | 它就是个普通 git 文件夹:~/.dsh/local-plugins/<plugin> | 加自己的 git remote 推上去 |

出问题时,它会告诉你当前配置是否仍然安全、下一步该做什么——不会只甩一句「失败」。

Apply 失败:什么都没开启,面板会告诉你下一步做什么

安装(2 分钟)

1. 在终端里执行(如果你用的是别的配置档,把 web 换成你的配置档名):
dsh plugin --profile web add github:ishanshmalviya5/dsh-local-plugins

2. 重启 dsh web。
3. 打开 Settings → Local Plugins。

需要 git、npm 和 Node 26 或更新版本,系统为 macOS 或 Linux(详情)。

你的第一次定制

1. + Add → Migrate,挑一个你已经在用的插件。你之前手动改过的内容都会保留,作为起点。
2. 动手改——编辑 ~/.dsh/local-plugins/<plugin> 里的文件,或者点 Work on it,让 AI 智能体陪你一起改。
3. Commit,然后 Apply,再重启 dsh web。

整个循环就这些。在你点 Apply 之前,dsh 不会有任何变化;如果新版本构建不出来,或者过不了检查,旧版本照常运行。

更多场景

作者发布了新版本

点 Check now(dsh 启动后不久也会自动检查一次)。有新版本的插件会显示 Update available,点 Update。你的改动和新版本会自动合并。如果你和作者改了同样的行,卡片会列出这些文件,由你来选:修好它们(或点 Fix with agent)再点 Finish update,或者点 Abort 放弃这次尝试。整个过程里正在运行的插件不受影响,什么时候 Apply 由你决定。

一次有冲突的更新:涉及哪些文件,以及三个安全选项

某次改动把事情搞砸了

点 Deploy older commit…,挑一个能用的版本。如果 dsh 本身在你应用了某个改动之后立刻开始出问题,它会自己发现,把上一个能用的版本放回去,并弹出提示——你的改动已经保存好,你可以用 Work on it 去修。

如果 dsh 完全起不来,在终端里运行下面这条命令,把所有原始插件放回去:node ~/.dsh/local-plugins/.deployed/dsh-local-plugins/scripts/undo.mjs --all

我在定制一个内置插件,现在想退出

Unlink (restore original) 会把原始、未改动的插件还给 dsh。你的版本仍留在列表里(标为 Not applied),想切回来随时点 Apply。

我想从列表里删掉一个插件

先 Unlink,再 Delete… 并输入它的名字。你的工作成果会移到 ~/.dsh/local-plugins/.trash/,不会被抹掉。把文件夹移回来,点 Repair installation 就能恢复。

我不知道现在是什么状态

点 Repair installation。它会找出所有对不上的地方(比如崩溃之后,或者 dsh 升级之后),能安全修的直接修,修不了的告诉你,并且绝不会删掉你的工作成果。Recent operations 显示跑过哪些操作、花了多久,还有日志;Disk usage 显示每个插件占用的空间,并让你先预览再清理旧版本。

最近操作与磁盘占用

使用精确版本
npm 插件:name、name@1.2.3 或 name@beta。git 来源的插件:网址、github:owner/repo 或 git@host:owner/repo,末尾可选 #v1.2.0(某个发布版本)、#branch 或 #commit。锁定到发布版本或 commit 后,它永远不会自己变动。

各标签的含义

| 标签 | 含义 | 你要做什么 |
| Applied | dsh 正在运行你的最新版本 | 什么都不用做 |
| Changes pending | 你有更新的改动或未提交的改动,尚未运行 | Commit,然后 Apply |
| Update available | 作者发布了更新版本 | Update |
| Conflict | 某次更新涉及一些文件,需要你决定 | Finish update 或 Abort |
| Link lost / Reapply required | dsh 升级或重装了 | Reapply all(可放心重复执行) |
| Broken | 记录与文件对不上 | Repair installation |
| Disabled | 它让 dsh 崩溃,已被移出,好让 dsh 能启动 | Work on it,修好,Commit,Apply |
| Not applied | 你在跟踪它,但 dsh 用的是普通插件 | 想用自己的版本时点 Apply |

控制权始终在你手里

* 脚本需要你同意。 有些插件想以你的完整权限运行安装或构建脚本。这项默认关闭;你可以选 Allow this time 或 Always allow for this plugin,发布者变更时会再次询问。
插件正在请求权限:尚未上线任何内容
* 你的工作不会悄悄丢失。 取消关联、删除、回滚和崩溃恢复都会把你的改动留在某个地方,随时能取回。
* 只有完成并检查过的版本才会被启用 —— 绝不会是改了一半的版本。
* 它只与你跟踪的来源通信(插件的 git 或 npm 源)。

如何保证这些:Security · Recovery · How it works。

兼容性 —— dsh-local-plugins 0.3.0

| 平台 | 状态 |
| macOS(Apple silicon),Node 26 | ✅ 支持。 已对真实 dsh 手工端到端测试,也在 CI 中测试 |
| Linux(Ubuntu),Node 26 | 🟡 CI 中 lint、全部单元测试和包检查均通过。尚未对真实 dsh 做端到端测试 |
| Linux,Node 22 / 24 | 🟡 CI 结果相同,但超出支持范围(需要 Node 26 或更新版本) |
| Windows | ❌ 不支持 —— 它会明确提示,并且不启动 |

基于 dsh 0.2.0-rc.2 构建并测试。从 0.1 版本升级无需你做任何事:插件保持原样(详见 CHANGELOG)。

须知

* 如果某个插件在这款插件加载之前就让 dsh 崩溃,它自己修不了 —— 请用上面的 undo.mjs 命令。
* 上线前的检查只确认文件完整可读,不会先运行插件。
* 内置插件跟随 dsh 自带的版本,而不是更新的 npm 发布版。
* 它一次只做一件事;它忙的时候你点了,它会让你稍等。

面向开发者

HTTP API · 状态规则 · 故障排查 · 性能 · 发布检查清单 · 开发

欢迎贡献,AI 辅助的贡献也欢迎:先读 CONTRIBUTING.md(编码智能体另见 AGENTS.md)。

许可证

MIT — 见 LICENSE。
预览分享卡片长什么样 ↓ishanshmalviya5/dsh-local-plugins
分享 · DeepSeek Harness Hub
ishanshmalviya5/dsh-local-plugins

把插件改成自己的版本,更新重装都不丢改动

回到顶部