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

DS娘桌宠 Luo-fengyi/dsh-pet

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

让 Live2D 桌宠跟随会话状态变脸并陪聊

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

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Luo-fengyi/dsh-pet
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · desktop
装得上吗
本站已真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 42 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 110 处,展示前 91 条)
  • 读取本地文件
    main.js:25
    return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'))
  • 读取本地文件
    main.js:29
    try { return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')) } catch (_) { return {} }
  • 写入本地文件
    main.js:49
    fs.writeFileSync(STATE_PATH, JSON.stringify({
  • 写入本地文件
    main.js:80
    fs.appendFileSync(LOG_PATH, '[' + stamp + ':' + process.pid + '] ' + line + '\n')
  • 读取本地文件
    main.js:86
    if (!fs.existsSync(LOG_PATH)) return
  • 读取本地文件
    main.js:87
    const lines = fs.readFileSync(LOG_PATH, 'utf8').split('\n')
  • 写入本地文件
    main.js:88
    if (lines.length > 300) fs.writeFileSync(LOG_PATH, lines.slice(-200).join('\n'))
  • 写入本地文件
    main.js:125
    fs.writeFileSync(path.join(demoDir, 'demo_' + name + '.png'), image.toPNG())
  • 写入本地文件
    main.js:154
    fs.writeFileSync(out, shot.jpeg)
  • 写入本地文件
    main.js:170
    fs.mkdirSync(outDir, { recursive: true })
  • 读取本地文件
    main.js:587
    try { return JSON.parse(fs.readFileSync(path.join(ROOT, 'assets', 'model', 'manifest.json'), 'utf8')) } catch (e) { return null }
  • 读取本地文件
    src/status-watcher.js:62
    try { dirs = fs.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()) } catch (_) { return null }
  • 读取本地文件
    src/status-watcher.js:66
    try { sessions = fs.readdirSync(projDir, { withFileTypes: true }).filter((x) => x.isDirectory()) } catch (_) { continue }
  • 读取本地文件
    src/status-watcher.js:70
    try { files = fs.readdirSync(sessionDir) } catch (_) { continue }
  • 读取本地文件
    src/status-watcher.js:75
    try { st = fs.statSync(full) } catch (_) { continue }
  • 读取本地文件
    src/settings-store.js:21
    return JSON.parse(fs.readFileSync(configPath(root), 'utf8'))
  • 写入本地文件
    src/settings-store.js:25
    fs.writeFileSync(configPath(root), JSON.stringify(next, null, 2), 'utf8')
  • 读取本地文件
    src/settings-store.js:34
    try { return JSON.parse(fs.readFileSync(secretsPath(root), 'utf8')) } catch (_) { return {} }
  • 写入本地文件
    src/settings-store.js:38
    fs.writeFileSync(secretsPath(root), JSON.stringify(next, null, 2), 'utf8')
  • 读取本地文件
    src/vision.js:43
    const text = fs.readFileSync(file, 'utf8')
  • 读取密钥环境变量
    src/vision.js:47
    return process.env.DEEPSEEK_API_KEY || null
  • 发起网络请求
    src/vision.js:137
    const res = await fetch(api.endpoint, { method: 'POST', headers, body: JSON.stringify(body) })
  • 读取本地文件
    src/zstd-frame.js:49
    const size = fs.statSync(filePath).size
  • 执行外部命令固定命令
    src/foreground.js:25
    execFile(
  • 读取本地文件
    src/chat.js:41
    const st = JSON.parse(fs.readFileSync(this.statePath, 'utf8'))
  • 写入本地文件
    src/chat.js:61
    fs.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), 'utf8')
  • 读取本地文件
    src/chat.js:114
    try { return fs.readFileSync(this.memoryPath, 'utf8').trim() } catch (_) { return '' }
  • 写入本地文件
    src/chat.js:127
    try { fs.writeFileSync(this.memoryPath, next, 'utf8') } catch (_) { return false }
  • 读取本地文件
    _tools/analyze_expressions.js:14
    const cdi = JSON.parse(fs.readFileSync(path.join(DS, 'c_0120.cdi3.json'), 'utf8'))
  • 读取本地文件
    _tools/analyze_expressions.js:31
    const files = fs.readdirSync(DS).filter((f) => f.endsWith('.exp3.json')).sort((a, b) => a.localeCompare(b, 'zh'))
  • 读取本地文件
    _tools/analyze_expressions.js:40
    try { json = JSON.parse(fs.readFileSync(path.join(DS, f), 'utf8')) } catch (e) { lines.push(label + ' 解析失败'); continue }
  • 写入本地文件
    _tools/analyze_expressions.js:64
    fs.writeFileSync(OUT, lines.join('\n'), 'utf8')
  • 读取本地文件
    _tools/analyze_motions.js:7
    const cdi = JSON.parse(fs.readFileSync(path.join(M, 'c_0120.cdi3.json'), 'utf8'))
  • 读取本地文件
    _tools/analyze_motions.js:16
    for (const f of fs.readdirSync(motionDir).filter((x) => x.endsWith('.json'))) {
  • 读取本地文件
    _tools/analyze_motions.js:17
    const m = JSON.parse(fs.readFileSync(path.join(motionDir, f), 'utf8'))
  • 写入本地文件
    _tools/analyze_motions.js:48
    fs.writeFileSync(path.join(__dirname, 'motion_analysis.txt'), out.join('\n'), 'utf8')
  • 写入本地文件
    _tools/build_assets.js:23
    fs.mkdirSync(p, { recursive: true })
  • 读取本地文件
    _tools/build_assets.js:27
    if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true })
  • 读取本地文件
    _tools/build_assets.js:31
    if (!fs.existsSync(SRC)) throw new Error('找不到源模型目录: ' + SRC)
  • 读取本地文件
    _tools/build_assets.js:41
    if (!fs.existsSync(from)) throw new Error('缺少文件: ' + f)
  • 写入本地文件
    _tools/build_assets.js:42
    fs.copyFileSync(from, path.join(OUT, f))
  • 读取本地文件
    _tools/build_assets.js:49
    const textures = fs.readdirSync(texSrc).filter((f) => f.toLowerCase().endsWith('.png')).sort()
  • 写入本地文件
    _tools/build_assets.js:50
    for (const f of textures) fs.copyFileSync(path.join(texSrc, f), path.join(texDst, f))
  • 读取本地文件
    _tools/build_assets.js:58
    const obj = JSON.parse(fs.readFileSync(from, 'utf8'))
  • 写入本地文件
    _tools/build_assets.js:60
    fs.writeFileSync(to, JSON.stringify(obj, null, 2), 'utf8')
  • 读取本地文件
    _tools/build_assets.js:63
    for (const f of fs.readdirSync(motionSrc).filter((x) => x.endsWith('.motion3.json'))) {
  • 写入本地文件
    _tools/build_assets.js:87
    fs.copyFileSync(path.join(SRC, f), path.join(OUT, 'exp', ascii + '.exp3.json'))
  • 写入本地文件
    _tools/build_assets.js:119
    fs.writeFileSync(path.join(OUT, 'ds-pet.model3.json'), JSON.stringify(model3, null, 2), 'utf8')
  • 读取本地文件
    _tools/fetch_core.js:10
    if (fs.existsSync(OUT) && fs.statSync(OUT).size > 1000) {
  • 发起网络请求
    _tools/fetch_core.js:15
    const res = await fetch(URL)
  • 写入本地文件
    _tools/fetch_core.js:18
    fs.mkdirSync(path.dirname(OUT), { recursive: true })
  • 写入本地文件
    _tools/fetch_core.js:19
    fs.writeFileSync(OUT, buf)
  • 读取本地文件
    _tools/fetch_electron.js:9
    const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'))
  • 读取本地文件
    _tools/fetch_electron.js:18
    if (fs.existsSync(exePath)) {
  • 发起网络请求
    _tools/fetch_electron.js:31
    const res = await fetch(url)
  • 写入本地文件
    _tools/fetch_electron.js:34
    fs.writeFileSync(tmpZip, buf)
  • 删除本地文件
    _tools/fetch_electron.js:39
    fs.rmSync(tmpDir, { recursive: true, force: true })
  • 执行外部命令固定命令
    _tools/fetch_electron.js:41
    execFileSync('powershell.exe', [
  • 写入本地文件
    _tools/fetch_electron.js:45
    fs.mkdirSync(distDir, { recursive: true })
  • 读取本地文件
    _tools/fetch_electron.js:47
    const entries = fs.readdirSync(tmpDir)
  • 读取本地文件
    _tools/fetch_electron.js:48
    const inner = entries.length === 1 && fs.statSync(path.join(tmpDir, entries[0])).isDirectory()
  • 读取本地文件
    _tools/fetch_electron.js:51
    for (const f of fs.readdirSync(inner)) {
  • 写入本地文件
    _tools/fetch_electron.js:54
    fs.writeFileSync(path.join(electronDir, 'path.txt'), process.platform === 'win32' ? 'electron.exe' : 'electron')
  • 发起网络请求
    _tools/fetch_file.js:14
    const res = await fetch(url, { redirect: 'follow' })
  • 写入本地文件
    _tools/fetch_file.js:17
    fs.mkdirSync(path.dirname(out), { recursive: true })
  • 写入本地文件
    _tools/fetch_file.js:18
    fs.writeFileSync(out, buf)
  • 写入本地文件
    _tools/fetch_file.js:19
    fs.writeFileSync(info, 'OK ' + res.status + ' ' + buf.length + ' bytes\n' + url + '\n')
  • 写入本地文件
    _tools/fetch_file.js:21
    fs.mkdirSync(path.dirname(info), { recursive: true })
  • 写入本地文件
    _tools/fetch_file.js:22
    fs.writeFileSync(info, 'FAIL ' + (e && e.message) + '\n' + url + '\n')
  • 读取密钥环境变量
    _tools/gh_api.js:3
    const token = process.env.GH_TOKEN
  • 请求外部主机api.github.com
    _tools/gh_api.js:10
    const res = await fetch('https://api.github.com' + path, Object.assign({}, opts, {
  • 发起网络请求
    _tools/gh_api.js:10
    const res = await fetch('https://api.github.com' + path, Object.assign({}, opts, {
  • 读取本地文件
    _tools/probe_events.js:47
    const raw = fs.readFileSync(src)
  • 写入本地文件
    _tools/probe_events.js:77
    fs.writeFileSync(out, outLines.join('\n'), 'utf8')
  • 读取本地文件
    _tools/test_assistant.js:10
    const cfg = JSON.parse(fs.readFileSync(path.join(ROOT, 'config.json'), 'utf8'))
  • 读取本地文件
    _tools/test_assistant.js:23
    if (fs.existsSync(imgPath)) {
  • 读取本地文件
    _tools/test_assistant.js:24
    const img = fs.readFileSync(imgPath)
  • 写入本地文件
    _tools/test_assistant.js:52
    fs.writeFileSync(path.join(__dirname, 'assistant_test_result.txt'), out.join('\n'), 'utf8')
  • 读取本地文件
    _tools/test_prompt_leak.js:10
    const cfg = JSON.parse(fs.readFileSync(path.join(ROOT, 'config.json'), 'utf8'))
  • 写入本地文件
    _tools/test_prompt_leak.js:45
    fs.writeFileSync(path.join(__dirname, 'prompt_leak_result.txt'), out.join('\n'), 'utf8')
  • 读取本地文件
    _tools/test_status.js:9
    const cfg = JSON.parse(fs.readFileSync(path.join(ROOT, 'config.json'), 'utf8'))
  • 读取本地文件
    _tools/test_status.js:39
    if (real && fs.existsSync(real)) {
  • 写入本地文件
    _tools/test_status.js:52
    fs.writeFileSync(dest, out.join('\n'), 'utf8')
  • 读取本地文件
    _tools/test_vision.js:15
    const yaml = fs.readFileSync(CRED, 'utf8')
  • 发起网络请求
    _tools/test_vision.js:27
    const res = await fetch(URL, { method: 'POST', headers, body: JSON.stringify(body) })
  • 读取本地文件
    _tools/test_vision.js:43
    const b64 = fs.readFileSync(IMG).toString('base64')
  • 读取本地文件
    _tools/test_vision_params.js:10
    const yaml = fs.readFileSync(CRED, 'utf8')
  • 读取本地文件
    _tools/test_vision_params.js:15
    const b64 = fs.readFileSync(IMG).toString('base64')
  • 发起网络请求
    _tools/test_vision_params.js:41
    const res = await fetch(URL, {
  • 发起网络请求
    renderer/renderer.js:333
    const res = await fetch('../assets/model/' + e.file)
  • 发起网络请求
    renderer/renderer.js:472
    const res = await fetch(MANIFEST_URL)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库
涉及外部主机:api.github.com

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

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 跟随会话状态变脸
  • 屏幕查看
  • 聊天与长期记忆

DS娘桌宠:Live2D 桌面宠物,跟随 DSH 会话状态变脸,能看屏幕、聊天、长期记忆

README

由 DeepSeek 最新模型翻译生成
dsh-pet · DS娘桌宠 把 Live2D 模型变成常驻桌面的小桌宠,并且跟着 DeepSeek Harness(DSH)的会话状态实时变脸;她还能看你的屏幕、跟你聊天、记住你说过的事。 Windows / Electron / 纯本地运行,不依赖任何云服务(除了你自己配的模型接口)。 默认适配的模型:B 站 UP 主 氵六青 发布的 DS娘模型(视频地址)。 模型版权归原作者所有,本仓库只做资源整理与程序适配,不随仓库分发模型文件。 关于代码是怎么来的:整个项目——从 Live2D 渲染、DSH 状态联动、看屏幕,到设置窗等等——都是在 DeepSeek Harness(DSH) 里由 deepseek-v4.1-flash 完成的,所以这是一份 AI 写出来的工程:主要功能都实测跑通过,但大概率还藏着不少没被发现的 bug。用的时候请多留个心眼;遇到问题欢迎提 issue。 功能 状态联动(读 DSH 会话日志,不需要 DSH 配合) | DSH 里发生的事 | 桌宠表现 | | 收到你的消息 | 感叹号 +「收到啦~」 | | 模型在思考 | 呆呆眼 +「思考中…」 | | 正在调工具 | 圆眼镜 +「正在用 终端…」(工具名自动中文化) | | 等你审批 / 回答 | 问号 +「在等你确认哦」 | | 正在输出回复 | 开心兴奋 +「正在说话…
展开完整文档(README 剩余 1 节)
dsh-pet · DS娘桌宠

把 Live2D 模型变成常驻桌面的小桌宠,并且跟着 DeepSeek Harness(DSH)的会话状态实时变脸;她还能看你的屏幕、跟你聊天、记住你说过的事。

Windows / Electron / 纯本地运行,不依赖任何云服务(除了你自己配的模型接口)。

默认适配的模型:B 站 UP 主 氵六青 发布的 DS娘模型(视频地址)。
模型版权归原作者所有,本仓库只做资源整理与程序适配,不随仓库分发模型文件。

关于代码是怎么来的:整个项目——从 Live2D 渲染、DSH 状态联动、看屏幕,到设置窗等等——都是在 DeepSeek Harness(DSH) 里由 deepseek-v4.1-flash 完成的,所以这是一份 AI 写出来的工程:主要功能都实测跑通过,但大概率还藏着不少没被发现的 bug。用的时候请多留个心眼;遇到问题欢迎提 issue。

功能

状态联动(读 DSH 会话日志,不需要 DSH 配合)

| DSH 里发生的事 | 桌宠表现 |
| 收到你的消息 | 感叹号 +「收到啦~」 |
| 模型在思考 | 呆呆眼 +「思考中…」 |
| 正在调工具 | 圆眼镜 +「正在用 终端…」(工具名自动中文化) |
| 等你审批 / 回答 | 问号 +「在等你确认哦」 |
| 正在输出回复 | 开心兴奋 +「正在说话…」 |
| 一轮结束 | 星星眼 + 吹泡泡,并按回复内容的情绪挑表情 |
| 报错 / 中断 | 晕晕 +「出错了…」 |
| 文件长时间没动 | 收回表情,安静待机 |

看屏幕:每 60 秒截一次屏(缩到 1280 宽转 JPEG),和上一张比对;有明显变化才发给模型说一句,没变化就随机静默 4~8 轮——省钱也不聒噪。

聊天:桌宠窗口上半部分有个半透明回复框,打字回车即可。她会连"刚看到的屏幕"一起理解,回复显示在气泡里并同步切表情。

记忆:她觉得值得记的事会写进 memory.txt(纯文本一整条),跨会话保留。

装扮:模型自带的头饰、眼镜、桌面道具等配件可以自由组合(六个分类各挑一件,能叠加)。右键一键随机,也能让每次启动都随机换一套。

其他:拖动改位置、点击有反应、右键菜单(大小 / 置顶 / 设置 / 新开会话 / 看一眼桌面)、窗口位置与大小自动记住。

环境要求

- Windows 10 / 11(x64)
- Node.js 20 或更高(只在安装依赖时用得到)
- 可选:DeepSeek Harness(装了才有状态联动;不装也能当普通桌宠用)

快速开始

1. 装依赖

npm install

会自动下载 Electron 运行时(约 215MB)。如果卡住或没下下来(公司网络/代理常见),手动补:

用 npmmirror 镜像直接下 zip,解压到 node_modules/electron/dist 下载地址形如: https://registry.npmmirror.com/-/binary/electron//electron-v-win32-x64.zip
node _tools/fetch_electron.js

2. 准备模型

本项目不带模型(模型版权归各作者所有)。你需要自备一个 Live2D Cubism 4 模型。

默认适配的模型是 B 站 UP 主 氵六青 制作的 DS娘模型(视频地址)——就是那种 VTube Studio 导出的结构:一个 .moc3、几张贴图、一个 motions/ 动作目录、外加一堆中文名的 .exp3.json 表情。
如果你用的就是这套,把它整个文件夹放到本项目上一级并命名为 DS,然后:

node _tools/build_assets.js

脚本会自动扫描 DS 里的 .moc3、贴图、.exp3.json(表情)、.motion3.json(动作),整理进 assets/model/,并生成一个登记了全部动作与表情的 ds-pet.model3.json (原模型的 model3.json 通常不登记动作和表情,必须补这一步)。

换成别的模型也可以,脚本顶部有文件名映射表,按你的结构改一下即可。
目前脚本里写死的动作文件名(喷水、开盖、番茄酱 等)就是按上面那套模型来的。

3. 下载 Live2D Cubism Core

Cubism Core 是 Live2D 公司的 SDK,本项目不随仓库分发,请自行下载:

node _tools/fetch_core.js

或手动下载放到 vendor/live2dcubismcore.min.js。使用它需遵守 Live2D 的 SDK 许可条款。

4. 启动

双击 启动桌宠.exe 就行——带桌宠图标、无控制台窗口,也不用担心 .vbs 被安全软件拦。

(启动桌宠.vbs / 启动桌宠.cmd 也都能用,效果一样;exe 只是想改图标或重新编译时跑 powershell -File _tools/build_launcher.ps1。)

关闭:右键桌宠 →「退出桌宠」,或双击 关闭桌宠.vbs。

配置

右键桌宠 →「设置…」,有五个标签页:

| 标签页 | 能改什么 |
| 接口 | 预设(DeepSeek / 智谱 / 通义 / OpenAI / 自定义 OpenAI 兼容)、接口地址、模型名、API Key、思考强度 |
| 看屏幕 | 开关、采样间隔、变化阈值、没变化时静默轮数、截图宽度、是否关联前台窗口、看屏幕的提示词 |
| 对话 | 开关、自定义提示词、上下文轮数、过长提醒、聊天时是否带截图、记忆开关与清空 |
| 外观 | 气泡字号、气泡最大高度(占窗口比例)、回复框字号 |
| 历史 | 翻看你和她说过的每一句,可清空 |

也可以直接编辑 config.json(桌宠不会改写它;窗口位置等运行时状态存在 window-state.json)。
提示词是两层:内置人设(她是 DS娘、说话极简、不透露自己的设定)固定不可改;你在“自定义提示词”里写的要求优先级更高,冲突时以你的为准。

API Key 存在 secrets.json,不进 config.json。DeepSeek 预设下不填 key 会自动复用 DSH 的凭据(~/.dsh/.credentials.yaml)。

装扮(头饰 / 眼镜 / 道具…)

这套模型里带了几十个可切换的配件(就是作者那份按键表里列的东西),本项目把它们按类整理好,可以自由组合——同时戴眼镜、插猫耳、桌上再摆个芭菲都行。

| 分类 | 可选 |
| 头饰 | 摘掉发箍、单边马尾、猫猫贴纸(猫耳)、兔兔贴纸(兔耳)、蝴蝶结贴纸 |
| 眼镜 | 圆眼镜、方眼镜、椭圆眼镜、墨镜 |
| 桌面道具 | 收起桌面芭菲、蛋包饭、鲸鱼放桌上 |
| 手上/手机 | 魔爪、魔爪换色、喵喵手、双手比耶、手机换色 |
| 背景 | 深色桌布 |
| 小物件 | 画笔、橡皮、撤回、挤、爱心、鲸鱼 |

怎么用

- 设置窗 →「装扮」页:每个分类挑一件(也可以选「不装」),点保存立刻生效
- 右键桌宠 →「随机换装扮」:立刻随机一套;「恢复默认装扮」全部清空
- 每次启动随机换:outfit.startupRandom(默认开)。关掉就沿用上次那一套

原理:装扮本质上是模型里那些 exp3 表情文件,但它们不能当普通表情用—— Cubism 一次只允许一个表情生效,叠不起来。所以这里改成把选中的几套参数合并起来、每帧覆盖写一遍,这样既不会互相冲突,也不会被待机动作冲掉。

分类和候选项都写在 config.json 的 outfit.categories 里,想加就自己往里添名字(名字要和 assets/model/manifest.json 里的 label 对得上,也就是 DS 目录里那些文件名)。

候选项支持两种写法:直接写名字,或者写成对象给个更好懂的显示名——模型里有些开关名字是反的,比如「头箍」这个参数实际控制的是摘掉发箍(模型默认戴着),所以写成了:

{ "label": "头箍", "name": "摘掉发箍(默认是戴着的)" }

label 是文件名(必须对得上),name 只是设置窗里显示的文字。

常用命令

node _tools/build_assets.js # 重新整理模型资源(换了模型就跑一次)
node _tools/fetch_core.js # 下载 Cubism Core
node _tools/analyze_motions.js # 分析每个动作驱动了哪些参数(模型调试用)
node _tools/test_status.js # 状态机自测(合成用例 + 真实会话)
node _tools/test_vision.js # 单测视觉接口能不能用(不碰你的屏幕)
node _tools/test_prompt_leak.js # 验证提示词优先级与"不泄密"

目录结构

dsh-pet/
main.js Electron 主进程:透明置顶窗口、菜单、拖动、设置窗 preload.js 安全桥 config.json 全部可调项(只读配置) settings.html / .js 设置窗 renderer/ 页面:Live2D 渲染、气泡、回复框、点击交互 src/ status-watcher.js DSH 会话日志 → 状态机 zstd-frame.js zstd 分帧解码(DSH 日志是逐帧追加的) mood.js 文字 → 情绪(用于挑表情) screen.js 截屏 + 缩放 + 画面变化比对 vision.js 调接口(Anthropic 格式 / OpenAI 兼容) chat.js 会话、上下文、长期记忆、内置人设 assistant.js 定时看屏幕 / 聊天调度 / 记忆写入 settings-store.js 配置读写(key 单独存) foreground.js 取当前前台窗口(借 PowerShell + P/Invoke) assets/model/ 模型资源(自己生成,不入库) vendor/ Cubism Core + PIXI + pixi-live2d-display _tools/ 构建与自测脚本启动桌宠.exe 双击启动(C# 启动器,源码 _tools/launcher.cs,重编译见 build_launcher.ps1)启动桌宠.vbs / 关闭桌宠.vbs pet.log 运行日志(排查用)

常见问题

桌宠不出现 / 白屏看 pet.log,里面有渲染进程的报错。

状态不跟着 DSH 变确认 config.json 里 watch.sessionsRoot 指向你的 DSH 会话目录(留空会自动探测 ~/.dsh/sessions),并且 DSH 确实在写 session.v3.jsonl.zstd。

被别的窗口盖住已在用最高置顶层级 + 每 1.5 秒刷新。独占全屏的游戏/播放器仍然盖不住,这是 Windows 的限制。

接口报错设置窗 →「测试连接」会直接显示 HTTP 状态和错误原文。

隐私开着看屏幕功能时,每次采样都会把整屏截图发给你配置的接口。不想用就在设置里关掉总开关,或把间隔调大、阈值调高。她也会把认为重要的事写进 memory.txt,可以随时清空。

致谢

- 模型:DS娘模型由 B 站 UP 主 氵六青 制作并公开分享,版权归原作者所有。本项目的资源整理脚本默认按其发布的结构适配;模型文件不随仓库分发,使用与再分发请以原作者的说明为准。若你把这个模型用在自己的项目里,记得署名原作者。
- 渲染:Live2D Cubism SDK(Core 需按官方条款自行下载)、 PIXI.js、pixi-live2d-display
- 桌面外壳:Electron

声明

- 代码以 MIT 协议开源,见 LICENSE。
- 本项目不包含任何 Live2D 模型。模型版权归各自作者所有,请自行准备并遵守其使用条款。
- 本项目不包含 Live2D Cubism Core。它由 Live2D 公司提供,使用需接受其 SDK 许可条款。
- vendor/ 里的 PIXI 与 pixi-live2d-display 均为 MIT 协议。
预览分享卡片长什么样 ↓Luo-fengyi/dsh-pet
分享 · DeepSeek Harness Hub
Luo-fengyi/dsh-pet

让 Live2D 桌宠跟随会话状态变脸并陪聊

这是你的仓库?认领条目

回到顶部