dsh-pet 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.en.md +10 -10
  2. package/README.md +11 -11
  3. package/assets/config.jsonc +88 -0
  4. package/assets/thumb//344/270/211/347/220/203/346/212/233/346/216/245.webm +0 -0
  5. package/assets/thumb//344/270/213/344/272/224/345/255/220/346/243/213.webm +0 -0
  6. package/assets/thumb//344/270/234/345/274/240/350/245/277/346/234/233.webm +0 -0
  7. package/assets/thumb//344/270/255/347/247/213/350/265/217/346/234/210/345/220/203/346/234/210/351/245/274.webm +0 -0
  8. package/assets/thumb//344/274/230/351/233/205/345/245/263/344/273/206/350/210/236.webm +0 -0
  9. package/assets/thumb//345/201/267/345/220/203/351/233/266/351/243/237/350/242/253/346/212/223/344/275/217.webm +0 -0
  10. package/assets/thumb//345/206/231/344/273/243/347/240/201.webm +0 -0
  11. package/assets/thumb//345/206/231/347/246/217/345/255/227.webm +0 -0
  12. package/assets/thumb//345/207/255/347/251/272/347/224/237/350/212/261.webm +0 -0
  13. package/assets/thumb//345/212/250/347/211/251/347/216/257/347/273/225.webm +0 -0
  14. package/assets/thumb//345/216/237/345/234/260/344/270/223/345/277/203/347/216/251/351/255/224/346/226/271.webm +0 -0
  15. package/assets/thumb//345/216/237/345/234/260/345/260/217/346/206/251/346/262/211/347/234/240.webm +0 -0
  16. package/assets/thumb//345/216/237/345/234/260/345/267/246/350/275/254/345/245/224/350/267/221.webm +0 -0
  17. package/assets/thumb//345/216/237/345/234/260/346/225/262/345/207/273/346/241/214/351/235/242/344/272/222/345/212/250.webm +0 -0
  18. package/assets/thumb//345/216/237/345/234/260/346/274/202/346/265/256/350/270/217/346/255/245.webm +0 -0
  19. package/assets/thumb//345/216/237/345/234/260/350/267/263/350/267/203/346/212/223/347/242/216/345/244/264/351/241/266/347/211/251/345/223/201.webm +0 -0
  20. package/assets/thumb//345/216/237/345/234/260/350/271/262/344/270/213/347/216/251/347/216/251/345/205/267/346/261/275/350/275/246.webm +0 -0
  21. package/assets/thumb//345/216/237/345/234/260/351/207/215/345/212/233/344/270/213/350/271/262/345/216/213/347/274/251.webm +0 -0
  22. package/assets/thumb//345/217/230/351/270/275/345/255/220.webm +0 -0
  23. package/assets/thumb//345/217/257/347/210/261/345/256/205/350/210/236.webm +0 -0
  24. package/assets/thumb//345/220/203Token.webm +0 -0
  25. package/assets/thumb//345/220/203/345/206/260/346/267/207/346/267/213/350/236/215/345/214/226.webm +0 -0
  26. package/assets/thumb//345/220/203/345/215/210/351/244/220.webm +0 -0
  27. package/assets/thumb//345/220/203/345/244/247/351/227/270/350/237/271.webm +0 -0
  28. package/assets/thumb//345/220/203/345/271/264/347/263/225.webm +0 -0
  29. package/assets/thumb//345/220/203/346/227/251/351/244/220.webm +0 -0
  30. package/assets/thumb//345/220/203/346/231/232/351/244/220.webm +0 -0
  31. package/assets/thumb//345/220/203/346/261/244/345/234/206.webm +0 -0
  32. package/assets/thumb//345/220/203/347/231/275/351/245/255.webm +0 -0
  33. package/assets/thumb//345/220/203/347/262/275/345/255/220.webm +0 -0
  34. package/assets/thumb//345/220/203/347/263/226/350/221/253/350/212/246.webm +0 -0
  35. package/assets/thumb//345/220/203/350/205/212/345/205/253/347/262/245.webm +0 -0
  36. package/assets/thumb//345/220/203/350/245/277/347/223/234.webm +0 -0
  37. package/assets/thumb//345/220/203/351/207/215/351/230/263/347/263/225.webm +0 -0
  38. package/assets/thumb//345/220/203/351/225/277/345/257/277/351/235/242.webm +0 -0
  39. package/assets/thumb//345/220/203/351/235/222/345/233/242.webm +0 -0
  40. package/assets/thumb//345/220/203/351/245/272/345/255/220.webm +0 -0
  41. package/assets/thumb//345/220/271/346/260/224/347/220/203.webm +0 -0
  42. package/assets/thumb//345/220/271/347/254/233/345/255/220.webm +0 -0
  43. package/assets/thumb//345/223/210/346/254/240/350/277/236/345/244/251.webm +0 -0
  44. package/assets/thumb//345/240/206/351/233/252/344/272/272.webm +0 -0
  45. package/assets/thumb//345/244/247/345/217/243/345/220/203/351/233/266/351/243/237.webm +0 -0
  46. package/assets/thumb//345/245/263/344/273/206/345/261/210/350/206/235/347/244/274/344/273/252.webm +0 -0
  47. package/assets/thumb//345/260/217/345/271/205/345/272/246/345/216/237/345/234/260360/345/272/246/346/227/213/350/275/254/345/261/225/347/244/272.webm +0 -0
  48. package/assets/thumb//345/260/217/346/217/220/347/220/264/346/274/224/345/245/217.webm +0 -0
  49. package/assets/thumb//345/276/205/346/234/272/345/221/274/345/220/270/344/274/221/351/227/262.webm +0 -0
  50. package/assets/thumb//346/202/240/351/227/262/345/223/274/346/255/214.webm +0 -0
  51. package/assets/thumb//346/211/221/345/205/213/351/255/224/346/234/257.webm +0 -0
  52. package/assets/thumb//346/211/223/347/236/214/347/235/241/350/242/253/346/203/212/351/206/222.webm +0 -0
  53. package/assets/thumb//346/212/275/351/231/200/350/236/272.webm +0 -0
  54. package/assets/thumb//346/213/206/347/244/274/347/211/251.webm +0 -0
  55. package/assets/thumb//346/217/222/350/214/261/350/220/270/350/265/217/350/217/212.webm +0 -0
  56. package/assets/thumb//346/221/207/346/211/207/347/272/263/345/207/211.webm +0 -0
  57. package/assets/thumb//346/222/270/347/214/253.webm +0 -0
  58. package/assets/thumb//346/224/266/347/272/242/345/214/205.webm +0 -0
  59. package/assets/thumb//346/224/276/345/255/224/346/230/216/347/201/257.webm +0 -0
  60. package/assets/thumb//346/224/276/346/262/263/347/201/257.webm +0 -0
  61. package/assets/thumb//346/224/276/347/203/237/350/212/261.webm +0 -0
  62. package/assets/thumb//346/224/276/351/243/216/347/255/235.webm +0 -0
  63. package/assets/thumb//346/225/264/344/275/223/346/215/242/350/243/205/350/257/225/350/211/262.webm +0 -0
  64. package/assets/thumb//346/230/257/345/225/212/357/274/214/345/220/203/344/273/200/344/271/210.webm +0 -0
  65. package/assets/thumb//346/231/250/351/227/264/345/210/267/347/211/231.webm +0 -0
  66. package/assets/thumb//346/266/256/347/201/253/351/224/205.webm +0 -0
  67. package/assets/thumb//346/267/261/345/272/246/346/200/235/350/200/203/347/242/216/347/242/216/345/277/265.webm +0 -0
  68. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224-/345/202/262/345/250/207/347/224/237/346/260/224.webm +0 -0
  69. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224-/345/205/203/346/260/224/346/214/245/346/211/213.webm +0 -0
  70. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224-/345/256/263/347/276/236/346/203/212/350/256/266.webm +0 -0
  71. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224-/345/274/200/345/277/203/350/267/203/345/212/250.webm +0 -0
  72. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224-/346/214/240/347/227/222/345/222/257/345/222/257/347/254/221.webm +0 -0
  73. package/assets/thumb//347/205/247/351/225/234/345/255/220.webm +0 -0
  74. package/assets/thumb//347/216/251/346/260/264/346/236/252.webm +0 -0
  75. package/assets/thumb//347/216/251/346/270/270/346/210/217/346/260/224/346/200/245/350/264/245/345/235/217.webm +0 -0
  76. package/assets/thumb//347/224/250/351/262/270/351/261/274/345/260/276/345/267/264/346/213/215/346/211/223/345/234/260/351/235/242.webm +0 -0
  77. package/assets/thumb//347/251/277/351/222/210/344/271/236/345/267/247.webm +0 -0
  78. package/assets/thumb//350/210/236/347/213/256/345/244/264.webm +0 -0
  79. package/assets/thumb//350/215/241/347/247/213/345/215/203.webm +0 -0
  80. package/assets/thumb//350/220/214/345/214/226/345/260/217/345/271/275/347/201/265.webm +0 -0
  81. package/assets/thumb//350/223/235/351/262/270/347/216/260/344/270/226.webm +0 -0
  82. package/assets/thumb//350/235/264/350/235/266/350/234/234/350/234/202/347/216/257/347/273/225/345/244/264/351/241/266/345/274/200/350/212/261.webm +0 -0
  83. package/assets/thumb//350/236/203/350/237/271/350/265/260/350/267/257.webm +0 -0
  84. package/assets/thumb//350/242/253/345/220/223/344/270/200/350/267/263.webm +0 -0
  85. package/assets/thumb//350/242/253/350/220/275/345/217/266/346/267/271/346/262/241.webm +0 -0
  86. package/assets/thumb//350/242/253/351/274/240/346/240/207/346/213/226/346/213/275/346/202/254/347/251/272/345/217/215/351/246/210.webm +0 -0
  87. package/assets/thumb//350/243/205/347/202/271/345/234/243/350/257/236/346/240/221.webm +0 -0
  88. package/assets/thumb//350/256/250/347/263/226/345/215/227/347/223/234/347/201/257.webm +0 -0
  89. package/assets/thumb//350/266/205/345/244/247/344/274/270/346/207/222/350/205/260.webm +0 -0
  90. package/assets/thumb//350/270/242/346/257/275/345/255/220.webm +0 -0
  91. package/assets/thumb//350/275/273/345/277/253/346/221/207/346/221/206/350/210/236.webm +0 -0
  92. package/assets/thumb//350/275/273/345/277/253/350/256/260/345/275/225.webm +0 -0
  93. package/assets/thumb//351/252/221/346/234/250/351/251/254.webm +0 -0
  94. package/assets/thumb//351/262/270/351/261/274/345/220/220/346/263/241/346/263/241/347/211/271/346/225/210.webm +0 -0
  95. package/lib/client.js +1105 -693
  96. package/lib/index.js +172 -125
  97. package/package.json +20 -6
  98. package/src/client/app.ts +488 -0
  99. package/src/client/config.ts +122 -0
  100. package/src/client/constants.ts +9 -0
  101. package/src/client/index.ts +15 -0
  102. package/src/client/pickers.ts +32 -0
  103. package/src/client/settings.ts +325 -0
  104. package/src/client/types.ts +37 -0
  105. package/src/env.d.ts +6 -0
  106. package/src/host/index.ts +204 -0
  107. package/assets/thumb//345/260/217/345/271/205/345/272/246/345/216/237/345/234/260 360 /345/272/246/346/227/213/350/275/254/345/261/225/347/244/272.webm +0 -0
  108. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224 - /345/202/262/345/250/207/347/224/237/346/260/224/357/274/210/344/276/247/350/272/253/345/261/225/347/244/272/357/274/211.webm +0 -0
  109. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224 - /345/256/263/347/276/236/346/203/212/350/256/266.webm +0 -0
  110. package/assets/thumb//347/202/271/345/207/273/345/233/236/345/272/224 - /345/274/200/345/277/203/350/267/203/345/212/250.webm +0 -0
  111. package/assets/thumb//350/242/253/345/220/223/344/270/200/350/267/263/357/274/210/347/202/270/346/257/233/357/274/211.webm +0 -0
package/lib/index.js CHANGED
@@ -1,135 +1,182 @@
1
- /**
2
- * ============================================================================
3
- * dsh-pet 宿主半侧(host half)—— 宠物插件的"后端"部分
4
- * ============================================================================
5
- *
6
- * 【这个文件是什么】
7
- * 本文件运行在 DSH 的 Node 服务端(不是浏览器)。它的唯一职责是:
8
- * 在 DSH 的 Web 服务器上注册一个 `/pet/` 前缀的 HTTP 路由,
9
- * 把插件包里的动画 WebM 文件流式返回给浏览器。
10
- *
11
- * 【为什么需要它】
12
- * DSH 的 `/plugins/` 路由只服务"客户端 JS bundle",不服务视频等静态资源。
13
- * 所以浏览器半侧(lib/client.js)要播放动画,必须有一个专门的路由来取文件。
14
- * 这正是 DSH 官方提供的扩展点:`ctx.webServer.register()`。
15
- *
16
- * 【路由结构】
17
- * /pet/thumb/<动画名>.webm → 读插件包内 assets/thumb/(360×360 播放变体)
18
- * /pet/full/<动画名>.webm → 读 $DSH_HOME/pet-assets/(原始 1200×1200,需先下载)
19
- *
20
- * 【安全性】
21
- * 路径做了"防穿越"校验(resolveAsset):请求里的路径规范化后必须仍在
22
- * assets 根目录内,否则返回 400。防止 /pet/../../etc/passwd 这类攻击。
23
- *
24
- * ============================================================================
25
- */
26
- import { createReadStream, existsSync } from 'node:fs';
27
- import { stat } from 'node:fs/promises';
28
- import { join, normalize, resolve, sep } from 'node:path';
29
- import { fileURLToPath } from 'node:url';
30
- // 官方 API:解析 DSH 主目录($DSH_HOME,默认 ~/.dsh)——full 资源存放位置
31
- import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
32
-
33
- // 插件行 id(与 cordis.patch.yml 一致)
34
- const name = 'pet';
35
- /** 需要注入的服务:webServer(Web 服务器路由注册表) */
36
- const inject = ['webServer'];
37
-
38
- /** 本包目录(src 和安装后都适用——import.meta.url 指向 lib/,上一级即包根) */
39
- const PACKAGE_ROOT = resolve(fileURLToPath(new URL('..', import.meta.url)));
40
-
41
- /** 路由前缀:/pet/thumb/<name>.webm、/pet/full/<name>.webm */
42
- const ROUTE_PREFIX = '/pet';
1
+ import { createReadStream, existsSync } from "node:fs";
2
+ import { readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import { join, normalize, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
43
6
 
7
+ //#region src/host/index.ts
8
+ const name = "pet";
9
+ const inject = ["webServer"];
10
+ /** 本包目录:宿主构建产物位于 lib/,其上一级即包根。 */
11
+ const PACKAGE_ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
12
+ /** 路由前缀 */
13
+ const ROUTE_PREFIX = "/pet";
44
14
  /** 不同扩展名对应的 Content-Type 映射 */
45
15
  const MIME = {
46
- '.webm': 'video/webm',
47
- '.mp4': 'video/mp4',
48
- '.png': 'image/png',
49
- '.json': 'application/json; charset=utf-8',
16
+ ".webm": "video/webm",
17
+ ".mp4": "video/mp4",
18
+ ".png": "image/png",
19
+ ".json": "application/json; charset=utf-8",
20
+ ".jsonc": "application/json; charset=utf-8"
50
21
  };
51
-
52
22
  /**
53
- * 规范化并校验请求路径,确保它在 assets 根目录内(防路径穿越)。
54
- * @param root - assets 根目录(绝对路径)
55
- * @param rel - 解码后的、路由前缀之后的路径片段
56
- * @returns 规范化后的绝对文件路径;非法(穿越)时返回 undefined
57
- */
23
+ * 规范化并校验请求路径,确保它在 assets 根目录内(防路径穿越)。
24
+ * @returns 规范化后的绝对文件路径;非法(穿越)时返回 undefined
25
+ */
58
26
  function resolveAsset(root, rel) {
59
- if (rel.length === 0) return undefined;
60
- // join + normalize 得到规范化路径(处理 ..、./、多余分隔符)
61
- const candidate = normalize(join(root, rel));
62
- // 根目录带分隔符的前缀,用于判断候选路径是否真的在根目录内
63
- const rootWithSep = root.endsWith(sep) ? root : root + sep;
64
- // 候选路径必须等于根目录或以"根目录/"开头,否则是穿越(如 ../lib/index.js)
65
- if (candidate !== root && !candidate.startsWith(rootWithSep)) return undefined;
66
- return candidate;
27
+ if (rel.length === 0) return void 0;
28
+ const candidate = normalize(join(root, rel));
29
+ const rootWithSep = root.endsWith(sep) ? root : root + sep;
30
+ if (candidate !== root && !candidate.startsWith(rootWithSep)) return void 0;
31
+ return candidate;
32
+ }
33
+ /** 流式返回一个文件(带 Content-Type / 长度 / 缓存头)。 */
34
+ async function sendFile(res, file, contentType) {
35
+ const { size } = await stat(file);
36
+ res.writeHead(200, {
37
+ "content-type": contentType,
38
+ "content-length": size,
39
+ "cache-control": "public, max-age=3600"
40
+ });
41
+ const stream = createReadStream(file);
42
+ stream.on("error", () => res.destroy());
43
+ stream.pipe(res);
44
+ }
45
+ /** 支持的角落白名单(与 client 端一致) */
46
+ const CORNERS = [
47
+ "top-left",
48
+ "top-right",
49
+ "bottom-left",
50
+ "bottom-right"
51
+ ];
52
+ /** 发送 JSON 响应 */
53
+ function sendJson(res, status, obj) {
54
+ const body = JSON.stringify(obj);
55
+ res.writeHead(status, {
56
+ "content-type": "application/json; charset=utf-8",
57
+ "content-length": Buffer.byteLength(body)
58
+ });
59
+ res.end(body);
60
+ }
61
+ /** 收集请求体(文本) */
62
+ function readBody(req) {
63
+ return new Promise((resolve2, reject) => {
64
+ const chunks = [];
65
+ req.on("data", (c) => chunks.push(c));
66
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
67
+ req.on("error", reject);
68
+ });
69
+ }
70
+ /** 校验并归一化用户配置:只接受 { pets: [{ id, size, position }] } */
71
+ function sanitizeUserConfig(raw) {
72
+ const o = raw && typeof raw === "object" ? raw : {};
73
+ const arr = Array.isArray(o.pets) ? o.pets : null;
74
+ if (!arr || !arr.length) return null;
75
+ const out = [];
76
+ for (const p of arr) {
77
+ if (!p || typeof p !== "object") return null;
78
+ const id = String(p.id ?? "");
79
+ if (!id || id.length > 64 || /[\\/:\x00-\x1f]/.test(id)) return null;
80
+ const size = Number(p.size);
81
+ if (!Number.isFinite(size) || size <= 0) return null;
82
+ const pos = p.position && typeof p.position === "object" ? p.position : {};
83
+ const corner = String(pos.corner ?? "");
84
+ if (!CORNERS.includes(corner)) return null;
85
+ const marginX = Number(pos.marginX);
86
+ const marginY = Number(pos.marginY);
87
+ if (!Number.isFinite(marginX) || !Number.isFinite(marginY)) return null;
88
+ out.push({
89
+ id,
90
+ size,
91
+ position: {
92
+ corner,
93
+ marginX,
94
+ marginY
95
+ }
96
+ });
97
+ }
98
+ return { pets: out };
67
99
  }
68
-
69
- /**
70
- * 宿主插件主体:注册 `/pet` 前缀路由。
71
- * @param ctx - 插件上下文;ctx.webServer 是 Web 服务器服务
72
- * @param config - 本行的配置(来自 patch 树)
73
- */
74
100
  function apply(ctx, config) {
75
- // 两个资源根:
76
- // - thumbRoot:插件包内 assets/thumb/(360×360 播放变体,随包发布,一定存在)
77
- // - fullRoot :$DSH_HOME/pet-assets/(原始母版,需手动下载,可能不存在)
78
- const thumbRoot = join(PACKAGE_ROOT, 'assets', 'thumb');
79
- const fullRoot = config.fullRoot ?? join(resolveDshHome(), 'pet-assets');
80
-
81
- // ctx.effect 包裹:插件卸载时自动注销路由(官方生命周期管理)
82
- ctx.effect(() => ctx.webServer.register({
83
- kind: 'prefix', // 前缀路由:匹配 /pet 以及 /pet/xxx
84
- path: ROUTE_PREFIX,
85
- handler: async (req, res) => {
86
- const url = new URL(req.url ?? '/', 'http://localhost');
87
- // 去掉 /pet/ 前缀并 URL 解码(中文文件名是编码后的)
88
- const rest = decodeURIComponent(url.pathname.slice(ROUTE_PREFIX.length + 1));
89
- // 第一段是 scope:thumb 或 full
90
- const [scope, ...nameParts] = rest.split('/');
91
- if (scope !== 'thumb' && scope !== 'full') {
92
- res.writeHead(400, { 'content-type': 'text/plain; charset=utf-8' });
93
- res.end('dsh-pet: expected /pet/{thumb|full}/<file>');
94
- return;
95
- }
96
- // 剩余部分是文件名(可能含空格/中文,原样保留)
97
- const fileName = nameParts.join('/');
98
- const root = scope === 'thumb' ? thumbRoot : fullRoot;
99
- // 防穿越校验
100
- const file = resolveAsset(root, fileName);
101
- if (file === undefined) {
102
- res.writeHead(400, { 'content-type': 'text/plain; charset=utf-8' });
103
- res.end('dsh-pet: invalid path');
104
- return;
105
- }
106
- // 文件不存在:full 未下载 vs thumb 缺失给不同提示
107
- if (!existsSync(file)) {
108
- res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
109
- res.end(scope === 'full'
110
- ? `dsh-pet: original asset not downloaded yet — run the fetch-assets script to populate ${fullRoot}`
111
- : 'dsh-pet: asset not found');
112
- return;
113
- }
114
- // 按扩展名定 Content-Type,附 Content-Length(浏览器可显示进度)
115
- const ext = file.slice(file.lastIndexOf('.')).toLowerCase();
116
- const contentType = MIME[ext] ?? 'application/octet-stream';
117
- const { size } = await stat(file);
118
- res.writeHead(200, {
119
- 'content-type': contentType,
120
- 'content-length': size,
121
- // 缓存 1 小时:动画是静态文件,重复播放直接命中缓存
122
- 'cache-control': 'public, max-age=3600',
123
- });
124
- // 流式返回(大文件不占内存)
125
- const stream = createReadStream(file);
126
- stream.on('error', () => {
127
- res.destroy();
128
- });
129
- stream.pipe(res);
130
- },
131
- }), 'dsh-pet: /pet asset route');
101
+ const thumbRoot = join(PACKAGE_ROOT, "assets", "thumb");
102
+ const fullRoot = config.fullRoot ?? join(resolveDshHome(), "pet-assets");
103
+ const userConfigPath = join(resolveDshHome(), "pet-config.json");
104
+ ctx.effect(() => ctx.webServer.register({
105
+ kind: "prefix",
106
+ path: ROUTE_PREFIX,
107
+ handler: async (req, res) => {
108
+ const url = new URL(req.url ?? "/", "http://localhost");
109
+ const rest = decodeURIComponent(url.pathname.slice(ROUTE_PREFIX.length + 1));
110
+ if (rest === "config") {
111
+ if (req.method === "GET") {
112
+ try {
113
+ const raw = await readFile(userConfigPath, "utf8");
114
+ sendJson(res, 200, JSON.parse(raw));
115
+ } catch {
116
+ sendJson(res, 200, {});
117
+ }
118
+ return;
119
+ }
120
+ if (req.method === "PUT") {
121
+ try {
122
+ const body = await readBody(req);
123
+ const parsed = JSON.parse(body);
124
+ const clean = sanitizeUserConfig(parsed);
125
+ if (!clean) {
126
+ sendJson(res, 400, { error: "invalid pet config: expected { pets:[{id,size,position:{corner,marginX,marginY}}] }" });
127
+ return;
128
+ }
129
+ await writeFile(userConfigPath, JSON.stringify(clean, null, 2), "utf8");
130
+ sendJson(res, 200, { ok: true });
131
+ } catch {
132
+ sendJson(res, 400, { error: "invalid JSON body" });
133
+ }
134
+ return;
135
+ }
136
+ if (req.method === "DELETE") {
137
+ try {
138
+ await rm(userConfigPath, { force: true });
139
+ } catch {}
140
+ sendJson(res, 200, { ok: true });
141
+ return;
142
+ }
143
+ sendJson(res, 405, { error: "method not allowed" });
144
+ return;
145
+ }
146
+ if (rest === "config.jsonc") {
147
+ const cfgFile = join(PACKAGE_ROOT, "assets", "config.jsonc");
148
+ if (!existsSync(cfgFile)) {
149
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
150
+ res.end("dsh-pet: config.jsonc not found");
151
+ return;
152
+ }
153
+ await sendFile(res, cfgFile, MIME[".jsonc"] ?? "application/octet-stream");
154
+ return;
155
+ }
156
+ const [scope, ...nameParts] = rest.split("/");
157
+ if (scope !== "thumb" && scope !== "full") {
158
+ res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
159
+ res.end("dsh-pet: expected /pet/{thumb|full}/<file>");
160
+ return;
161
+ }
162
+ const fileName = nameParts.join("/");
163
+ const root = scope === "thumb" ? thumbRoot : fullRoot;
164
+ const file = resolveAsset(root, fileName);
165
+ if (file === void 0) {
166
+ res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
167
+ res.end("dsh-pet: invalid path");
168
+ return;
169
+ }
170
+ if (!existsSync(file)) {
171
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
172
+ res.end(scope === "full" ? `dsh-pet: original asset not downloaded yet — run the fetch-assets script to populate ${fullRoot}` : "dsh-pet: asset not found");
173
+ return;
174
+ }
175
+ const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
176
+ await sendFile(res, file, MIME[ext] ?? "application/octet-stream");
177
+ }
178
+ }), "dsh-pet: /pet asset route");
132
179
  }
133
180
 
134
- // 导出插件三件套(Cordis Loader 需要)
135
- export { apply, inject, name };
181
+ //#endregion
182
+ export { apply, inject, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pet",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A floating desktop pet for the DeepSeek Harness Web UI: idle breathing, occasional direction turns, random actions, and screen wandering.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -45,16 +45,22 @@
45
45
  "types": "./lib/types/client/index.d.ts",
46
46
  "default": "./lib/client.js"
47
47
  },
48
+ "./src/*": "./src/*",
48
49
  "./cordis.patch.yml": "./cordis.patch.yml",
49
50
  "./package.json": "./package.json"
50
51
  },
51
52
  "files": [
52
53
  "lib",
54
+ "src",
53
55
  "assets/thumb",
56
+ "assets/config.jsonc",
54
57
  "cordis.patch.yml"
55
58
  ],
56
59
  "scripts": {
57
- "prepack": "node scripts/prepack-check.js"
60
+ "bundle": "tsdown",
61
+ "watch": "tsdown --watch",
62
+ "prepare": "npm run bundle",
63
+ "prepack": "npm run bundle && node scripts/prepack-check.js"
58
64
  },
59
65
  "license": "MIT",
60
66
  "dsh": {
@@ -70,10 +76,18 @@
70
76
  },
71
77
  "peerDependencies": {
72
78
  "@deepseek-ai/cordis": "^4.0.1",
73
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
74
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
75
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
76
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
79
+ "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.1",
80
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.1",
81
+ "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.1",
82
+ "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
77
83
  "react": "^18.2.0"
84
+ },
85
+ "devDependencies": {
86
+ "@types/node": "^26.2.0",
87
+ "@types/react": "^18.2.0",
88
+ "@types/react-dom": "^18.2.0",
89
+ "rolldown": "^1.0.0-beta.7",
90
+ "tsdown": "^0.8.0",
91
+ "typescript": "^5.5.0"
78
92
  }
79
93
  }