# dsh-market · DeepSeek Harness (DSH) 创意工坊商厦与一键资产安装插件

[English](README.md) | 中文

<p align="center">
  <img src="https://img.shields.io/npm/v/@linxin666/dsh-client-ui-market?style=flat-square" alt="Version">
  &nbsp;
  <img src="https://img.shields.io/badge/DSH-%3E%3D0.2.0--rc.2-4c6ef5?style=flat-square&amp;labelColor=454a54" alt="DSH">
  &nbsp;
  <img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License">
</p>

<p align="center">
  <strong>DeepSeek Harness（DSH）官方 Web GUI 与桌面客户端内置创意工坊商店与资产管理中心</strong><br>
  <em>一站式浏览 dsh-market.com · 主题皮肤/桌宠/预设/插件一键安装 · 在线热度榜 · 零重启热加载</em>
</p>

DSH Web GUI 与官方桌面客户端设置页的创意工坊商店卡片：唯一的「创意工坊」一级分区在 GUI 内浏览 [dsh-market.com](https://dsh-market.com) 的皮肤、宠物、插件与社区预设，并一键安装到本机；已安装内容由各自界面管理（皮肤中心、宠物、官方插件分区内的插件管理，以及本卡片预设标签页渲染的预设面板）；最前的「编辑推荐」标签固定展示一小份皮肤、宠物与社区插件清单。

## 功能

- 五个标签页：最前的「编辑推荐」固定类别 + 四类目录。「编辑推荐」按发布顺序钉住一份手工维护的皮肤 / 宠物 / 社区插件清单（不含预设），没有搜索框与分类筛选；每个条目保留其所属类别自身的安装、点赞与预览能力，本部署解析不到的引用会被丢弃。
- 四类目录（皮肤 / 宠物 / 插件 / 预设），排序与创意工坊站一致：设备点赞优先（同票回落清单顺序）、搜索框、每卡预览链接（皮肤打开实时试穿模拟器）。
- 一键安装资产（回环浏览器）：皮肤下载到 `$DSH_HOME/skins/<id>/`，宠物下载到 `$DSH_HOME/pets/<id>/` —— 这两个正是皮肤中心与宠物注册表已扫描的 DSH home 目录，无需重启（重新打开卡片即生效）。预设下载到惰性库 `$DSH_HOME/agent-presets/<id>/`，没有任何发现根扫描它；启用由预设面板把它移入 roster 的用户根。覆盖已有目录前弹确认并原子替换。
- 一键安装插件复用官方写入器：只要宿主发布了官方进程内插件管理器的 remote 面（`ctx.remote.pluginManager.installBundle`，即官方插件页所用的同一个调用）就走它——在打包桌面客户端上该管理器是唯一写入器，因为 CLI 拒绝应用自有 profile；否则回退到家族 `pluginManager` 服务（`@linxin666/dsh-client-ui-plugin-manager`）。两者都没有时降级为复制命令索引。
- 已安装插件在官方容器里管理，而不是在本卡片里：卡片的「在插件页管理」动作调用官方插件页的 `pluginNavigation.openBundle(packageName)`，在官方「插件」面板中打开该组合包页面——启停、卸载与安装诊断流本来就在那里。
- 远程浏览器只读：隐藏安装按钮，保留创意工坊站链接与复制命令兜底。
- 外链（工坊站、卡片名称、仓库、皮肤预览）在宿主注册该标签类型（alpha.2）时打开官方右侧边栏浏览器，否则照旧新开浏览器标签页。
- 每张卡片除点赞数外还显示独立的工坊安装量与插件 NPM 近 30 天下载量（仅对有 npm 包名的插件显示）；工坊安装量记录成功安装事件，NPM 下载量为 registry 公开口径，两者不与点赞合并。

## 安装

```sh
dsh plugin --profile web add @linxin666/dsh-client-ui-market
```

重启 `dsh web` 后，设置页出现「创意工坊」分区，直接展开本商店卡片（编辑推荐 / 皮肤 / 宠物 / 插件 / 预设五个类目）。皮肤中心、宠物与官方「插件」分区里的插件管理各自是独立的设置分区；预设标签页由 `@linxin666/dsh-client-ui-preset-center` 注入到本卡片声明的子槽位中渲染。

## 配置

- 启用开关：卡片在插件配置区带自己的总开关——即本包在自身 profile 行上的 `enabled` 配置，宿主据此自动生成本行的设置页；关闭后隐藏目录内容、仅保留开关本身。
- 无其他配置项；目录数据始终来自 dsh-market.com。

## 已知限制

- 远程（非回环）浏览器完全无法驱动安装，只能看到只读目录与复制命令兜底。
- 资产安装要求创意工坊站可达；清单或下载失败时，已存在的资产目录保持原样。
- 点赞按设备计（浏览器保存匿名指纹），与任何登录体系无关。

## 数据遥测

浏览器半区每个 UTC 日向 dsh-market.com 发送一次匿名安装心跳：仅含一个 localStorage 随机 ID 与本包名，无其他数据。服务端只存储该 ID 的加盐哈希，不存 IP，且只暴露聚合计数。完整契约见 [docs/telemetry.md](../../docs/telemetry.md)。

## 架构

- host 半区（`src/index.ts`）不再注册任何设置：卡片的启用开关就是本包自身的 `Config` schema，宿主据此把它作为本行的设置页提供，浏览器半区经该行的配置表单读回。该半区只挂载仅回环的网关（`/api/market/installed`、`/api/market/install-skin`、`/api/market/install-pet`、`/api/market/install-preset`）。
- 安装器核心（`src/core/installer.ts`）自行从 `dsh-market.com` 拉取清单、按保守白名单校验每个路径、原子写入（临时目录后 rename）——失败下载不会留下半成品目录；客户端从不提供 URL 或文件列表。
- 创意工坊每项资产带明确的文件清单，`scripts/market-build` 重新生成 `market/dist` 后，新皮肤包即自动可装。
- 「编辑推荐」类别读取 `manifest/editor-picks.json`：一份手工维护的引用清单（`market/editor-picks.json`，只含皮肤 / 宠物 / 插件），`scripts/market-build` 会对照自己生成的目录校验它——指向已删除或改名资产的条目会让构建失败，而不是静默消失。
- 卡片声明 keyed 子槽位 `dsh-workshop.panel`，按类目渲染一格；预设面板注册 `preset` 格并以 owner props 收到目录记录与下载网关，因此商店对所有类目只做一次清单抓取、只维护一个网关。

## 安全模型

- 安装路由仅回环可访问（与插件管理器同一门禁）；远程浏览器无法驱动。
- 下载内容全部来自 `https://dsh-market.com`（URL 由验证后的清单重建）；皮肤 CSS 由皮肤中心运行时净化后才应用。
- 清单（1 MiB）、单项资产文件数（2000）与单文件大小（200 MiB）均设上限，每次请求带 30 秒超时；超限或超时的清单/下载会明确失败，已存在的资产目录保持原样。
- 预设只安装进惰性库，绝不直接落入发现根：预设是代码（组合可以加载本地文件、在主进程内求值内联表达式），因此下载绝不能等同于运行。启用步骤与其确认由预设中心负责。
- 每次安装都会向资产目录写入 `dsh-market.provenance.json`：每个已安装文件的 sha256，钉住 `https://dsh-market.com` 来源。皮肤中心据此仅在市场皮肤的磁盘字节与市场所服务的内容哈希一致时运行其 hooks（issue #1073）；手工投放或被篡改的目录保持拒绝 hooks。
- 插件安装走与插件管理器页相同的确认与 CLI 路径。
- 卡片在调用插件管理器前校验清单安装来源：仅接受 npm 包名（可带版本标签）与纯 https:// git
  地址；ssh://、file://、http:// 及相对路径、裸仓库名一律拒绝并报错，不发起安装。
