dsh-crossterm 0.6.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # dsh-crossterm 变更记录
2
2
 
3
+ ## 0.6.2 — 2026-10-19
4
+
5
+ ### 插件页:插件名/简介/组件表/更新日志
6
+ - 插件改名:`locale/zh.json` 的 `meta.title` 从「跨会话」改为 **跨端互通**(英文 `CrossTerm`),
7
+ `meta.description` 写成插件级简介(跨会话/跨终端/跨设备/云端中转与备份,一句话说清)。
8
+ **四个业务组件名不变**:跨会话 / 跨终端 / 跨设备 / 跨账号。
9
+ - `cordis.patch.yml` 结构变化:原来第一行用裸包名 `dsh-crossterm` 承担「跨会话」,
10
+ 现在拆成两行 —— 第一行 `dsh-crossterm/parts/session`(跨会话),**末尾新增裸包名行**
11
+ `dsh-crossterm`(`config.part: ui`,不挂任何工具,只承载浏览器半端的配置卡片与插件标题)。
12
+ 原因:`dsh-client-modules` 只对裸包名行加载 client bundle(`locatePkgJson()` 对子路径行返回
13
+ undefined),而插件页顶部标题与裸包名行共用同一份 locale meta,二者无法分成两个名字;
14
+ 把裸行放到组件列表末尾,前四行仍是四个业务组件。
15
+ - `lib/index.js`:`PARTS` 增 `ui`,`PART_LABEL.ui = '入口(插件卡片)'`;`ui` 不映射任何工具
16
+ (`TOOL_PART` 里没有它,`buildTools()` 过滤后为空),所以不会重复注册工具。
17
+ - `lib/web.js` 新增 `GET /crossterm/api/about`:返回 `title` / `description` / `components[]`
18
+ (读 `parts/*.manifest.json`,与组件行显示同源)/ `changelog`(读包内 `CHANGELOG.md` 最近 3 节)。
19
+ 导出 `aboutOf` 供探针使用。
20
+ - `client/client.js`:卡片标题改用 `about.title`;展开后依次显示 **插件简介**、
21
+ **包含的组件(名称 + 简介两列表格)**,底部新增可折叠 **更新日志**(点按钮展开,等宽字体、可滚动)。
22
+ - 探针 `F:\dsh\001X\crossterm-work\probe-about.mjs`:7 项全 PASS(标题、简介、四组件顺序与简介、
23
+ 更新日志含本版且不超长、版本一致、缓存命中)。
24
+
3
25
  ## 0.6.1 — 2026-10-19
4
26
 
5
27
  ### 修:旧布局缓存挡住了新增的云端文档
package/README.md CHANGED
@@ -12,7 +12,7 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
12
12
 
13
13
  ## 零、简介与本版更新(每次发版重写这一节)
14
14
 
15
- **插件名称**:`dsh-crossterm` | **插件简介**:DSH 跨终端互联 —— 同一账号下的多个 DSH 终端/进程互发邮件与文件(主题内联、正文落盘,收件方自己读)。
15
+ **插件名称**:`dsh-crossterm`(插件页显示 **跨端互通**)| **插件简介**:同一 DSH 账号下的会话、终端、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份。
16
16
 
17
17
  **四个组件(插件页里就是这四个开关,名称与简介来自各自的 manifest)**
18
18
 
@@ -23,18 +23,17 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
23
23
  | 跨设备 | 经云端总线,在多台机器的 DSH 之间互发邮件与文件 | `term_cloud` |
24
24
  | 跨账号 | 预留位:后续用于跨账号互通,当前不挂工具 | — |
25
25
 
26
- **本版更新(0.6.1)**
27
-
28
- - **修:旧布局缓存把新增的云端文档挡住了**。0.5.0 / 0.6.0 说「命中旧缓存会自动补写」,实际没补 ——
29
- 云盘里根本没有 `_规则-账号.md`、`_规则-设备.md`、`inbox/_说明.md`。
30
- - 根因:`ensureLayout()` 命中缓存时直接沿用 `cached.paths`,而路径是 `buildPaths()` **算出来的**;
31
- 旧缓存没有 `accountRulesFile` / `deviceRulesFile` / `inboxReadmeFile` 三个字段,`ensureRules()` 拿到 `undefined`
32
- 就跳过,还照样把 `rulesVersion` 标成最新。
33
- - 修法:命中缓存时按 `home / account / deviceId` 重新算路径;`ensureRules()` 返回 `{ written, failed }`,
34
- **只有 `failed === 0` 才把 `rulesVersion` 抬上去**,否则保持 0 下次继续重试。
35
- - 探针 `probe-layout-stale-cache.mjs` 12 项全 PASS(旧缓存→重算→补写三份文档→写回新缓存;
36
- 第二次不重复上传;上传失败时不冒充最新),回归 26 + 42 + 17 项全 PASS。
37
- - 上一版(0.6.0)的内容仍在:共享收件箱里的 `_说明.md`、中转站 `isEnvelopeFile()` 只认 `.json` 信封。
26
+ > 插件页里还会多出一行 **跨端互通**(组件列表最后一行):它是「入口行」,裸包名加载浏览器半端,
27
+ > 承载插件标题与配置卡片,不挂任何工具。浏览器 client 半端只认裸包名行,所以这一行没法省。
28
+
29
+ **本版更新(0.6.2)**
30
+
31
+ - **插件页终于像个插件了**:插件改名 **跨端互通**(原来叫「跨会话」,看起来像只管会话),
32
+ 并写上了插件级简介;四个业务组件名保持不变。
33
+ - 配置卡片里新增:**插件简介**、**包含的组件(名称 + 简介两列表格)**、底部可折叠 **更新日志**
34
+ (直接读包内 `CHANGELOG.md`,展开就能看最近三版改了什么)。
35
+ - 数据来自新接口 `GET /crossterm/api/about`(组件名与简介读 `parts/*.manifest.json`,与组件行同源)。
36
+ - 上一版(0.6.1)的内容仍在:旧布局缓存不再挡住云端规则文档与 `inbox/_说明.md`。
38
37
 
39
38
  ## 一、安装与卸载
40
39
 
package/client/client.js CHANGED
@@ -63,6 +63,23 @@ window.__ModuleLoader__.load({
63
63
  fontSize: 12,
64
64
  },
65
65
  msg: { fontSize: 12, opacity: 0.8, whiteSpace: "pre-wrap", wordBreak: "break-word" },
66
+ intro: { lineHeight: 1.6, opacity: 0.85 },
67
+ sectionTitle: { fontWeight: 600, fontSize: 12, opacity: 0.7 },
68
+ comp: { display: "grid", gridTemplateColumns: "max-content 1fr", gap: "4px 12px" },
69
+ compName: { fontWeight: 600 },
70
+ compDesc: { opacity: 0.75, lineHeight: 1.5 },
71
+ log: {
72
+ whiteSpace: "pre-wrap",
73
+ fontSize: 11.5,
74
+ lineHeight: 1.55,
75
+ opacity: 0.85,
76
+ maxHeight: 240,
77
+ overflow: "auto",
78
+ background: "rgba(128,128,128,.09)",
79
+ borderRadius: 8,
80
+ padding: "8px 10px",
81
+ fontFamily: "ui-monospace, Consolas, monospace",
82
+ },
66
83
  switchOn: {
67
84
  width: 34,
68
85
  height: 18,
@@ -176,6 +193,8 @@ window.__ModuleLoader__.load({
176
193
  const [busy, setBusy] = React.useState("");
177
194
  const [msg, setMsg] = React.useState("");
178
195
  const [devices, setDevices] = React.useState(null);
196
+ const [about, setAbout] = React.useState(null);
197
+ const [showLog, setShowLog] = React.useState(false);
179
198
 
180
199
  const load = React.useCallback(() => {
181
200
  call("/crossterm/api/status")
@@ -205,6 +224,16 @@ window.__ModuleLoader__.load({
205
224
  if (open && !devices) void loadDevices();
206
225
  }, [open, devices, loadDevices]);
207
226
 
227
+ // 插件自述与更新日志(0.6.2):只读一次,读不到就退回内置文案
228
+ React.useEffect(() => {
229
+ if (!open || about) return;
230
+ call("/crossterm/api/about")
231
+ .then((data) => {
232
+ if (data && data.ok) setAbout(data);
233
+ })
234
+ .catch(() => {});
235
+ }, [open, about]);
236
+
208
237
  const onCheck = async () => {
209
238
  setBusy("check");
210
239
  setMsg("正在检查更新…");
@@ -344,7 +373,7 @@ window.__ModuleLoader__.load({
344
373
  h(
345
374
  "div",
346
375
  null,
347
- h("div", { style: S.title }, `dsh-crossterm ${version}`),
376
+ h("div", { style: S.title }, `${(about && about.title) || "跨端互通"} ${version}`),
348
377
  h("div", { style: S.sub }, subtitle),
349
378
  ),
350
379
  // h("span", { style: S.chevron }, open ? "\u2303" : "\u2304"),
@@ -354,6 +383,26 @@ window.__ModuleLoader__.load({
354
383
  h(
355
384
  "div",
356
385
  { style: S.body },
386
+ about && about.description ? h("div", { style: S.intro }, about.description) : null,
387
+ about && about.components && about.components.length
388
+ ? h(
389
+ "div",
390
+ null,
391
+ h("div", { style: S.sectionTitle }, "包含的组件"),
392
+ h(
393
+ "div",
394
+ { style: S.comp },
395
+ ...about.components.map((c) =>
396
+ h(
397
+ React.Fragment,
398
+ { key: c.part },
399
+ h("div", { style: S.compName }, c.name),
400
+ h("div", { style: S.compDesc }, c.description || "—"),
401
+ ),
402
+ ),
403
+ ),
404
+ )
405
+ : null,
357
406
  h(FriendlyRow, { label: "当前版本", value: status ? `v${status.version}${mode === "link" ? "(源码 link:)" : ""}` : "" }),
358
407
  h(FriendlyRow, {
359
408
  label: "当前终端",
@@ -438,6 +487,18 @@ window.__ModuleLoader__.load({
438
487
  h("button", { style: S.btn, onClick: onBackupNow, disabled: busy !== "" }, busy === "backup" ? "备份中…" : "备份会话"),
439
488
  ),
440
489
  msg ? h("div", { style: S.msg }, msg) : null,
490
+ about && about.changelog
491
+ ? h(
492
+ "div",
493
+ { style: { display: "flex", flexDirection: "column", gap: 8 } },
494
+ h("div", null, h(
495
+ "button",
496
+ { style: S.btn, onClick: () => setShowLog((v) => !v) },
497
+ showLog ? "收起更新日志" : "更新日志",
498
+ )),
499
+ showLog ? h("div", { style: S.log }, about.changelog) : null,
500
+ )
501
+ : null,
441
502
  ),
442
503
  );
443
504
  }
package/cordis.patch.yml CHANGED
@@ -8,8 +8,10 @@
8
8
  # dsh plugin --profile desktop remove dsh-crossterm
9
9
  #
10
10
  # 每个 insert 行在 DSH「插件 → dsh-crossterm → 包含的组件」里是一行组件,可单独开关。
11
- # 四行指向同一个包,只是走不同子路径:
11
+ # 五行指向同一个包,只是走不同子路径:
12
12
  #
13
+ # dsh-crossterm 入口:插件页配置卡片(浏览器半端)+ 全插件简介/更新日志,
14
+ # 不挂任何工具(config.part: ui)
13
15
  # dsh-crossterm/parts/session 跨会话:term_link / term_unlink / term_broadcast / term_inbox / term_config
14
16
  # dsh-crossterm/parts/terminal 跨终端:term_whoami / term_list / term_send / term_send_files
15
17
  # dsh-crossterm/parts/device 跨设备:term_cloud(云端总线同步)
@@ -18,15 +20,19 @@
18
20
  # 组件名来自 package.json 的 exports 映射:
19
21
  # "./parts/session/package.json" -> "./parts/session.manifest.json"
20
22
  # 每个组件显示自己的中文名,实际加载的仍是 lib/index.js,靠 config.part 决定挂哪组工具。
21
- # 四个组件在同一 DSH 进程里共用一份总线实例:心跳只起一次,云同步只在 device 组件启用
23
+ #
24
+ # 为什么入口行放在最后:浏览器的 client 半端只对「裸包名」行生效(dsh-client-modules 的
25
+ # locatePkgJson 对子路径行直接返回 undefined),而插件页顶部标题与裸包名行共用同一份
26
+ # locale/zh.json 的 meta —— 插件叫「跨端互通」、四个业务组件叫跨会话/跨终端/跨设备/跨账号,
27
+ # 只能让裸包名行单独做入口,且排在组件列表末尾,四个组件仍是列表里的前四行。
28
+ #
29
+ # 五个组件在同一 DSH 进程里共用一份总线实例:心跳只起一次,云同步只在 device 组件启用
22
30
  # 且 config.json 里 cloudEnabled=true 时才跑。安装/改动后需重启对应的 DSH 进程。
23
31
  # ============================================================================
24
32
 
25
33
  - insert:
26
34
  - id: crossterm-session
27
- # 第一行用裸包名:浏览器的 client 半端只对「裸包名」行生效,
28
- # 插件页里的配置卡片就挂在这一行上。名字仍由 locale/zh.json 的 meta.title 提供。
29
- name: dsh-crossterm
35
+ name: dsh-crossterm/parts/session
30
36
  config:
31
37
  part: session
32
38
  - id: crossterm-terminal
@@ -40,4 +46,10 @@
40
46
  - id: crossterm-account
41
47
  name: dsh-crossterm/parts/account
42
48
  config:
43
- part: account
49
+ part: account
50
+ - id: crossterm-ui
51
+ # 裸包名:浏览器 client 半端只认这一行,插件页配置卡片挂在这里。
52
+ # 也是插件页顶部标题/简介的来源(locale/zh.json 的 meta)。
53
+ name: dsh-crossterm
54
+ config:
55
+ part: ui
package/lib/bus.js CHANGED
@@ -22,7 +22,7 @@ import os from 'node:os'
22
22
  import path from 'node:path'
23
23
  import crypto from 'node:crypto'
24
24
 
25
- export const VERSION = '0.6.1'
25
+ export const VERSION = '0.6.2'
26
26
 
27
27
  /** DSH 的家目录:与宿主其它状态(sessions / profiles)同一个根,多终端天然共享 */
28
28
  export function dshHome() {
package/lib/index.js CHANGED
@@ -1315,10 +1315,11 @@ const TOOL_PART = {
1315
1315
  term_cloud: 'device',
1316
1316
  }
1317
1317
 
1318
- const PARTS = ['all', 'session', 'terminal', 'device', 'account']
1318
+ const PARTS = ['all', 'ui', 'session', 'terminal', 'device', 'account']
1319
1319
 
1320
1320
  const PART_LABEL = {
1321
1321
  all: '全部组件',
1322
+ ui: '入口(插件卡片)',
1322
1323
  session: '跨会话',
1323
1324
  terminal: '跨终端',
1324
1325
  device: '跨设备',
package/lib/web.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import fs from 'node:fs'
4
4
  import os from 'node:os'
5
5
  import path from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
6
7
  import { spawn } from 'node:child_process'
7
8
  import { VERSION, readConfig, writeConfig } from './bus.js'
8
9
  import { createPan } from './cloud/pan.js'
@@ -11,6 +12,8 @@ import { runSetup } from './cloud/setup.js'
11
12
 
12
13
  const PKG = 'dsh-crossterm'
13
14
  const NPM_REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org']
15
+ // 包根目录:lib/web.js 的上一级(README.md / CHANGELOG.md / locale / parts 都在这里)
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
14
17
 
15
18
  let installed = false
16
19
  let setupRunning = false
@@ -263,6 +266,63 @@ function statusOf(term) {
263
266
  }
264
267
  }
265
268
 
269
+ /**
270
+ * 插件自述(0.6.2):插件标题/简介 + 四个组件的名称与简介 + 最近几版更新日志。
271
+ * 卡片(浏览器半端)与插件页顶部共用 locale/zh.json 的 meta,这里读同一份文件,
272
+ * 保证「插件页怎么显示」和「卡片里怎么显示」永远一致。
273
+ */
274
+ const PART_ORDER = [
275
+ { part: 'session', fallback: '跨会话' },
276
+ { part: 'terminal', fallback: '跨终端' },
277
+ { part: 'device', fallback: '跨设备' },
278
+ { part: 'account', fallback: '跨账号' },
279
+ ]
280
+
281
+ let aboutCache = null
282
+
283
+ function readChangelog(maxSections = 3, maxChars = 6000) {
284
+ try {
285
+ const text = fs.readFileSync(path.join(ROOT, 'CHANGELOG.md'), 'utf8')
286
+ const chunks = text.split(/^## /m)
287
+ const head = chunks.shift() || ''
288
+ const picked = chunks.slice(0, maxSections).map((c) => '## ' + c.trim())
289
+ const body = picked.join('\n\n')
290
+ const out = body || head.trim()
291
+ return out.length > maxChars ? out.slice(0, maxChars) + '\n…' : out
292
+ } catch {
293
+ return ''
294
+ }
295
+ }
296
+
297
+ function aboutOf() {
298
+ if (aboutCache && aboutCache.version === VERSION) return aboutCache
299
+ let meta = {}
300
+ try {
301
+ const doc = JSON.parse(fs.readFileSync(path.join(ROOT, 'locale', 'zh.json'), 'utf8'))
302
+ meta = (doc && doc.meta) || {}
303
+ } catch { /* ignore */ }
304
+ const components = PART_ORDER.map((item) => {
305
+ let doc = {}
306
+ try {
307
+ doc = JSON.parse(fs.readFileSync(path.join(ROOT, 'parts', `${item.part}.manifest.json`), 'utf8'))
308
+ } catch { /* ignore */ }
309
+ return {
310
+ part: item.part,
311
+ name: doc.name || item.fallback,
312
+ description: doc.description || '',
313
+ }
314
+ })
315
+ aboutCache = {
316
+ name: PKG,
317
+ version: VERSION,
318
+ title: meta.title || PKG,
319
+ description: meta.description || '',
320
+ components,
321
+ changelog: readChangelog(),
322
+ }
323
+ return aboutCache
324
+ }
325
+
266
326
  /**
267
327
  * 只注册一次;行被关闭时随 fiber 一起注销,重新启用会再注册。
268
328
  */
@@ -281,6 +341,19 @@ export function registerWeb(ctx, term, log) {
281
341
  handler: (req, res) => json(res, 200, statusOf(term)),
282
342
  }),
283
343
  )
344
+ disposers.push(
345
+ webCtx.webServer.register({
346
+ kind: 'exact',
347
+ path: '/crossterm/api/about',
348
+ handler: (req, res) => {
349
+ try {
350
+ json(res, 200, { ok: true, ...aboutOf() })
351
+ } catch (error) {
352
+ json(res, 200, { ok: false, message: String((error && error.message) || error) })
353
+ }
354
+ },
355
+ }),
356
+ )
284
357
  disposers.push(
285
358
  webCtx.webServer.register({
286
359
  kind: 'exact',
@@ -525,4 +598,4 @@ export function registerWeb(ctx, term, log) {
525
598
  }
526
599
 
527
600
  // 只给探针用:把内部小工具导出来,宿主运行时不依赖这些导出
528
- export { decodeOutput, runtimeTools, looksMissingCommand, updateErrorText }
601
+ export { decodeOutput, runtimeTools, looksMissingCommand, updateErrorText, aboutOf }
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "Cross-session",
4
- "description": "Link the sessions inside this terminal: chain codes, inline mail, inbox"
3
+ "title": "CrossTerm",
4
+ "description": "Connect sessions, terminals and devices of one DSH account: chain codes, inline mail, a cloud relay and automatic backups. Four components, each with its own switch."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "跨会话",
4
- "description": "同一终端里的多个会话之间互通:特征码链、内联邮件、收件箱"
3
+ "title": "跨端互通",
4
+ "description": "同一 DSH 账号下的会话、终端、设备之间互通:特征码链、内联邮件、云端中转站与自动备份。四个组件可单独开关。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-crossterm",
3
- "version": "0.6.1",
4
- "description": "DSH 跨终端互联:同一账号下的多个 DSH 终端/进程互发邮件与文件(主题内联、正文落盘,收件方自己读)",
3
+ "version": "0.6.2",
4
+ "description": "跨端互通:同一 DSH 账号下的会话、终端、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "exports": {