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

动漫2.5D桌宠 moon-partner/dsh-anime25d-pets-v2

DeepSeek Harness
moon-partner28 天前有提交已提供中文文档扫描:含敏感能力

一张图生成会眨眼摆发的动漫桌宠,实时同步 Agent 状态

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

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

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add moon-partner/dsh-anime25d-pets-v2
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · security
装得上吗
静态安装检查有提示项,装前建议看一眼 README
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 28 天前
档位由哪些信号合成

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

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

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

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

✗npm 包dsh-anime25d-pets(未发布到 npm,仅可源码安装)
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

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

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

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

读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 189 处,展示前 135 条)
  • 写入本地文件
    lib/index.js:183
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    lib/index.js:185
    writeFileSync(tmp, content, "utf8");
  • 写入本地文件
    lib/index.js:186
    renameSync(tmp, path);
  • 读取本地文件
    lib/index.js:348
    if (!existsSync(path)) return null;
  • 读取本地文件
    lib/index.js:349
    const parsed = JSON.parse(readFileSync(path, "utf8"));
  • 读取本地文件
    lib/index.js:366
    if (existsSync(path)) unlinkSync(path);
  • 删除本地文件
    lib/index.js:366
    if (existsSync(path)) unlinkSync(path);
  • 写入本地文件
    lib/index.js:456
    mkdirSync(dirname(this.path), { recursive: true });
  • 写入本地文件
    lib/index.js:460
    if (!existsSync(this.path) && existsSync(legacyPath)) copyFileSync(legacyPath, this.path);
  • 读取本地文件
    lib/index.js:460
    if (!existsSync(this.path) && existsSync(legacyPath)) copyFileSync(legacyPath, this.path);
  • 读取本地文件
    lib/index.js:463
    if (!existsSync(this.path)) writeFileSync(this.path, PERSONAS_TEMPLATE, "utf8");
  • 写入本地文件
    lib/index.js:463
    if (!existsSync(this.path)) writeFileSync(this.path, PERSONAS_TEMPLATE, "utf8");
  • 读取本地文件
    lib/index.js:470
    text = readFileSync(this.path, "utf8");
  • 删除本地文件
    lib/index.js:1808
    if (existsSync(target)) unlinkSync(target);
  • 删除本地文件
    lib/index.js:1813
    unlinkSync(file);
  • 删除本地文件
    lib/index.js:2002
    if (existsSync(target)) unlinkSync(target);
  • 删除本地文件
    lib/index.js:2007
    unlinkSync(source);
  • 发起网络请求
    lib/client.js:1094
    const response = await fetch(SETTINGS_API);
  • 发起网络请求
    lib/client.js:1104
    const response = await fetch(SETTINGS_API, {
  • 发起网络请求
    lib/client.js:1118
    const response = await fetch(CUSTOM_MODELS_API);
  • 发起网络请求
    lib/client.js:1128
    const response = await fetch(CUSTOM_MODELS_API, {
  • 发起网络请求
    lib/client.js:1171
    const response = await fetch(STATE_API);
  • 写入剪贴板
    lib/client.js:1181
    await navigator.clipboard.writeText(text);
  • 发起网络请求
    lib/client.js:1224
    fetch(MODELS_API).then((r) => r.ok ? r.json() : null).then((data) => {
  • 写入剪贴板
    lib/client.js:2237
    navigator.clipboard?.writeText(s.inboxPath);
  • 写入剪贴板
    lib/client.js:2500
    navigator.clipboard?.writeText(materializedPath);
  • 写入剪贴板
    lib/client.js:2521
    navigator.clipboard?.writeText(s.inboxPath);
  • 发起网络请求
    src/client/anime25d.ts:482
    const res = await fetch(url)
  • 发起网络请求
    src/client/creator.ts:86
    res = await fetch(url, init)
  • 写入剪贴板
    src/client/creator.ts:318
    void navigator.clipboard?.writeText(s.inboxPath)
  • 发起网络请求
    src/client/creator.ts:555
    const res = await fetch(absUrl(view.imageUrl))
  • 写入剪贴板
    src/client/creator.ts:597
    void navigator.clipboard?.writeText(materializedPath!)
  • 写入剪贴板
    src/client/creator.ts:624
    void navigator.clipboard?.writeText(s.inboxPath)
  • 发起网络请求
    src/client/index.ts:241
    state: (): Promise<PetStateView> => fetch(`${PET_API}/state`).then((res) => readJson<PetStateView>(res)),
  • 发起网络请求
    src/client/index.ts:257
    fetch(`${PET_API}/set-display`, {
  • 发起网络请求
    src/client/index.ts:323
    void fetch('/api/anime25d-pet/settings', {
  • 发起网络请求
    src/client/index.ts:339
    void fetch('/api/anime25d-pet/settings', {
  • 发起网络请求
    src/client/settings.ts:725
    const response = await fetch(SETTINGS_API)
  • 发起网络请求
    src/client/settings.ts:736
    const response = await fetch(SETTINGS_API, {
  • 发起网络请求
    src/client/settings.ts:755
    const response = await fetch(CUSTOM_MODELS_API)
  • 发起网络请求
    src/client/settings.ts:770
    const response = await fetch(CUSTOM_MODELS_API, {
  • 发起网络请求
    src/client/settings.ts:822
    const response = await fetch(STATE_API)
  • 写入剪贴板
    src/client/settings.ts:833
    await navigator.clipboard.writeText(text)
  • 发起网络请求
    src/client/settings.ts:869
    fetch(MODELS_API)
  • 删除本地文件递归
    src/config-io.test.ts:25
    rmSync(tempRoot, { recursive: true, force: true })
  • 读取本地文件
    src/config-io.test.ts:39
    expect(readFileSync(file, 'utf8')).toBe('{"v":1}')
  • 读取本地文件
    src/config-io.test.ts:41
    expect(readFileSync(file, 'utf8')).toBe('{"v":2}')
  • 读取本地文件
    src/config-io.test.ts:42
    expect(readdirSync(join(tempRoot, 'sub')).filter((n) => n.endsWith('.tmp'))).toEqual([])
  • 写入本地文件
    src/config-io.test.ts:131
    writeFileSync(file(), '{broken', 'utf8')
  • 读取本地文件
    src/config-io.test.ts:140
    expect(existsSync(join(petDataDir(), 'settings-fallback.json'))).toBe(true)
  • 写入本地文件
    src/config-io.ts:27
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    src/config-io.ts:29
    writeFileSync(tmp, content, 'utf8')
  • 写入本地文件
    src/config-io.ts:30
    renameSync(tmp, path)
  • 读取本地文件
    src/config-io.ts:183
    if (!existsSync(path)) return null
  • 读取本地文件
    src/config-io.ts:184
    const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
  • 读取本地文件
    src/config-io.ts:207
    if (existsSync(path)) unlinkSync(path)
  • 删除本地文件
    src/config-io.ts:207
    if (existsSync(path)) unlinkSync(path)
  • 写入本地文件
    src/creator/characters.ts:116
    mkdirSync(dirname(this.path), { recursive: true })
  • 读取本地文件
    src/creator/characters.ts:121
    if (!existsSync(this.path)) {
  • 读取本地文件
    src/creator/characters.ts:133
    text = readFileSync(this.path, 'utf8')
  • 删除本地文件递归
    src/creator/creator.test.ts:24
    rmSync(tempRoot, { recursive: true, force: true })
  • 读取本地文件
    src/creator/creator.test.ts:31
    const fd = readFileSync(SAMPLE_PSD).subarray(0, 26)
  • 读取本地文件
    src/creator/creator.test.ts:49
    const good = Buffer.from(readFileSync(SAMPLE_PSD).subarray(0, 26))
  • 写入本地文件
    src/creator/creator.test.ts:77
    writeFileSync(file, Buffer.alloc(100))
  • 写入本地文件
    src/creator/creator.test.ts:90
    writeFileSync(file, Buffer.alloc(10))
  • 写入本地文件
    src/creator/creator.test.ts:93
    appendFileSync(file, Buffer.alloc(10))
  • 读取本地文件
    src/creator/creator.test.ts:125
    const head = readFileSync(SAMPLE_PSD).subarray(0, 26)
  • 写入本地文件
    src/creator/creator.test.ts:126
    writeFileSync(broken, Buffer.concat([head, Buffer.alloc(64, 0x42)]))
  • 写入本地文件
    src/creator/creator.test.ts:192
    writeFileSync(file, '{broken jsonc', 'utf8')
  • 写入本地文件
    src/creator/creator.test.ts:200
    writeFileSync(file, JSON.stringify({
  • 读取本地文件
    src/creator/creator.test.ts:246
    const head = readFileSync(SAMPLE_PSD).subarray(0, 26)
  • 读取本地文件
    src/creator/creator.test.ts:260
    expect(existsSync(failedDir)).toBe(true)
  • 读取本地文件
    src/creator/creator.test.ts:261
    expect(readdirSync(failedDir).some((n) => n.includes('parse-failed'))).toBe(true)
  • 读取本地文件
    src/creator/import-psd.ts:31
    if (existsSync(target)) unlinkSync(target)
  • 删除本地文件
    src/creator/import-psd.ts:31
    if (existsSync(target)) unlinkSync(target)
  • 写入本地文件
    src/creator/import-psd.ts:33
    renameSync(source, target)
  • 写入本地文件
    src/creator/import-psd.ts:35
    copyFileSync(source, target)
  • 删除本地文件
    src/creator/import-psd.ts:36
    unlinkSync(source)
  • 写入本地文件
    src/creator/import-psd.ts:64
    mkdirSync(assetDir, { recursive: true })
  • 读取本地文件
    src/creator/import-psd.ts:91
    if (archived && existsSync(archived)) {
  • 写入本地文件
    src/creator/inbox-watch.ts:75
    mkdirSync(inboxDir, { recursive: true })
  • 写入本地文件
    src/creator/inbox-watch.ts:91
    mkdirSync(failedDir, { recursive: true })
  • 读取本地文件
    src/creator/inbox-watch.ts:93
    if (existsSync(target)) unlinkSync(target)
  • 删除本地文件
    src/creator/inbox-watch.ts:93
    if (existsSync(target)) unlinkSync(target)
  • 写入本地文件
    src/creator/inbox-watch.ts:95
    renameSync(file, target)
  • 写入本地文件
    src/creator/inbox-watch.ts:97
    copyFileSync(file, target)
  • 删除本地文件
    src/creator/inbox-watch.ts:98
    unlinkSync(file)
  • 读取本地文件
    src/creator/inbox-watch.ts:116
    entries = readdirSync(dir, { withFileTypes: true })
  • 写入本地文件
    src/creator/inbox-watch.ts:228
    mkdirSync(inboxDir, { recursive: true })
  • 读取本地文件
    src/creator/psd-validate.ts:88
    size = statSync(filePath).size
  • 读取本地文件
    src/creator/psd-validate.ts:121
    if (existsSync(join(root, 'assets', 'vendor', 'ag-psd.min.js'))) return root
  • 读取本地文件
    src/creator/psd-validate.ts:128
    const source = readFileSync(join(root, 'assets', 'vendor', 'ag-psd.min.js'), 'utf8')
  • 读取本地文件
    src/creator/psd-validate.ts:174
    const buffer = readFileSync(filePath)
  • 删除本地文件递归
    src/custom-models.test.ts:48
    rmSync(dir, { recursive: true, force: true })
  • 读取本地文件
    src/custom-models.test.ts:56
    expect(readFileSync(storePath, 'utf8')).toContain('自定义模型配置文件')
  • 写入本地文件
    src/custom-models.test.ts:73
    writeFileSync(
  • 写入本地文件
    src/custom-models.test.ts:97
    writeFileSync(storePath, '{broken jsonc', 'utf8')
  • 写入本地文件
    src/custom-models.ts:81
    mkdirSync(dirname(this.path), { recursive: true })
  • 读取本地文件
    src/custom-models.ts:86
    if (!existsSync(this.path)) writeFileSync(this.path, CUSTOM_MODELS_TEMPLATE, 'utf8')
  • 写入本地文件
    src/custom-models.ts:86
    if (!existsSync(this.path)) writeFileSync(this.path, CUSTOM_MODELS_TEMPLATE, 'utf8')
  • 读取本地文件
    src/custom-models.ts:96
    text = readFileSync(this.path, 'utf8')
  • 写入本地文件
    src/custom-models.ts:133
    writeFileSync(this.path, serializeCustomModels(normalized), 'utf8')
  • 写入本地文件
    src/local-models.test.ts:13
    writeFileSync(modelFile, '8BPS', 'utf8')
  • 写入本地文件
    src/local-models.test.ts:14
    mkdirSync(join(modelDir, 'textures'), { recursive: true })
  • 写入本地文件
    src/local-models.test.ts:15
    writeFileSync(join(modelDir, 'textures', 'body.png'), 'png', 'utf8')
  • 删除本地文件递归
    src/local-models.test.ts:19
    rmSync(modelDir, { recursive: true, force: true })
  • 读取本地文件
    src/local-models.ts:26
    const st = statSync(p)
  • 读取本地文件
    src/local-models.ts:28
    let files = readdirSync(p).filter((f) => f.toLowerCase().endsWith('.psd'))
  • 读取本地文件
    src/local-models.ts:30
    files = readdirSync(p).filter((f) => f.toLowerCase().endsWith('.model3.json'))
  • 读取本地文件
    src/local-models.ts:59
    if (!existsSync(file)) return null
  • 写入本地文件
    src/models-host.test.ts:16
    writeFileSync(psd, '8BPS', 'utf8')
  • 删除本地文件递归
    src/models-host.test.ts:21
    rmSync(tempDir, { recursive: true, force: true })
  • 读取本地文件
    src/models-host.ts:30
    readFileSync(join(packageRoot(), 'src/presets/presets.jsonc'), 'utf8'),
  • 删除本地文件递归
    src/persist.test.ts:16
    rmSync(tempRoot, { recursive: true, force: true })
  • 读取本地文件
    src/persist.test.ts:42
    expect(existsSync(displayFilePath())).toBe(false)
  • 读取本地文件
    src/persist.test.ts:47
    expect(existsSync(displayFilePath())).toBe(true)
  • 读取本地文件
    src/persist.test.ts:50
    expect(existsSync(`${displayFilePath()}.tmp`)).toBe(false)
  • 读取本地文件
    src/persist.test.ts:55
    expect(Object.keys(JSON.parse(readFileSync(displayFilePath(), 'utf8'))).sort()).toEqual(['bottom', 'right', 'size'])
  • 写入本地文件
    src/persist.test.ts:59
    mkdirSync(join(tempRoot, 'anime25d-pet'), { recursive: true })
  • 写入本地文件
    src/persist.test.ts:60
    writeFileSync(displayFilePath(), '{broken', 'utf8')
  • 写入本地文件
    src/persist.test.ts:69
    writeFileSync(
  • 读取本地文件
    src/persist.test.ts:75
    expect(existsSync(displayFilePath())).toBe(true)
  • 读取本地文件
    src/persist.test.ts:77
    const legacy = JSON.parse(readFileSync(legacyFile(), 'utf8'))
  • 写入本地文件
    src/persist.test.ts:83
    writeFileSync(legacyFile(), JSON.stringify({ enabled: true }), 'utf8')
  • 写入本地文件
    src/persist.test.ts:88
    writeFileSync(legacyFile(), JSON.stringify({ right: 1, bottom: 1, size: 50 }), 'utf8')
  • 读取本地文件
    src/persist.ts:71
    const display = extractDisplay(JSON.parse(readFileSync(displayFilePath(), 'utf8')))
  • 读取本地文件
    src/persist.ts:77
    const legacy = extractDisplay(JSON.parse(readFileSync(legacyFilePath(), 'utf8')))
  • 写入本地文件
    src/personas.ts:135
    mkdirSync(dirname(this.path), { recursive: true })
  • 写入本地文件
    src/personas.ts:142
    if (!existsSync(this.path) && existsSync(legacyPath)) copyFileSync(legacyPath, this.path)
  • 读取本地文件
    src/personas.ts:142
    if (!existsSync(this.path) && existsSync(legacyPath)) copyFileSync(legacyPath, this.path)
  • 读取本地文件
    src/personas.ts:148
    if (!existsSync(this.path)) writeFileSync(this.path, PERSONAS_TEMPLATE, 'utf8')
  • 写入本地文件
    src/personas.ts:148
    if (!existsSync(this.path)) writeFileSync(this.path, PERSONAS_TEMPLATE, 'utf8')
  • 读取本地文件
    src/personas.ts:158
    text = readFileSync(this.path, 'utf8')
  • 读取本地文件
    src/routes.ts:339
    return readFile(file).then((body) => {
  • 读取本地文件
    src/routes.ts:366
    return readFile(join(packageRoot, 'assets', file.name)).then((body) => {
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-settings@deepseek-ai/dsh-user-approval
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 一张图生成动漫桌宠
  • 眨眼摆发动画
  • 同步Agent状态

DSH 0.2.0 兼容的动漫 2.5D 桌面宠物

README

由 DeepSeek 最新模型翻译生成
dsh-anime25d-pets 🐾 Anime2.5DRig × DSH 桌宠:借助 see-through,只凭一张干净背景的图像,即可获得带自动装配、发丝物理、表情动画和状态镜像的桌宠。 Demo 功能特色 - 🎨 一张图,自动成宠:只需一张干净背景的角色图像,Anime2.5DRig 自动完成装配,无需 Live2D 模型或复杂的手工设置。 - 🌊 发丝物理 & 表情动画:自动装配后自带发丝物理、眨眼、随机小动作、随机开口说话和状态表情。 - 🖼️ 状态镜像:真实反映 DSH Agent 的思考 / 空闲 / 出错 / 完成 / 等待审批五种状态。 - 💡 状态呼吸灯:右下角呼吸灯随状态变色(绿/黄/红/蓝/紫),一眼掌握 Agent 状态。 - 🔄 左右翻转:浮动面板一键镜像桌宠,同一个角色左右朝向自由切换。 - ⚙️ 轻量调节面板:鼠标悬停显示设置按钮,浮动面板可实时调整 34 个 2.5D 参数,并支持随机说话 / 随机动作 / 镜像翻转。 - 🎛️ 性能与外观:DSH 设置页可调整 FPS 限制(30 / 60 / 无限制)和透明度(0~1,含气泡)。 - 🐾 多模型 & 人设:支持内置预设模型与自定义 PSD 模型,并为人设配置台词与状态表现。
展开完整文档(README 剩余 1 节)
dsh-anime25d-pets 🐾

Anime2.5DRig × DSH 桌宠:借助 see-through,只凭一张干净背景的图像,即可获得带自动装配、发丝物理、表情动画和状态镜像的桌宠。

Demo

功能特色

- 🎨 一张图,自动成宠:只需一张干净背景的角色图像,Anime2.5DRig 自动完成装配,无需 Live2D 模型或复杂的手工设置。
- 🌊 发丝物理 & 表情动画:自动装配后自带发丝物理、眨眼、随机小动作、随机开口说话和状态表情。
- 🖼️ 状态镜像:真实反映 DSH Agent 的思考 / 空闲 / 出错 / 完成 / 等待审批五种状态。
- 💡 状态呼吸灯:右下角呼吸灯随状态变色(绿/黄/红/蓝/紫),一眼掌握 Agent 状态。
- 🔄 左右翻转:浮动面板一键镜像桌宠,同一个角色左右朝向自由切换。
- ⚙️ 轻量调节面板:鼠标悬停显示设置按钮,浮动面板可实时调整 34 个 2.5D 参数,并支持随机说话 / 随机动作 / 镜像翻转。
- 🎛️ 性能与外观:DSH 设置页可调整 FPS 限制(30 / 60 / 无限制)和透明度(0~1,含气泡)。
- 🐾 多模型 & 人设:支持内置预设模型与自定义 PSD 模型,并为人设配置台词与状态表现。
- ✨ 创作向导:浮动面板一键打开「创作新形象」——输入描述出图(直连文生图 backend)→ 满意后自动存参考图并打开 See-Through 分层 → 把 PSD 存进收件夹即自动校验、导入、登记、切换桌宠,全程无需手动配置模型。

兼容性

| 项目 | 要求 |
| DeepSeek Harness (DSH) | 已验证 0.1.5-rc.1 |
| Node.js | ^22.19.0 || >=24.0.0 |
| 包管理器 | pnpm / npm / bun |
| 浏览器 | Chrome / Edge / Firefox / Safari(需支持 WebGL) |
| 最后验证日期 | 2026-09-17 |

DSH 0.1.2-rc.1 兼容说明:该版本移除了 @deepseek-ai/dsh-settings 的运行时导出 settingsNamespace。本插件已改为直接使用 namespace 字符串,并保留类型层面的 SettingsNamespace 兼容层;因此同时兼容 0.1.0-rc.6 与 0.1.2-rc.1。

DSH 0.1.5-rc.x 兼容说明:该版本移除了会话列表行上的 SessionSummary.pendingInteraction,待审批 / 待提问 / 待计划确认改由客户端 uiSession.pendingInteractions 统一发布。桌宠的「等待审批」状态已改为订阅该客户端存储(src/client/pending-state.ts),并保留对旧版 Host approval/request 的回落;dsh.client.inject 同步改为真实包名。

安装与卸载

安装(推荐:从 Release / GitHub 安装)

dsh plugin --profile web add github:coldfish486/dsh-anime25d-pets

本插件随包附带已编译好的 lib/ 产物,安装后无需额外构建。
如通过 Release 包或 npm 安装,同样直接可用。

安装(源码开发)

git clone https://github.com/coldfish486/dsh-anime25d-pets.git
cd dsh-anime25d-pets
pnpm install
pnpm build
dsh plugin --profile web add /path/to/dsh-anime25d-pets

升级

从 GitHub 安装的可重新执行 add 拉取最新版,或进入插件目录后:
cd dsh-anime25d-pets
git pull
pnpm install
pnpm build
重新安装/重启 DSH

Release 正式包已包含 lib/ 产物,普通用户升级时不需要本地构建。

禁用

在 DSH 设置 →「桌宠配置」中关闭"启用桌宠"开关即可。

彻底移除

dsh plugin --profile web remove dsh-anime25d-pets
或手动删除: /root/.dsh/profiles/web/node_modules/dsh-anime25d-pets/ 并从 /root/.dsh/profiles/web/package.json 中移除引用

快速开始

最小配置示例

1. 安装插件(见上文)
2. 启动 DSH,右下角出现默认宠物(内置 Anim2.5DRig 示例 PSD 模型)
3. 添加自己的 PSD 模型:
- 打开 DSH 设置 →「桌宠配置」→「我的模型」
- 填写名称和 .psd 地址(支持 HTTP URL 或本地绝对路径)
- 点击保存
4. 使用:
- 将鼠标移到桌宠上,右下角会浮出 ⚙ 设置按钮,点击打开角色调节面板
- 拖动滑块实时调整头部/眼睛/眉毛/嘴巴/发型/身体参数
- 切换"随机开口说话""随机小动作"和"左右翻转桌宠"开关
- 在 DSH「桌宠配置」设置页可调整帧率限制(30/60/无限制)和透明度(含气泡)
- 拖动桌宠到任意位置(自动保存)

可复现示例

settings.yaml 中 anime25d-pet 部分的参考配置 anime25d-pet: enabled: true size: 200 model: sample # 使用内置示例模型 animeParams: angleX: 0.2 # 左右转头 mouthOpen: 0.3 # 嘴巴张开 talk: true # 随机开口说话 rand: false # 随机小动作 flip: false # 左右镜像翻转 fpsLimit: 30 # 帧率限制(30 / 60 / 0=无限制) opacity: 1.0 # 宠物透明度(0~1,含气泡) persona: tsundere

配置说明

settings.yaml 配置项

| 配置项 | 类型 | 默认值 | 说明 |
| enabled | boolean | true | 插件总开关 |
| size | number | 160 | 宠物尺寸(40~400 px) |
| model | string | sample | 模型 ID(内置 preset 或自定义模型 ID) |
| animeParams | object | {} | Anime2.5D 参数滑块(34 个参数) |
| talk | boolean | false | 随机开口说话 |
| rand | boolean | false | 随机小动作 |
| flip | boolean | false | 左右镜像翻转桌宠(浮动面板同步管理) |
| fpsLimit | number | 30 | 帧率限制(30 / 60 / 0=无限制) |
| opacity | number | 1 | 宠物透明度(0~1,含气泡,DSH 设置页可调) |
| persona | string | tsundere | 人设 ID |
| creatorBackendUrl | string | http://127.0.0.1:8000 | ✨ 创作向导直连的文生图 backend 地址(须在 backend 的 CORS_ORIGINS 白名单内) |
| developerMode | boolean | false | 开发者模式 |
| debug | boolean | false | 调试面板 |
| showTapZones | boolean | false | 显示点击分区 |

Anime2.5D 参数(34 个)

| 分组 | 参数 | 范围 | 默认 |
| 头部姿态 | angleX / angleY / angleZ | -1 ~ 1 | 0 |
| 眼睛 | eyeOpenL / eyeOpenR | 0 ~ 1 | 1 |
| 眼睛 | eyeX / eyeY | -1 ~ 1 | 0 |
| 眼睛 | irisScale | 0.5 ~ 1.3 | 1 |
| 眼睛 | eyeScaleL / eyeScaleR | 0.5 ~ 1.5 | 1 |
| 眼睛 | eyeEase | 0 ~ 1 | 0.3 |
| 眼睛 | eyeCY / eyeCAng | -1 ~ 1 | 0 |
| 眉毛 | brow | -1 ~ 1 | 0 |
| 眉毛 | browAngSym / browAngL / browAngR | -1 ~ 1 | 0 |
| 嘴巴 | mouthOpen | 0 ~ 1 | 0 |
| 嘴巴 | mouthForm | -1 ~ 1 | 0 |
| 嘴巴 | mouthCY / mouthCAng | -1 ~ 1 | 0 |
| 嘴巴 | mouthEase | 0 ~ 1 | 0.45 |
| 嘴巴 | mouthScale | 0.5 ~ 1.5 | 1 |
| 发型 | fhAmp | 0 ~ 3 | 2 |
| 发型 | fhSoft | 0 ~ 2 | 0.4 |
| 发型 | bangL / bangC / bangR | -1 ~ 1 | 0 |
| 身体物理 | body | -1 ~ 1 | 0 |
| 身体物理 | armY / armPos | -1 ~ 1 | 0 |
| 身体物理 | bust | 0 ~ 5 | 2.5 |
| 身体物理 | bustY | -1 ~ 3 | 1 |
| 身体物理 | physAmp | 0 ~ 3 | 2 |
| 身体物理 | soft | 0 ~ 2 | 2 |

环境变量

| 变量 | 说明 |
| DSH_HOME | DSH 配置目录(默认 ~/.dsh) |

敏感项

无。本插件不存储凭据、不访问用户敏感数据。

权限与数据

| 数据/资源 | 访问方式 | 说明 |
| 本地 PSD 文件 | node:fs 读取 | 仅在用户配置了本地模型路径时读取 |
| 设置配置 | DSH settings API | 读写 settings.yaml |
| 自定义模型配置 | ~/.dsh/anime25d-pet/custom-models.jsonc | JSONC 格式 |
| 角色档案 / 收件夹 | ~/.dsh/anime25d-pet/characters.jsonc、inbox/、characters/<id>/ | ✨ 创作向导产物;校验失败的 PSD 隔离到 inbox/_failed/ |
| 静态资源 | HTTP 同源路由 | /pet-assets/、/pet-local-models/、/api/anime25d-pet/creator/* |
| 网络访问 | 仅本机 | ✨ 向导会请求本机文生图 backend(默认 http://127.0.0.1:8000,可用 creatorBackendUrl 修改);除用户配置的远程 PSD URL 外不访问外网 |
| 摄像头 | 无 | 不访问摄像头 |

常见问题排查

常见错误

| 现象 | 原因 | 解决 |
| 紫色 🐾 方块 | 模型加载失败 | 检查模型路径是否正确;检查 assets/lib/ 是否存在 |
| 空白画面 | WebGL 渲染问题 | 检查浏览器是否支持 WebGL;检查 canvas 尺寸 |
| 参数不生效 | 自动动画覆盖 | 手动设置参数后自动动画会跳过;开启自动动画会释放手动锁定 |
| 重启后参数丢失 | 持久化失败 | 检查 settings.yaml 中 anime25d-pet 配置是否存在 |
| 点击分区不准确 | 坐标系不匹配 | 确认 getBounds() 使用 CSS 坐标系 |

日志位置

DSH Host 日志:/var/log/dsh/dsh-web.log

回滚

通过 DSH 插件系统移除
dsh plugin --profile web remove dsh-anime25d-pets

或手动删除插件目录
rm -rf /root/.dsh/profiles/web/node_modules/dsh-anime25d-pets/

开发指南

克隆仓库
git clone https://github.com/coldfish486/dsh-anime25d-pets.git
cd dsh-anime25d-pets

安装依赖
pnpm install

类型检查
pnpm run typecheck

测试
pnpm test

静态检查 + 宿主集成冒烟(53 断言)
pnpm run lint
pnpm run smoke

构建
pnpm build

构建产物在 lib/ 目录

项目结构

dsh-anime25d-pets/
├── src/
│ ├── index.ts # Host 入口(插件 Config / 生命周期) │ ├── service.ts # 宠物状态机 / SSE 推送 │ ├── routes.ts # HTTP 路由 / 静态资源 │ ├── models.ts # 模型类型定义 │ ├── models-host.ts # 模型 URL 解析 │ ├── local-models.ts # 本地 PSD 路径映射 │ ├── presets/ # 内置模型清单 │ ├── creator/ # 创作向导宿主侧:角色档案 / 收件夹五层管线 / PSD 校验 / 导入 │ └── client/ │ ├── index.ts # 桌宠主逻辑(加载/交互/面板) │ ├── creator.ts # ✨ 创作向导(四步 UI) │ ├── anime25d.ts # Anime2.5D 渲染引擎适配 │ ├── settings.ts # DSH 设置面板 │ └── personas.ts # 人设台词 ├── scripts/ │ └── smoke-host.mjs # 宿主集成冒烟(pnpm run smoke) ├── assets/ │ ├── vendor/ # ag-psd 等第三方库 │ ├── lib/ # rigger 等 Anime2.5DRig 资源 │ ├── models/ # 示例模型 │ ├── icons/ # 图标 │ └── demo.gif # 效果演示图 ├── lib/ # 构建产物(pnpm build 生成) ├── shared/ # DSH 构建配置 ├── package.json └── tsconfig.json

贡献

欢迎提交 PR!请确保:
1. pnpm run typecheck 通过
2. pnpm test 通过
3. 修改用户可感知行为时更新 README

许可证与安全

许可证

本项目采用 MIT License(见 LICENSE)。

第三方组件许可

| 组件 | 用途 | 许可证 |
| Anime2.5DRig | WebGL 渲染引擎 / 自动装配 | MIT |
| ag-psd | PSD 解析器 | MIT |
| dsh-live2d-pets | 桌宠插件框架 | MIT |

版权声明

- PSD 模型资产:assets/lib/eye_close.psd、assets/lib/mouth_close.psd、assets/models/sample.psd 来自 Anime2.5DRig 仓库(MIT License),但 PSD 内的美术作品版权归各原作者所有。发布或分发前请确认版权。
- Live2D 官方模型:原 dsh-live2d-pets 内置的 Hiyori/Haru 等模型不再分发。

安全

如发现安全问题,请通过 GitHub Issues 私密报告(不要公开漏洞详情)。
预览分享卡片长什么样 ↓moon-partner/dsh-anime25d-pets-v2
分享 · DeepSeek Harness Hub
moon-partner/dsh-anime25d-pets-v2

一张图生成会眨眼摆发的动漫桌宠,实时同步 Agent 状态

回到顶部