@jiugeqqqqq/dsh-house 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -102,6 +102,22 @@ dsh plugin --profile web add @jiugeqqqqq/dsh-house
102
102
 
103
103
  默认就是空的,正常不用动。
104
104
 
105
+ ## 更新
106
+
107
+ ```powershell
108
+ dsh plugin --profile desktop add @jiugeqqqqq/dsh-house@latest
109
+ ```
110
+
111
+ 重启 DSH 生效。
112
+
113
+ **自动检测**:插件每天查一次 npm(配置项 `updateCheck`,默认开)。有新版本时:
114
+
115
+ - `<DSH_HOME>/plugin-updates.json` 里记一条
116
+ - 日志里写一行
117
+ - 侧边栏「房子」页标题旁显示 `v0.2.1 → 0.3.0 可更新`
118
+
119
+ **它不会自动安装** —— DSH 没有热重载,装了也要重启;运行时替换自己的代码风险不值得。
120
+
105
121
  ## 常见问题
106
122
 
107
123
  **Q:页面空白 / 显示"解析不出条目"?**
package/lib/client.js CHANGED
@@ -227,6 +227,13 @@ window.__ModuleLoader__.load({
227
227
  React.createElement('h1', { style: styles.title }, '房子'),
228
228
  React.createElement('span', { style: styles.sub }, '常驻的资产与能力清单'),
229
229
  React.createElement('span', { style: styles.note }, '共 ' + String(total) + ' 条'),
230
+ s.data && s.data.version
231
+ ? React.createElement(
232
+ 'span',
233
+ { style: Object.assign({}, styles.note, s.data.updateAvailable ? { color: '#e0a030' } : null) },
234
+ 'v' + s.data.version + (s.data.updateAvailable ? ' → ' + s.data.latest + ' 可更新' : ''),
235
+ )
236
+ : null,
230
237
  ),
231
238
  );
232
239
 
package/lib/host/index.js CHANGED
@@ -17,10 +17,19 @@ import { readFile, writeFile, mkdir, copyFile } from 'node:fs/promises';
17
17
  import fs from 'node:fs';
18
18
  import zlib from 'node:zlib';
19
19
  import path from 'node:path';
20
+ import { createUpdateChecker } from '../update-check.js';
20
21
 
21
22
  export const name = 'house';
22
23
  export const inject = [];
23
24
 
25
+ const pkgVersion = (() => {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version || '0.0.0';
28
+ } catch {
29
+ return '0.0.0';
30
+ }
31
+ })();
32
+
24
33
  const ROUTE_PREFIX = '/dsh-house/api';
25
34
 
26
35
  function writeJson(response, status, payload) {
@@ -102,6 +111,19 @@ export function apply(ctx, config = {}) {
102
111
  ? config.sessionsDir
103
112
  : path.join(dshHome, 'sessions');
104
113
 
114
+ // 版本自检:只查 npm 有没有新版,不自动安装。
115
+ // 注意:本地开发目录的 package.json name 是 dsh-house,而 npm 上是 scoped 包,所以写死 npmName。
116
+ const updateChecker = createUpdateChecker({
117
+ npmName: '@jiugeqqqqq/dsh-house',
118
+ version: pkgVersion,
119
+ log: (m) => ctx.logger?.info('dsh-house: ' + m),
120
+ enabled: config.updateCheck !== false,
121
+ });
122
+ const updateTimer = setTimeout(() => {
123
+ updateChecker.check().catch(() => {});
124
+ }, 15000);
125
+ updateTimer.unref?.();
126
+
105
127
  // 房子清单不存在时自动建一份模板:别人装完第一次打开页面就有东西可看,
106
128
  // 不会像 2026-10-26 那样提示「读不到房子清单」。
107
129
  const TEMPLATE = [
@@ -425,7 +447,19 @@ export function apply(ctx, config = {}) {
425
447
 
426
448
  try {
427
449
  if (method === 'list') {
428
- writeJson(response, 200, await loadAssets());
450
+ const result = await loadAssets();
451
+ if (result.ok && result.data) {
452
+ const u = updateChecker.get() || {};
453
+ result.data.version = pkgVersion;
454
+ result.data.latest = u.latest || '';
455
+ result.data.updateAvailable = u.updateAvailable === true;
456
+ }
457
+ writeJson(response, 200, result);
458
+ return;
459
+ }
460
+ if (method === 'checkUpdate') {
461
+ const rec = await updateChecker.check({ force: true });
462
+ writeJson(response, 200, { ok: true, data: { ...(rec || {}), version: pkgVersion } });
429
463
  return;
430
464
  }
431
465
  if (method === 'read') {
@@ -0,0 +1,116 @@
1
+ // dsh-affection —— 版本更新检查(自动检测 npm 上有没有新版本)
2
+ //
3
+ // 只做「检测 + 报告」,不擅自替换自己的代码:DSH 没有热重载,
4
+ // 插件在运行中改自己的文件也只会下次重启才生效,风险不值得。
5
+ //
6
+ // 结果写到 <DSH_HOME>/plugin-updates.json,页面/日志都能读:
7
+ // { "dsh-affection": { current, latest, updateAvailable, checkedAt, lastError? } }
8
+ //
9
+ // 频率:默认每 24 小时查一次;失败也记录时间,避免离线时每步重试。
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import os from 'node:os';
13
+
14
+ /** '0.10.2' vs '0.9.9' → 1;相等 0;更小 -1。忽略预发布后缀。 */
15
+ export function compareVersions(a, b) {
16
+ const parse = (v) =>
17
+ String(v || '')
18
+ .trim()
19
+ .replace(/^v/i, '')
20
+ .split('-')[0]
21
+ .split('.')
22
+ .map((x) => Number.parseInt(x, 10) || 0);
23
+ const pa = parse(a);
24
+ const pb = parse(b);
25
+ const len = Math.max(pa.length, pb.length);
26
+ for (let i = 0; i < len; i++) {
27
+ const x = pa[i] || 0;
28
+ const y = pb[i] || 0;
29
+ if (x > y) return 1;
30
+ if (x < y) return -1;
31
+ }
32
+ return 0;
33
+ }
34
+
35
+ export function createUpdateChecker({
36
+ npmName,
37
+ version,
38
+ log,
39
+ enabled = true,
40
+ intervalHours = 24,
41
+ registry = 'https://registry.npmjs.org',
42
+ }) {
43
+ const dshHome = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
44
+ const statePath = path.join(dshHome, 'plugin-updates.json');
45
+
46
+ function readState() {
47
+ try {
48
+ const j = JSON.parse(fs.readFileSync(statePath, 'utf8'));
49
+ return j && typeof j === 'object' ? j : {};
50
+ } catch {
51
+ return {};
52
+ }
53
+ }
54
+
55
+ function writeState(state) {
56
+ try {
57
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
58
+ const tmp = `${statePath}.tmp-${process.pid}`;
59
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 1), 'utf8');
60
+ fs.renameSync(tmp, statePath);
61
+ } catch {}
62
+ }
63
+
64
+ function record(partial) {
65
+ const state = readState();
66
+ state[npmName] = { ...(state[npmName] || {}), ...partial };
67
+ writeState(state);
68
+ return state[npmName];
69
+ }
70
+
71
+ async function check({ force = false } = {}) {
72
+ if (!enabled) return null;
73
+ const prev = readState()[npmName] || {};
74
+ const now = Date.now();
75
+ const interval = Math.max(1, Number(intervalHours) || 24) * 3600 * 1000;
76
+ if (!force && prev.checkedAt && now - prev.checkedAt < interval) return prev;
77
+
78
+ try {
79
+ const res = await fetch(`${registry.replace(/\/+$/, '')}/${npmName.replace('/', '%2F')}/latest`, {
80
+ headers: { accept: 'application/json' },
81
+ signal: AbortSignal.timeout(8000),
82
+ });
83
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
84
+ const j = await res.json();
85
+ const latest = String(j?.version || '');
86
+ if (!latest) throw new Error('响应里没有 version');
87
+ const available = compareVersions(latest, version) > 0;
88
+ const rec = record({
89
+ current: version,
90
+ latest,
91
+ updateAvailable: available,
92
+ checkedAt: now,
93
+ lastError: '',
94
+ updateCommand: available ? `dsh plugin --profile <你的profile> add ${npmName}@latest` : '',
95
+ });
96
+ if (available) {
97
+ log?.(`发现新版本 ${latest}(当前 ${version}):dsh plugin --profile <你的profile> add ${npmName}@latest`);
98
+ }
99
+ return rec;
100
+ } catch (e) {
101
+ const rec = record({
102
+ current: version,
103
+ checkedAt: now,
104
+ lastError: String(e?.message ?? e),
105
+ });
106
+ log?.(`检查更新失败(不影响使用):${rec.lastError}`);
107
+ return rec;
108
+ }
109
+ }
110
+
111
+ function get() {
112
+ return readState()[npmName] || null;
113
+ }
114
+
115
+ return { check, get, npmName, version };
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "@jiugeqqqqq/dsh-house",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "房子 — 常驻的资产/能力清单,配一个侧边栏页面。清单会自动进系统提示(装了就生效);房子只记有没有、到哪一步,细节放 knowledge 和日志里。",
5
5
  "type": "module",
6
6
  "license": "MIT",