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

桌宠伴侣 Plus youhuangqing/dsh-pet-plus

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

养亲密度、开番茄钟,让桌宠陪你专注工作

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · security
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 22 天前
档位由哪些信号合成

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

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

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 153 处,展示前 123 条)
  • 读取本地文件
    runtime/electron-helper/main.js:162
    const v = Number(JSON.parse(readFileSync(dpiCacheFile(), 'utf8')).scaleFactor);
  • 写入本地文件
    runtime/electron-helper/main.js:171
    writeFileSync(dpiCacheFile(), JSON.stringify({ scaleFactor: value }), 'utf8');
  • 执行外部命令来自配置/变量
    runtime/electron-helper/main.js:195
    out = execFileSync(process.execPath, [__filename, '--dsh-pet-plus-dpi-probe'], opts);
  • 读取本地文件
    runtime/electron-helper/main.js:602
    const data = await fsPromises.readFile(resp.file);
  • 写入本地文件
    runtime/electron-helper/main.js:996
    writeFileSync(smokeOut, image.toPNG());
  • 发起网络请求
    runtime/electron-helper/renderer.js:49
    const res = await fetch(BASE + '/config', { cache: 'no-store' });
  • 发起网络请求
    runtime/electron-helper/sprite.js:1097
    fetch(BASE + '/config', {
  • 发起网络请求
    runtime/electron-helper/sprite.js:1117
    fetch(BASE + '/config', {
  • 发起网络请求
    runtime/electron-helper/sprite.js:1149
    fetch(BASE + '/pomodoro', { method: 'POST', headers: { 'content-type': 'application/json' }, body })
  • 发起网络请求
    runtime/electron-helper/sprite.js:1157
    const r = await fetch(BASE + (kind === 'stats' ? '/stats?days=1' : '/pomodoro'));
  • 发起网络请求
    runtime/electron-helper/sprite.js:1324
    fetch(BASE + '/reload', { method: 'POST' }).catch((e) => {
  • 发起网络请求
    src/client/notify.ts:129
    const r = await fetch('/dsh-pet-plus-7340/config');
  • 发起网络请求
    src/client/pet.ts:349
    fetch(assetUrl, { cache: 'default', signal: ac.signal })
  • 发起网络请求
    src/client/pet.ts:1354
    void fetch('/dsh-pet-plus-7340/pomodoro', {
  • 发起网络请求
    src/client/pet.ts:1370
    void fetch('/dsh-pet-plus-7340/config', {
  • 发起网络请求
    src/client/pet.ts:1392
    void fetch('/dsh-pet-plus-7340/config', {
  • 发起网络请求
    src/client/pet.ts:1413
    const r = await fetch(url);
  • 发起网络请求
    src/client/pet.ts:1687
    const r = await fetch('/dsh-pet-plus-7340/config');
  • 发起网络请求
    src/client/settings.ts:932
    fetch('/dsh-pet-plus-7340/config/meta')
  • 发起网络请求
    src/client/settings.ts:974
    fetch('/dsh-pet-plus-7340/config')
  • 发起网络请求
    src/client/settings.ts:1014
    fetch('/dsh-pet-plus-7340/models')
  • 发起网络请求
    src/client/settings.ts:1156
    const res = await fetch('/dsh-pet-plus-7340/config' + (force === true ? '?force=1' : ''), {
  • 发起网络请求
    src/client/settings.ts:1193
    const res = await fetch('/dsh-pet-plus-7340/config', { method: 'POST' });
  • 写入本地文件
    src/host/broadcast.test.ts:44
    mkdirSync(memesDir, { recursive: true });
  • 写入本地文件
    src/host/broadcast.test.ts:45
    writeFileSync(join(memesDir, `${IN_POOL}.png`), 'x');
  • 写入本地文件
    src/host/broadcast.test.ts:46
    writeFileSync(join(memesDir, `${IN_POOL2}.png`), 'x');
  • 删除本地文件递归
    src/host/broadcast.test.ts:61
    rmSync(tempRoot, { recursive: true, force: true });
  • 写入本地文件
    src/host/broadcast.test.ts:200
    mkdirSync(ownDir, { recursive: true });
  • 写入本地文件
    src/host/broadcast.test.ts:201
    writeFileSync(join(ownDir, '自家图.png'), 'x');
  • 写入本地文件
    src/host/config.test.ts:94
    writeFileSync(paths.defaultFile, JSON.stringify(BASE));
  • 写入本地文件
    src/host/config.test.ts:95
    writeFileSync(paths.userFile, JSON.stringify(suite.overlay));
  • 删除本地文件递归
    src/host/config.test.ts:102
    rmSync(dir, { recursive: true, force: true });
  • 写入本地文件
    src/host/config.test.ts:175
    writeFileSync(paths.defaultFile, JSON.stringify({ ...BASE, ...baseExtra }));
  • 写入本地文件
    src/host/config.test.ts:176
    if (overlay) writeFileSync(paths.userFile, JSON.stringify(overlay));
  • 写入本地文件
    src/host/config.test.ts:194
    writeFileSync(paths.defaultFile, JSON.stringify(BASE));
  • 写入本地文件
    src/host/config.test.ts:195
    if (raw !== null) writeFileSync(paths.userFile, raw);
  • 读取本地文件
    src/host/config.test.ts:216
    assert.equal(existsSync(paths.legacyUserFile as string), false, '旧文件应已重命名走');
  • 读取本地文件
    src/host/config.test.ts:217
    assert.equal(readFileSync(paths.userFile, 'utf8'), raw, '新文件内容必须与旧文件逐字节一致');
  • 删除本地文件递归
    src/host/config.test.ts:220
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/host/config.test.ts:229
    assert.equal(existsSync(paths.legacyUserFile as string), true, '旧文件保持原样');
  • 删除本地文件递归
    src/host/config.test.ts:232
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/host/config.test.ts:240
    assert.equal(existsSync(paths.userFile), false, '不凭空造文件');
  • 删除本地文件递归
    src/host/config.test.ts:242
    rmSync(dir, { recursive: true, force: true });
  • 删除本地文件递归
    src/host/config.test.ts:253
    rmSync(dir, { recursive: true, force: true });
  • 删除本地文件递归
    src/host/config.test.ts:265
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/host/config.test.ts:294
    assert.throws(() => JSON.parse(readFileSync(paths.userFile, 'utf8')), '前置:严格 JSON.parse 确实读不了');
  • 读取本地文件
    src/host/config.test.ts:729
    assert.equal(readFileSync(paths.userFile, 'utf8'), raw, '用户层必须是默认文件的原文(含注释)');
  • 读取本地文件
    src/host/config.ts:105
    const raw = JSON.parse(stripJsonc(readFileSync(path, 'utf8'))) as unknown;
  • 读取本地文件
    src/host/config.ts:158
    if (existsSync(paths.userFile)) return paths.userFile;
  • 读取本地文件
    src/host/config.ts:159
    if (paths.legacyUserFile && existsSync(paths.legacyUserFile)) return paths.legacyUserFile;
  • 读取本地文件
    src/host/config.ts:174
    if (!legacy || !existsSync(legacy) || existsSync(paths.userFile)) return false;
  • 写入本地文件
    src/host/config.ts:176
    renameSync(legacy, paths.userFile);
  • 读取本地文件
    src/host/config.ts:194
    entries = readdirSync(petDir, { withFileTypes: true });
  • 读取本地文件
    src/host/config.ts:800
    const raw = readFileSync(paths.defaultFile, 'utf8');
  • 写入本地文件
    src/host/config.ts:801
    mkdirSync(dirname(paths.userFile), { recursive: true });
  • 写入本地文件
    src/host/config.ts:802
    writeFileSync(paths.userFile, raw, 'utf8');
  • 读取本地文件
    src/host/desktop-reload.test.ts:27
    const readSource = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
  • 删除本地文件递归
    src/host/helper-process.test.ts:46
    rmSync(dir, { recursive: true, force: true });
  • 写入本地文件
    src/host/helper-process.test.ts:86
    writeFileSync(fake, '');
  • 写入本地文件
    src/host/helper-process.test.ts:88
    writeFileSync(other, '');
  • 写入本地文件
    src/host/helper-process.test.ts:96
    writeFileSync(fake, '');
  • 写入本地文件
    src/host/helper-process.test.ts:104
    writeFileSync(viaEnv, '');
  • 写入本地文件
    src/host/helper-process.test.ts:119
    writeFileSync(fake, '');
  • 写入本地文件
    src/host/helper-process.test.ts:127
    writeFileSync(fake, '');
  • 读取本地文件
    src/host/helper-process.ts:100
    return list.find((value) => existsSync(value));
  • 读取本地文件
    src/host/helper-process.ts:183
    if (existsSync(exe)) return exe;
  • 写入本地文件
    src/host/helper-process.ts:191
    mkdirSync(targetDir, { recursive: true });
  • 读取本地文件
    src/host/helper-process.ts:229
    if (!existsSync(exe)) {
  • 执行外部命令来自配置/变量
    src/host/helper-process.ts:312
    const child = spawn(command, args, {
  • 发起网络请求
    src/host/helper-process.ts:402
    void fetch(cb, {
  • 读取本地文件
    src/host/host-liveness.test.ts:35
    const readSource = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
  • 读取本地文件
    src/host/index.ts:177
    return candidate && existsSync(candidate) ? candidate : undefined;
  • 读取本地文件
    src/host/index.ts:190
    const stream = createReadStream(file);
  • 读取本地文件
    src/host/index.ts:303
    if (own !== undefined && existsSync(own)) return [own];
  • 读取本地文件
    src/host/index.ts:451
    raw = await readFile(memoryPath, 'utf8');
  • 写入本地文件
    src/host/index.ts:464
    await mkdir(userRoot, { recursive: true });
  • 写入本地文件
    src/host/index.ts:465
    await writeFile(`${memoryPath}.bak-${Date.now()}`, raw, 'utf8');
  • 写入本地文件
    src/host/index.ts:474
    await mkdir(userRoot, { recursive: true });
  • 写入本地文件
    src/host/index.ts:475
    await writeFile(memoryPath, JSON.stringify(mem, null, 2), 'utf8');
  • 读取本地文件
    src/host/index.ts:628
    const raw = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')) as { version?: unknown };
  • 写入本地文件
    src/host/index.ts:919
    await mkdir(userRoot, { recursive: true });
  • 写入本地文件
    src/host/index.ts:920
    await writeFile(userConfigPath, JSON.stringify(clean, null, 2), 'utf8');
  • 读取本地文件
    src/host/index.ts:1286
    extraAnimDir !== undefined && existsSync(extraAnimDir)
  • 写入本地文件
    src/host/memes.test.ts:23
    mkdirSync(memesDir, { recursive: true });
  • 写入本地文件
    src/host/memes.test.ts:24
    for (const n of names) writeFileSync(join(memesDir, n + '.png'), 'x');
  • 删除本地文件递归
    src/host/memes.test.ts:27
    rmSync(dir, { recursive: true, force: true });
  • 删除本地文件递归
    src/host/memes.test.ts:78
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/host/memes.ts:39
    if (!dirs.some((dir) => existsSync(join(dir, name + '.png')))) continue;
  • 读取本地文件
    src/host/pointer-target.test.ts:29
    const readSource = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
  • 写入本地文件
    src/host/routes.test.ts:71
    mkdirSync(join(userRoot, 'pet', `${CN_PET}-animation`), { recursive: true });
  • 写入本地文件
    src/host/routes.test.ts:72
    writeFileSync(join(userRoot, 'pet', `${CN_PET}-animation`, 'test.webm'), OWN);
  • 写入本地文件
    src/host/routes.test.ts:73
    mkdirSync(join(userRoot, 'main-animation', 'webm'), { recursive: true });
  • 写入本地文件
    src/host/routes.test.ts:74
    writeFileSync(join(userRoot, 'main-animation', 'webm', 'mainidle.webm'), MAIN);
  • 写入本地文件
    src/host/routes.test.ts:76
    mkdirSync(join(userRoot, 'memes'), { recursive: true });
  • 写入本地文件
    src/host/routes.test.ts:77
    writeFileSync(join(userRoot, 'memes', '共用图.png'), SHARED_PIC);
  • 删除本地文件递归
    src/host/routes.test.ts:163
    rmSync(dir, { recursive: true, force: true });
  • 写入本地文件
    src/host/storage-paths.test.ts:112
    mkdirSync(join(dir, 'dsh-pet-plus'), { recursive: true });
  • 删除本地文件递归
    src/host/storage-paths.test.ts:115
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/host/storage-paths.test.ts:135
    const src = readFileSync(new URL('../../runtime/electron-helper/main.js', import.meta.url), 'utf8');
  • 读取本地文件
    src/host/storage-paths.ts:108
    return items.map((it) => ({ ...it, exists: existsSync(it.path) }));
  • 删除本地文件递归
    src/host/work-status-lifecycle.test.ts:137
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/shared/balance.test.ts:34
    const readSource = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
  • 发起网络请求
    src/shared/chat.ts:24
    const res = await fetch(baseUrl, {
  • 读取本地文件
    src/shared/config.test.ts:41
    const readSource = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
  • 删除本地文件递归
    src/shared/config.test.ts:66
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    src/shared/pickers.test.ts:171
    const browserShell = readFileSync(fileURLToPath(new URL('../client/pet.ts', import.meta.url)), 'utf8');
  • 读取本地文件
    src/shared/pickers.test.ts:172
    const desktopShell = readFileSync(
  • 发起网络请求
    src/shared/state.ts:45
    const res = await fetch(baseUrl, { cache: 'no-store' });
  • 发起网络请求
    src/shared/state.ts:237
    const res = await fetch(baseUrl, {
  • 读取本地文件
    src/standalone/check.ts:26
    const parsed = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { version?: unknown };
  • 读取本地文件
    src/standalone/check.ts:46
    `用户配置 ${paths.userFile}${existsSync(paths.userFile) ? '' : '(不存在 → 用包内默认)'}`,
  • 读取本地文件
    src/standalone/check.ts:47
    `文件宠物目录 ${paths.petDir}${existsSync(paths.petDir) ? '' : '(不存在)'}`,
  • 读取本地文件
    src/standalone/check.ts:48
    `内置默认配置 ${paths.defaultFile}${existsSync(paths.defaultFile) ? '' : ' ✗ 缺失'}`,
  • 读取本地文件
    src/standalone/check.ts:59
    if (!existsSync(paths.defaultFile)) {
  • 读取本地文件
    src/standalone/cli.ts:43
    if (!existsSync(paths.defaultFile)) {
  • 读取本地文件
    src/standalone/cli.ts:101
    if (!existsSync(paths.userFile)) configSource = `${paths.userFile}(不存在 → 用包内默认)`;
  • 删除本地文件递归
    src/standalone/integration.test.ts:73
    rmSync(home, { recursive: true, force: true });
  • 发起网络请求
    src/standalone/integration.test.ts:78
    const response = await fetch(`${base}${path}`);
  • 发起网络请求
    src/standalone/integration.test.ts:158
    const response = await fetch(`${base}/`);
  • 发起网络请求
    src/standalone/integration.test.ts:167
    const response = await fetch(`${base}/shutdown`, { method: 'POST' });
  • 发起网络请求
    src/standalone/server.test.ts:210
    const response = await fetch(`http://127.0.0.1:${server.port}/`);
  • 发起网络请求
    src/standalone/server.test.ts:229
    const inside = await fetch(`http://127.0.0.1:${server.port}/dsh-pet-plus-7340/state`);
  • 发起网络请求
    src/standalone/server.test.ts:231
    const outside = await fetch(`http://127.0.0.1:${server.port}/whatever`);
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent-default-model@deepseek-ai/dsh-client-connection@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-commands@deepseek-ai/dsh-credentials@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-llm
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 亲密度养成
  • 番茄钟
  • 语音朗读

DSH 桌宠插件 dsh-pet 的二创版:新增亲密度养成、番茄钟、语音朗读、音效、增强菜单、每日统计;可与原版并存

README

由 DeepSeek 最新模型翻译生成
dsh-pet-plus DSH(DeepSeek Harness)桌面宠物插件 —— 基于 dsh-pet 0.3.6 的二次创作。 保留上游全部基础功能(动画、漫游、拖拽抛掷物理、余额、碎碎念、对话与记忆、系统通知、工作状态联动、多开与布局、透明置顶桌面小窗),并新增六组功能。与上游可以并存(改名隔离,见下)。 二创来源与权利声明 - 上游:dsh-pet 0.3.6,作者 PC2005-cloud,MIT 许可(<https://github.com/PC2005-cloud/dsh-pet>)。 - 本二创:dsh-pet-plus,作者 youhuangqing,就本仓库新增与修改的部分同样以 MIT 释出。 上游 LICENSE 原文完整保留,两者许可互相独立、互不影响。 - 素材例外:动画 / 提示词 / 源视频沿用上游声明 —— 允许开源使用,禁止商用。 - 详见 THIRD_PARTY_NOTICES.md。 〇、路径约定 下文用 <DSH安装目录> 表示你的 DeepSeek Harness 安装位置(不是固定值,取决于你安装时选的路径)。 dsh.cmd 与 pnpm 都在它下面。
展开完整文档(README 剩余 1 节)
dsh-pet-plus

DSH(DeepSeek Harness)桌面宠物插件 —— 基于 dsh-pet 0.3.6 的二次创作。

保留上游全部基础功能(动画、漫游、拖拽抛掷物理、余额、碎碎念、对话与记忆、系统通知、工作状态联动、多开与布局、透明置顶桌面小窗),并新增六组功能。与上游可以并存(改名隔离,见下)。

二创来源与权利声明

- 上游:dsh-pet 0.3.6,作者 PC2005-cloud,MIT 许可(<https://github.com/PC2005-cloud/dsh-pet>)。
- 本二创:dsh-pet-plus,作者 youhuangqing,就本仓库新增与修改的部分同样以 MIT 释出。
上游 LICENSE 原文完整保留,两者许可互相独立、互不影响。
- 素材例外:动画 / 提示词 / 源视频沿用上游声明 —— 允许开源使用,禁止商用。
- 详见 THIRD_PARTY_NOTICES.md。

〇、路径约定

下文用 <DSH安装目录> 表示你的 DeepSeek Harness 安装位置(不是固定值,取决于你安装时选的路径)。
dsh.cmd 与 pnpm 都在它下面。不确定就查:

Get-Command dsh | Select-Object -ExpandProperty Source # dsh 已在 PATH 时

拿到的路径形如 <DSH安装目录>\resources\runtime\cli\bin\dsh.cmd,往上退三级就是 <DSH安装目录>。

一、环境要求

| 项 | 要求 |
| DSH | 桌面版 0.2.0-rc.1 / 0.2.0-rc.2(0.1.7-rc.2 不兼容) |
| 系统 | Windows 优先(桌面小窗与语音朗读依赖 Windows 自带能力);其它平台可用浏览器内的那只宠物 |
| 网络 | 仅在首次会下载 Electron 运行时(约 200MB,用于桌面小窗)。之后离线可用 |
| 语音朗读 | 走 Windows 系统语音(离线),或用 Edge 联网音色(可选) |

不需要额外安装 Node.js —— 构建产物已随包提供。

二、安装

假设你的配置档名是 desktop(DSH 桌面版默认)。

方式 1:一行命令(推荐,直接从 Release 装)

dsh plugin --profile desktop add https://github.com/youhuangqing/dsh-pet-plus/releases/download/v0.1.0/dsh-pet-plus-0.1.0.tgz

dsh 不在 PATH 时用全路径:& '<DSH安装目录>\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop add <上面的网址或本地 tgz 路径>。

方式 2:从本地 tgz 装

& '<DSH安装目录>\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop add file:C:\path\to\dsh-pet-plus-0.1.0.tgz

装完重启 DeepSeek Harness 即可(插件在启动时加载)。

若该包日后发布到 npm,也可以按包名安装:dsh plugin --profile desktop add dsh-pet-plus。

为什么装完要重启

DSH 在启动时组合插件树,运行中不热加载第三方插件。

安装后怎么确认装上了

浏览器开发者控制台(Ctrl+Shift+I)里搜 dsh-pet-plus,应能看到功能就绪日志;或访问 http://127.0.0.1:19387/dsh-pet-plus-7340/state,返回的 sections 里应含 social / pomodoro / stats / sfx / tts。

三、功能一览

| 功能 | 怎么用 |
| 亲密度 + 喂食养成 | 点击宠物摸头;右键「互动」→ 摸摸头 / 喂小鱼干;/social 看状态 |
| 番茄钟 | 右键「专注」→ 开始 25 / 10 / 45 分钟;菜单标题实时显示倒计时 |
| 语音朗读 | 自动朗读宠物说的话;右键「偏好 → 语音朗读」开关;/tts status 看服务商 |
| 音效 | 自动(点击 / 落地 / 余额 / 番茄钟结束 / 升级 / 喂食);右键「偏好 → 音效」开关 |
| 增强菜单 | 右键新增「互动 / 专注 / 统计 / 偏好」四组,浏览器里那只与桌面上那只完全一致 |
| 每日统计与记账 | 右键「统计」;/stats;/dsh-pet-plus-7340/stats?days=7 |

上游原有功能(余额、碎碎念、对话、系统通知、工作状态联动、多开布局、设置页)全部保留。

四、配置

首次运行后配置文件在:

C:\Users\<你>\.dsh\dsh-pet-plus\main-config.jsonc

改完即时生效(每个请求重读),不用重启、不用重装。包内 assets/config.jsonc 是带完整注释的默认值,想看所有可配项就看它。

语音音色(最常问的一个)

在 main-config.jsonc 里写 tts 段,然后让它说句话(或 /tts 你好呀)即可听效果:

{
"tts": {
"enabled": true,
"provider": "edge", // "auto"(Windows→系统语音)| "sapi" | "edge" | "stub" "voice": "zh-CN-XiaoyiNeural", // ← 音色名写这里 "rate": 0, // 语速 -10~10 "volume": 100 } }

音色名怎么选:
- provider: "sapi"(离线,系统自带):音色名要写已安装的 Windows 语音名。列出本机已装的: Add-Type -AssemblyName System.Speech (New-Object System.Speech.Synthesis.SpeechSynthesizer).GetInstalledVoices() |
ForEach-Object { $_.VoiceInfo.Name + ' [' + $_.VoiceInfo.Culture.Name + ']' }

Windows 中文版通常只有 Microsoft Huihui Desktop(成年女声)。想要少女音/萝莉音,需要另外装 Windows 语音包,或者用下面的 Edge。

- provider: "edge"(联网,微软 Edge「大声朗读」接口,音色明显更好且免 key):候选音色名包括 zh-CN-XiaoyiNeural(少女)、zh-CN-XiaoshuangNeural(童声/萝莉)、zh-CN-XiaoxiaoNeural(默认女声)、 zh-CN-XiaomengNeural、zh-CN-XiaohanNeural。
⚠️ 这是未公开接口,随微软改动可能失效。为此做了三重保护:默认不用它、返回字节必须是合法 MP3 才采纳、失败自动回落系统语音。所以最坏结果只是「Edge 没生效、退回系统语音」,不会更坏。
/tts status 会显示实际生效的提供方;应用日志里会写明是否发生过回落。

- provider: "stub":只产出静音,用来验证链路(排查用,不出声)。

改设置页里那一行的名字

src/client/settings.ts 里 zh 字典的 nav 字段(以及 en 的),改完重新构建:

& '<DSH安装目录>\resources\runtime\pnpm\bin\pnpm.mjs' run build

(仓库里 lib/ 是构建产物、不入库,所以要自己构建一次;详见第九节。)

五、命令

| 命令 | 说明 |
| /pet | 选择当前桌宠(支持选择框或手输 id/名字) |
| /chat | 与桌宠对话 |
| /balance | 查询余额 |
| /social | 亲密度 / 小鱼干状态 |
| /pomodoro | 番茄钟(start / stop / status …) |
| /stats | 每日统计 |
| /tts | 语音朗读(status / 直接给一句试听文本) |

与上游 dsh-pet 并存时,/pet /chat /balance 三个短名归先加载的原版,本插件会自动改用 /pet-plus /chat-plus /balance-plus(注册失败只告警,不会让插件加载失败)。
卸掉原版后短名自动归本插件,无需改配置。

六、卸载

& '<DSH安装目录>\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop remove dsh-pet-plus

然后重启。用户数据(~/.dsh/dsh-pet-plus/)不会自动删除,需要时手动删。

七、与上游并存

本插件把所有注册进 DSH 全局命名空间的标识都加了 -plus:包名、cordis 行 id、路由前缀(/dsh-pet-plus-7340)、桥协议 scheme、用户数据目录(~/.dsh/dsh-pet-plus/)、Electron 应用名、环境变量前缀、客户端插件名与插槽 id、locale 命名空间(pet-plus.config)、斜杠命令。

两套可以同时装、互不影响(会看到两只宠物、设置页两个分区)。想只用一套就卸掉另一个。

⚠️ Electron 运行时目录(~/.dsh/electron/)是两套共用的,这是刻意的 —— 避免重复下载 200MB。

八、已知限制

| 项 | 说明 |
| 悬停快捷条 | 未实现(计划里有)。它要改桌面端「整窗点击穿透」的判定,改错会让鼠标移过去就穿透、按钮点不到,在缺少真机验证条件下风险过高。 |
| Edge 联网音色 | 协议为未公开接口,未经运行时验证(有三重保护,最坏退回系统语音)。 |
| token 用量统计 | 会话事件是否携带 usage 未核实,故留空而非填猜测值。 |

九、开发

仓库不含 lib/(构建产物),clone 后先装依赖再构建:

1) 装依赖(需要网络;peer 依赖 @deepseek-ai/* 是公共 npm 包) & '<DSH安装目录>\resources\runtime\pnpm\bin\pnpm.mjs' install

2) 构建与自测 $env:ELECTRON_RUN_AS_NODE = '1' $env:PATH = '<DSH安装目录>\resources\runtime\bin;' + $env:PATH $exe = '<DSH安装目录>\DeepSeek Harness.exe' $pnpm = '<DSH安装目录>\resources\runtime\pnpm\bin\pnpm.mjs'

& $exe --expose-internals $pnpm run typecheck # 类型检查 & $exe --expose-internals $pnpm run build # tsdown + 重建 shared-core.js & $exe --expose-internals $pnpm test # 上游测试套件(553 个用例) & $exe --expose-internals $pnpm run test:single # 受限环境用这个(不 spawn 子进程)

3) 独立模式(不需要 DSH、不用重启应用) $env:DSH_HOME = '<工作区内的临时目录>' & node.cmd lib/standalone.js --port 3099

$exe --expose-internals $pnpm 是「用 DSH 自带的 Node 跑 pnpm」的写法。
你也可以直接用系统里的 pnpm(如果有):pnpm install && pnpm run build。

runtime/electron-helper/*.js 不在 tsc 范围内,改动后要单独做语法检查:node --check <文件>。

打分发用的包:pnpm pack → 产出 dsh-pet-plus-<版本>.tgz,把它作为 Release 附件发布。

十、许可与署名

代码 MIT(见 LICENSE)。素材来自上游 dsh-pet:代码 MIT;动画 / 提示词 / 源视频允许开源使用,禁止商用。 详细说明见 THIRD_PARTY_NOTICES.md。
预览分享卡片长什么样 ↓youhuangqing/dsh-pet-plus
分享 · DeepSeek Harness Hub
youhuangqing/dsh-pet-plus

养亲密度、开番茄钟,让桌宠陪你专注工作

回到顶部