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

模型档案切换 fan56/dsh-profile-switch

DeepSeek Harnessv0.3.0
fan5636 天前有提交已提供中文文档扫描:高危

一个 dsh 插件:具名模型配置档案,你可以通过宿主原生的 ask-user…

30.7/100
综合分
1
GitHub 星标
455
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @aiwayds/dsh-profile-switch
npm 源 · @aiwayds/dsh-profile-switch 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · ui
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
数据截至 2026/10/25·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]—
更新放缓:最近一次提交在 36 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

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

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

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

🔴 安全扫描发现高危能力自动 · 静态分析源码
v2.7.0
🔴 扫描:高危尚无人工评估

静态扫描发现安装期即执行代码,或命令参数由运行时变量拼接(参数来源无法静态判定,从严处理)。

安装时即执行脚本可执行外部命令读写/删除本地文件
查看扫描证据(共 54 处,展示前 54 条)
  • 读取本地文件
    scripts/link-dsh-closure.mjs:40
    if (!existsSync(join(repoRoot, 'tsconfig.json')) || !existsSync(join(repoRoot, '.git'))) {
  • 读取本地文件
    scripts/link-dsh-closure.mjs:53
    if (existsSync(join(dir, "cordis"))) return dir
  • 执行外部命令固定命令
    scripts/link-dsh-closure.mjs:59
    const bin = execFileSync('which', ['dsh'], { encoding: 'utf8' }).trim()
  • 读取本地文件
    scripts/link-dsh-closure.mjs:63
    if (existsSync(join(closure, 'cordis'))) return closure
  • 执行外部命令固定命令
    scripts/link-dsh-closure.mjs:69
    const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8' }).trim()
  • 读取本地文件
    scripts/link-dsh-closure.mjs:71
    if (existsSync(join(nested, 'cordis'))) return nested
  • 读取本地文件
    scripts/link-dsh-closure.mjs:76
    if (existsSync(join(flat, 'cordis'))) return flat
  • 写入本地文件
    scripts/link-dsh-closure.mjs:87
    mkdirSync(scopeDir, { recursive: true })
  • 读取本地文件
    scripts/link-dsh-closure.mjs:89
    for (const name of readdirSync(closure)) {
  • 删除本地文件递归
    scripts/link-dsh-closure.mjs:95
    rmSync(target, { recursive: true, force: true })
  • 读取本地文件
    lib/agents-dir.js:141
    if (!existsSync(dir))
  • 读取本地文件
    lib/agents-dir.js:143
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    lib/agents-dir.js:147
    const result = parseAgentMarkdown(readFileSync(path, 'utf8'), path);
  • 读取本地文件
    lib/model-profiles.js:139
    text = readFileSync(path, 'utf8');
  • 写入本地文件
    lib/model-profiles.js:166
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/model-profiles.js:167
    writeFileSync(tmp, body);
  • 写入本地文件
    lib/model-profiles.js:168
    renameSync(tmp, path);
  • 删除本地文件
    lib/model-profiles.js:173
    rmSync(tmp, { force: true });
  • 读取本地文件
    lib/model-profiles.js:292
    if (existsSync(candidate)) {
  • 读取本地文件
    lib/model-profiles.js:293
    const parsed = parseProfilePinText(readFileSync(candidate, 'utf8'));
  • 写入本地文件
    lib/model-profiles.js:325
    writeFileSync(path, `${profileName}\n`);
  • 读取本地文件
    lib/model-profiles.js:345
    text = readFileSync(path, 'utf8');
  • 读取本地文件
    lib/model-profiles.js:379
    text = readFileSync(path, 'utf8');
  • 删除本地文件
    lib/model-profiles.js:390
    rmSync(path);
  • 写入本地文件
    scripts/contract-check.mjs:87
    mkdirSync(join(home, 'agents'), { recursive: true })
  • 写入本地文件
    scripts/contract-check.mjs:88
    mkdirSync(worktree, { recursive: true })
  • 删除本地文件递归
    scripts/contract-check.mjs:132
    rmSync(scratch, { recursive: true, force: true })
  • 读取本地文件
    scripts/smoke-boot.mjs:39
    const pkg = JSON.parse(await readFile(path.join(repoRoot, 'package.json'), 'utf8'))
  • 写入本地文件
    scripts/smoke-boot.mjs:45
    mkdirSync(profile, { recursive: true })
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:54
    const pack = spawnSync('npm', ['pack', '--pack-destination', work], { cwd: repoRoot, encoding: 'utf8' })
  • 写入本地文件
    scripts/smoke-boot.mjs:58
    writeFileSync(path.join(profile, 'cordis.yml'), '# dsh profile root — empty; the tree is composed from the bundle patches\n[]\n')
  • 写入本地文件
    scripts/smoke-boot.mjs:59
    writeFileSync(path.join(profile, 'cordis.patch.yml'), '# scratch smoke profile: no extra patch layer\n[]\n')
  • 写入本地文件
    scripts/smoke-boot.mjs:60
    writeFileSync(path.join(profile, 'package.json'), JSON.stringify({
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:76
    const install = spawnSync('npm', ['install', '--no-audit', '--no-fund', '--loglevel=error'], { cwd: profile, encoding: 'utf8' })
  • 读取本地文件
    scripts/smoke-boot.mjs:83
    if (existsSync(path.join(installedOwn, 'node_modules', '@deepseek-ai'))) {
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:90
    const dump = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:98
    const boot = spawnSync('dsh', ['--profile', 'smoke'], {
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:123
    const remove = spawnSync('dsh', ['plugin', '--profile', 'smoke', 'remove', ownName], { cwd: profile, encoding: 'utf8', env: dshEnv })
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:125
    const dumpAfter = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
  • 删除本地文件递归
    scripts/smoke-boot.mjs:132
    rmSync(work, { recursive: true, force: true })
  • 读取本地文件
    src/agents-dir.ts:179
    if (!existsSync(dir)) return { agents, broken }
  • 读取本地文件
    src/agents-dir.ts:180
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    src/agents-dir.ts:183
    const result = parseAgentMarkdown(readFileSync(path, 'utf8'), path)
  • 读取本地文件
    src/model-profiles.ts:175
    text = readFileSync(path, 'utf8')
  • 写入本地文件
    src/model-profiles.ts:201
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    src/model-profiles.ts:202
    writeFileSync(tmp, body)
  • 写入本地文件
    src/model-profiles.ts:203
    renameSync(tmp, path)
  • 删除本地文件
    src/model-profiles.ts:207
    rmSync(tmp, { force: true })
  • 读取本地文件
    src/model-profiles.ts:333
    if (existsSync(candidate)) {
  • 读取本地文件
    src/model-profiles.ts:334
    const parsed = parseProfilePinText(readFileSync(candidate, 'utf8'))
  • 写入本地文件
    src/model-profiles.ts:365
    writeFileSync(path, `${profileName}\n`)
  • 读取本地文件
    src/model-profiles.ts:385
    text = readFileSync(path, 'utf8')
  • 读取本地文件
    src/model-profiles.ts:421
    text = readFileSync(path, 'utf8')
  • 删除本地文件
    src/model-profiles.ts:432
    rmSync(path)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-commands@deepseek-ai/dsh-llm@deepseek-ai/dsh-user-questions
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 交互式切换模型配置档
  • 管理默认模型与思考等级
  • 配置各子代理模型

dsh 插件:通过宿主 ask-user 流程交互式切换命名模型配置档(默认模型、思考等级、各子代理模型)——TUI 和 web 界面共用一套实现

v0.3.12026-10-12
fix(packaging): 打包时带上 scripts/,让 postinstall 能解析到;链接器在消费方目录树里不介入 和 fan56/dsh-subagent-registry#2 是同一类缺陷:postinstall 钩子引用了 scripts/link-dsh-closure.mjs,但 files 白名单从没把它打包进去——所有非链接安装(插件管理器、普通 npm install)都会在生命周期步骤里以 MODULE_NOT_FOUND 失败。把 scripts/ 打包进去,并把链接器的退出保护改为依据只在仓库里才有的标记(tsconfig.json + .git),不再用脆弱的 src/ 检查。 补上这个盲区:新增 test/packaged-install.test.mjs,打包 tarball 后在 npm 下开启生命周期脚本安装(pnpm 启动冒烟默认会拦截依赖的生命周期脚本,这类问题就是这样漏出去的),启动冒烟改用 npm 安装,并断言链接器在消费方目录树里已退出。
v0.3.02026-09-29
杂项:把 dsh 宿主最低版本提到 0.2.0-rc.2,对等依赖 dsh-agent/dsh-commands/dsh-llm/dsh-user-questions 的最低版本也提到 0.2.0-rc.2,开发依赖固定为 rc.2,pnpm-lock 按 registry.npmjs.org 重新解析;cordis ^4.0.4 不变;README 里的最低版本已更新;测试 66 通过 / 0 失败
v0.2.02026-09-24
0.1.7-rc.1 迁移:peer 地板抬 >=rc.1(无需 settings 迁移,数据本就在插件自有 store);icon/locale 元数据
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-profile-switch 一个 dsh 插件:具名模型配置档案,你可以通过宿主原生的 ask-user 流程进行交互式切换和配置——一套实现同时适用于 TUI 和 Web 界面,并在无头环境下快速失败并给出指引。 一个模型配置档案是你的整个模型配置的快照: - 默认模型 + 思考级别(保存的 /model 选择),以及 - 可选的按子代理的模型/思考覆盖。 在不同上下文之间切换(work ↔ personal)变成一条命令,而不是一次 /model 加逐个代理的巡览。 “profile”在 dsh 中有两种不同含义——本插件指的是模型这一种。 dsh 的 profile(~/.dsh/profiles//、dsh --profile )是启动时的插件组合;它在进程运行期间无法更改。本插件所切换的模型配置档案存放在 ~/.dsh/model-profiles.json 中,在运行时生效,并且作用域限定于绑定它们的 workspace 树。 安装 dsh plugin --profile add @aiwayds/dsh-profile-switch 需要 dsh ≥ 0.2.0-rc.2(peer 依赖下限)。
展开完整文档(README 剩余 1 节)
dsh-profile-switch

一个 dsh 插件:具名模型配置档案,你可以通过宿主原生的 ask-user 流程进行交互式切换和配置——一套实现同时适用于 TUI 和 Web 界面,并在无头环境下快速失败并给出指引。

一个模型配置档案是你的整个模型配置的快照:

- 默认模型 + 思考级别(保存的 /model 选择),以及
- 可选的按子代理的模型/思考覆盖。

在不同上下文之间切换(work ↔ personal)变成一条命令,而不是一次 /model 加逐个代理的巡览。

“profile”在 dsh 中有两种不同含义——本插件指的是模型这一种。 dsh 的 profile(~/.dsh/profiles//、dsh --profile )是启动时的插件组合;它在进程运行期间无法更改。本插件所切换的模型配置档案存放在 ~/.dsh/model-profiles.json 中,在运行时生效,并且作用域限定于绑定它们的 workspace 树。

安装

dsh plugin --profile add @aiwayds/dsh-profile-switch

需要 dsh ≥ 0.2.0-rc.2(peer 依赖下限)。该插件附带本地化的 Plugin-Manager 元数据(locale/en.json / locale/zh.json)以及一个 icon.svg,因此在宿主的 Plugin Manager 中会以正确的标题、描述和图标呈现。

该插件以 inject: ['commands', 'userQuestions'] 挂载——两者都是 dsh-base 服务,因此它可以与任何界面和任何插件栈共存。

设计上无需设置: 自 dsh 0.1.7 起,插件的 static Config 就是它的设置页注册,而本插件没有声明任何设置——它的数据是向导编辑的存储和下方的树绑定,通过命令管理,而非表单字段。无需从 0.1.7 之前的 settings.yaml 迁移任何内容。

命令

/profile-switch

一个问题:此 workspace 树绑定到哪个配置档案。回答后会分层应用该配置档案:

1. 实时默认模型——当存在实时选择通道时:在 TUI 上通过 dsh-tui-pi 的选择桥接,在 Web 上通过会话控制器。当前对话会立即在新模型上继续。没有通道时,摘要会说明这一点;该路由仍会在下一个会话通过树绑定生效。
2. 树绑定——将 .dsh-profile 写入当前目录(沿树向上最近的该文件生效,类似 .nvmrc 风格),因此此树中的每个新会话都会从该配置档案组装其初始选择。其他树不受影响:切换是 workspace 作用域的,绝不是机器全局的。
3. 子代理覆盖——在应用时不写入任何位置。每个代理的运行时模型/思考在生成时由代理文件 frontmatter 基线 ⊕ 此绑定配置档案的覆盖组合而成(每次派发时重新计算,包括续接),这正是使切换完全限定于 workspace 作用域的原因。

该切换有意不写入全局的 agent-default-model 设置——那仍然是 /model 的写入操作,而跨越树边界恰恰是配置文件切换绝不能做的事。

当树被绑定时,列表还会带有 (unbind this tree):移除该固定(有保护——手工装饰的固定文件绝不会被覆盖;插件会拒绝并说明原因)。

/profile-cfg

配置向导——先是一个操作菜单,然后是匹配的流程:

| 操作 | 它会询问什么 |
| new | 名称 → 默认模型链 → 每个子代理的覆盖 |
| edit | 选择一个配置文件 → 重新回答同一条链(当前值会显示在问题中) |
| save-current | 名称 → 捕获当前实时选择(或部署默认值)→ 可选地快照每个已发现代理的基线 |
| rename | 选择一个配置文件 → 新名称 |
| delete | 选择一个配置文件 → 确认(最后一个剩余的配置文件会被拒绝) |

每个模型选择都是一条由独立的 ask-user 问题组成的三步收窄链——提供商 → 该提供商的模型 → 该模型的思考级别——因为每一步的选项都取决于前一个答案。所有三个列表都从已安装的 llm 适配器实时读取(listProviders / listModels / resolveModelInfo),因此思考级别正是适配器所提供的——绝不硬编码(llm-deepseek 路由会拒绝 medium;只有适配器知道某个模型接受什么)。没有列出任何内容的提供商会回退到手动输入模型 id;没有可选 effort 的模型会跳过思考步骤。

在任何地方取消(Esc / 关闭问题)都会中止向导,且不保存任何内容——存储只在最后写入一次。

子代理覆盖(配合 dsh-subagent-registry)

当安装了 dsh-subagent-registry 时,new/edit 向导会针对你已发现的代理(~/.dsh/agents/.md)添加一个多选,然后对每个选中的代理执行同样的提供商 → 模型 → 思考链——其中 (inherit) 位于每一步的开头。组合语义:

- 已记录的非空值优先于代理文件的 frontmatter;
- 空条目(两步都继承)记录一个显式继承;
- 配置文件中不存在的代理从基线组合。

没有该注册表时,覆盖将没有消费者,因此会跳过按代理的部分并附上说明,配置文件只保存主默认模型——检测是一个契约成员探测(composeAgentRuntime / readModelProfilesDoc / workspaceProfileName),与 dsh-tui-pi 确立的约定相同。

界面

| | TUI | web | headless |
| 问题渲染 | dsh-tui-pi 的 ask-user 停靠面板 | web 编辑器接管 | — |
| 实时默认模型切换 | 是(tui-pi 桥接) | 是(会话控制器) | 不适用 |
| 树绑定 + 存储 | 是 | 是 | 通过文件 |
| 无回答者行为 | — | — | 快速失败并给出指引 |

在没有 ask-user 回答者的界面上(headless 运行一个任务后退出),这些命令会快速失败并给出指向非交互路径的指引:写入 /.dsh-profile 并直接编辑 ~/.dsh/model-profiles.json。不会有任何挂起。

存储与文件

- ~/.dsh/model-profiles.json — 存储文件(遵循 $DSH_HOME)。带版本(version: 1),可自愈(损坏/未知文件会降级为预置的 work / personal / other 配置文件,绝不抛错),原子写入(临时同级文件 + 重命名)。此文件是一份共享契约: dsh-subagent-registry 会读取它(生成时组合),dsh-tui-pi 的会话引导也会读取它(从 pin 进行新会话预置)。Schema:

{
"version": 1,
"current": "work", // informational, last applied anywhere
"profiles": [
{
"name": "work",
"defaultModel": { "provider": "zai-coding-cn", "model": "glm-5.3", "reasoningEffort": "high" },
"agents": {
"workhorse": { "model": "volc-ark-plan/deepseek-v4-flash", "thinking": "high" },
"oldfox": {} // empty entry = explicit inherit
}
}
]
}

- /.dsh-profile — 树级 pin:一行,命名该配置文件;允许空行和 # 注释。插件在切换时覆盖它,仅当它解析为恰好一行条目时——手工修饰过的文件会被拒绝并上报,绝不会被覆盖。

与 dsh-tui-pi 的关系

模型配置文件功能从 dsh-tui-pi 迁移到了这里(此前它作为仅 TUI 面板承载该功能)。配对关系:

- 搭配提供 dshTuiModelSelection 桥接的 dsh-tui-pi: /profile-switch 也会实时切换当前 TUI 会话的默认模型;
- 搭配没有该桥接的较旧/较新 tui-pi:其他一切照常工作;实时部分降级为“此树中的下一个会话”,摘要中会说明这一点;
- tui-pi 的 /agents 管理器中的按 agent 编辑入口会以相同的 schema 写入同一个存储——两个编辑器共存;
- 建议在 TUI 上使用 dsh-tui-pi ≥ 2.18.1。 tui-pi 2.16.0–2.18.0 在将面板迁移到这里时,从它的永不中止调度列表中去掉了这些命令名,因此超过 90 秒的 /profile-cfg 会话会回显一个虚假的 aborted due to timeout(面板本身仍在工作; 2.18.1 恢复了调度)。在 web/无头界面上不存在此类防护,任何插件版本都可与任何版本配对。

任何地方都不需要手动配置配置文件:dsh plugin add 会协调 bundle 条目,bundle 补丁会挂载插件,命令会在配置文件启动的每个界面上注册。

卸载

dsh plugin --profile remove @aiwayds/dsh-profile-switch

该插件不拥有任何守护进程,也不持有任何宿主资源,因此卸载在下次启动时即可干净完成。你的数据会被有意保留:

- ~/.dsh/model-profiles.json — 如果你想删除该存储,就删掉它;
- /.dsh-profile pin — 按树删除(rm .dsh-profile);命名了不存在配置文件的 pin 不会绑定任何内容。

开发

pnpm installmarkdown
pnpm build && pnpm check && pnpm test # 单元测试套件(先构建)
node scripts/smoke-boot.mjs # 真实主机启动门禁(需要全局 dsh)
pnpm contract # 与注册表的写↔读握手
(在无法解析注册表的地方自动跳过)

契约检查在真实文件上证明了部署后的三仓库形态:插件的存储写入(pin + 每代理覆盖)通过注册表的 composeAgentRuntime 组合回来——覆盖优先,显式继承和基线回退成立,并且探测环节确认 registryDetected() 通过配置文件的同级 node_modules 解析,与实时部署中完全一致。在没有注册表的机器/配置文件上,它会降级为有文档记录的跳过,因此可以在任何地方运行。

scripts/link-dsh-closure.mjs 将 node_modules/@deepseek-ai/ 重新指向全局 dsh CLI 自身的闭包,以便类型检查和测试只看到一个 cordis。CI 在 Node 22/24 上运行相同的步骤以及启动冒烟测试,并且每日计划会监视上游 rc/stable 的漂移。

许可证 — MIT。
预览分享卡片长什么样 ↓fan56/dsh-profile-switch
分享 · DeepSeek Harness Hub
fan56/dsh-profile-switch

一个 dsh 插件:具名模型配置档案,你可以通过宿主原生的 ask-user…

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部