# 模型命令（@fufu1437/dsh-model-commands）

一个 DeepSeek Harness（DSH）插件：**在设置页里写你自己的技能（skill）。**

每条条目都会发布进 Harness 的**技能注册表**，行为和内置技能完全一样：模型目录里只有一行（`name` + `description`），需要时由内置的 `skill` 工具加载正文；**插件不执行任何东西**——技能是模型读的文本，不是被跑起来的命令。

> English: [README.md](README.md)

## 为什么走技能而不是工具

DSH 本来就带了"按需加载"这套机制：技能目录每条常驻只占一行，正文只在模型真正需要时付一次费。这个插件在它之上加的是**一张表、一个设置页、一个注册表 provider**——它自己**不注册任何工具**，所以模型没有新东西要学，也没有第二份状态需要同步。

| | 本插件声明的 | 内置技能（`skills` 目录） |
|---|---|---|
| 存在哪 | `<DSH_HOME>/model-commands/skills.json` | 磁盘上的文件 |
| 谁来编辑 | 设置 → 模型命令 | 你的编辑器 |
| 模型看到的行为 | 完全一致 | 完全一致 |

## 页面

「设置 → 模型命令」每条一个卡片：名称、徽章（`用户创建`/`模型创建`、`已停用`、`仅我可调用`）、说明，以及一行文字操作（`编辑` / `停用` / `删除`）。右上角的圆形 **+** 打开编辑器：

| 字段 | 含义 |
|---|---|
| **名称** | kebab-case（`disk-usage`），模型加载时用的键。 |
| **说明** | 必填。常驻目录里唯一的内容，也是模型做判断的全部依据。 |
| **何时使用** | 可选，更具体的触发场景，同样进目录。 |
| **正文** | 模型加载技能后读到的 Markdown。整段粘贴 `--help` 也没问题（上限 65536 字符）。 |
| **模型可调用** | 默认开：进入模型的技能目录。 |
| **我也可以调用** | 默认关：同时出现在人类可用的命令入口。 |
| **启用** | 关掉后条目仍在表里，但不进目录。 |

保存会原子写入整张表、重新发布、并使技能目录失效，所以模型**下一步**就能看到改动——不用重启，不用重载。

校验在 Host 侧，页面只负责显示它的诊断：**错误**（名称不合法、说明或正文为空、正文超长、重名）会**阻止保存**；**提示**（名字与其他 provider 冲突、只有你能调用）只作为可关闭的横幅出现，不拦保存。

## 配置

```yaml
- id: fufu-model-commands
  name: '@fufu1437/dsh-model-commands'
  config:
    storePath: /home/me/.dsh/model-commands/skills.json
    maxSkills: 200
```

| 字段 | 默认 | 含义 |
|---|---|---|
| `storePath` | `<DSH_HOME>/model-commands/skills.json` | 表的位置。 |
| `maxSkills` | `200` | 条目上限。 |

## 安装

面向 DSH 0.2.x（profile 由 pnpm 管理）。`engines.dsh` 声明
`>=0.2.1-rc.2 <0.3.0`，0.2.1 的插件管理器在安装与激活前会校验它。

在 Harness 内一次 `install_bundle` 就够了：加 profile 依赖、选中 bundle 层、并重载当前 profile：

```
install_bundle   target: @fufu1437/dsh-model-commands
                 target: link:/绝对路径/dsh-model-commands   # 本地开发
```

在 shell 里，就是管理器替你做的两步：

```bash
dsh plugin --profile web add @fufu1437/dsh-model-commands
# 再选中 bundle：设置 → 插件，或把包名追加到
# ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles
```

**刷新一次 Web 页面**以加载浏览器半部。

### DSH 升级之后

升级可能重建 profile，把第三方 bundle 丢掉：依赖和 `dsh.profile.bundles`
条目都会消失，而 `cordis.patch.yml` 仍在，于是插件静默地不再加载。重跑上面的安装步骤即可；
`<DSH_HOME>/model-commands/skills.json` 里的表不受影响。

### 迭代开发

`npm run check && npm test` 不需要 Harness 就能验证纯逻辑。

- **`client.js`** 由客户端模块注册表从磁盘读取：把 `<profile>/cordis.patch.yml` 里这一行的 `disabled` 翻转一次（`true` 再 `false`）让插件行重建，然后刷新页面。
- **`index.js`** 需要模块监听或重启 Harness。本机这个 profile 已经为这个目录打开了监听（`hmr` 的 `base` 指向插件目录、`root: ["."]`），所以保存 `index.js` 会自动重载。

## 行为与边界

- **不执行任何东西**：读一个 JSON 文件、发布技能、提供两个受围栏保护的路由。没有 shell、没有沙箱、不评估用户代码。
- 表是唯一真相，发布出去的目录是派生物；保存先落盘、再在同一步重新发布。
- 写入是原子的（临时文件 + rename）；某一行不合法只单独报出，不会丢掉其他条目。
- 两个路由都先经过 `connection.requestRejection` 信任围栏，未认证请求得到 401/403。
- 只使用 `node:` 内置模块，**不依赖任何 `@deepseek-ai/*` 私有包**。
- 与别的 provider 同名的技能只会给**提示**而不是报错：注册表按层和 rank 决定谁生效，两者中会有一个被遮蔽。

## 从 0.2.x 迁移

0.2.0 及以前，一条条目声明的是一段命令行，插件为每条注册一个模型工具。那个模型已经取消：条目现在就是技能，存在 `skills.json`。旧的 `commands.json` **原样保留但不再读取**——想把里面的内容拿回来，就把它粘进新的编辑器；如果你有一整个目录的文件，直接用内置的文件系统技能根目录（例如 `~/.dsh/skills/`）更合适。

## 还没做（后续迭代）

- 模型自己创建条目（类似 `skill_create` 的工具集 + 用户审批），对齐 `dsh-custom-tool` 让模型自己长工具的做法。
- 导入/导出、排序。
- 按项目分区（provider 已经能拿到视图的 `cwd`，剩下的是存储决策而不是 API 问题）。
