dsh-crossterm 0.6.1 → 0.7.0
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 +40 -0
- package/README.md +22 -23
- package/client/client.js +62 -1
- package/cordis.patch.yml +29 -12
- package/lib/bus.js +1 -1
- package/lib/index.js +20 -2
- package/lib/web.js +76 -1
- package/locale/en.json +3 -2
- package/locale/zh.json +3 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# dsh-crossterm 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.7.0 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
### 入口行升格为「总开关」,排到组件列表第一位
|
|
6
|
+
- 用户要求(paraphrased):把入口那一行放到第一个,开关做成和插件开关一样的**总开关**,并标注清楚。
|
|
7
|
+
- `cordis.patch.yml` 顺序变化:`crossterm-ui`(裸包名 `dsh-crossterm`,`config.part: ui`)从列表末尾
|
|
8
|
+
移到**第一位**,其后依次是 session / terminal / device / account。五行指向同一个包,顺序只影响展示。
|
|
9
|
+
- **总开关是真开关**,不是只关卡片(`lib/index.js` 的 `apply()`):
|
|
10
|
+
- `part === 'ui'` 时 `root.provide('crossterm', { master: true, part, since })`;
|
|
11
|
+
- 其余四个组件改为 `root.inject(['agents', 'crossterm'], ...)` —— **总开关关掉时服务不存在,
|
|
12
|
+
这四个回调根本不会执行**,工具、心跳、云同步、云端中转站、会话备份一起停;
|
|
13
|
+
- 总开关开着时,四个组件之间仍可各自单独开关;
|
|
14
|
+
- 总开关 3 秒内没出现时打一行 `console.warn`(`总开关(入口组件)未启用,组件「X」已跳过挂载`)方便排查;
|
|
15
|
+
- 用 cordis 服务注入而不是「读配置 / 看加载顺序」,所以谁先加载都不影响结果。
|
|
16
|
+
- 标注清楚:`locale/zh.json` 的 `meta.title` 改为 **跨端互通(总开关)**,`meta.description` 写明
|
|
17
|
+
「关掉这一项,下面四个组件全部停用……开着它时四个组件可各自单独开关」;
|
|
18
|
+
新增 `meta.shortTitle = 跨端互通`,配置卡片标题用短名(`GET /crossterm/api/about` 多返回一个 `shortTitle`)。
|
|
19
|
+
- 探针 `probe-about.mjs` 改为断言 `title === '跨端互通(总开关)'` 且 `shortTitle === '跨端互通'`。
|
|
20
|
+
|
|
21
|
+
## 0.6.2 — 2026-10-19
|
|
22
|
+
|
|
23
|
+
### 插件页:插件名/简介/组件表/更新日志
|
|
24
|
+
- 插件改名:`locale/zh.json` 的 `meta.title` 从「跨会话」改为 **跨端互通**(英文 `CrossTerm`),
|
|
25
|
+
`meta.description` 写成插件级简介(跨会话/跨终端/跨设备/云端中转与备份,一句话说清)。
|
|
26
|
+
**四个业务组件名不变**:跨会话 / 跨终端 / 跨设备 / 跨账号。
|
|
27
|
+
- `cordis.patch.yml` 结构变化:原来第一行用裸包名 `dsh-crossterm` 承担「跨会话」,
|
|
28
|
+
现在拆成两行 —— 第一行 `dsh-crossterm/parts/session`(跨会话),**末尾新增裸包名行**
|
|
29
|
+
`dsh-crossterm`(`config.part: ui`,不挂任何工具,只承载浏览器半端的配置卡片与插件标题)。
|
|
30
|
+
原因:`dsh-client-modules` 只对裸包名行加载 client bundle(`locatePkgJson()` 对子路径行返回
|
|
31
|
+
undefined),而插件页顶部标题与裸包名行共用同一份 locale meta,二者无法分成两个名字;
|
|
32
|
+
把裸行放到组件列表末尾,前四行仍是四个业务组件。
|
|
33
|
+
- `lib/index.js`:`PARTS` 增 `ui`,`PART_LABEL.ui = '入口(插件卡片)'`;`ui` 不映射任何工具
|
|
34
|
+
(`TOOL_PART` 里没有它,`buildTools()` 过滤后为空),所以不会重复注册工具。
|
|
35
|
+
- `lib/web.js` 新增 `GET /crossterm/api/about`:返回 `title` / `description` / `components[]`
|
|
36
|
+
(读 `parts/*.manifest.json`,与组件行显示同源)/ `changelog`(读包内 `CHANGELOG.md` 最近 3 节)。
|
|
37
|
+
导出 `aboutOf` 供探针使用。
|
|
38
|
+
- `client/client.js`:卡片标题改用 `about.title`;展开后依次显示 **插件简介**、
|
|
39
|
+
**包含的组件(名称 + 简介两列表格)**,底部新增可折叠 **更新日志**(点按钮展开,等宽字体、可滚动)。
|
|
40
|
+
- 探针 `F:\dsh\001X\crossterm-work\probe-about.mjs`:7 项全 PASS(标题、简介、四组件顺序与简介、
|
|
41
|
+
更新日志含本版且不超长、版本一致、缓存命中)。
|
|
42
|
+
|
|
3
43
|
## 0.6.1 — 2026-10-19
|
|
4
44
|
|
|
5
45
|
### 修:旧布局缓存挡住了新增的云端文档
|
package/README.md
CHANGED
|
@@ -12,29 +12,28 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
12
12
|
|
|
13
13
|
## 零、简介与本版更新(每次发版重写这一节)
|
|
14
14
|
|
|
15
|
-
**插件名称**:`dsh-crossterm
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| 组件名称 |
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- 上一版(0.6.0)的内容仍在:共享收件箱里的 `_说明.md`、中转站 `isEnvelopeFile()` 只认 `.json` 信封。
|
|
15
|
+
**插件名称**:`dsh-crossterm`(插件页显示 **跨端互通(总开关)**)| **插件简介**:同一 DSH 账号下的会话、终端、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份。
|
|
16
|
+
|
|
17
|
+
**组件列表(插件页第一个是总开关,后面四个是业务组件,名称与简介来自各自的 manifest)**
|
|
18
|
+
|
|
19
|
+
| 顺序 | 组件名称 | 作用 | 工具 |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| ① | 跨端互通(总开关) | 插件总开关:关掉它,下面四个组件全部停用(工具、心跳、云同步、中转站、备份一起停);开着时四个组件可各自单独开关。裸包名行,承载插件标题/简介与配置卡片,不挂工具 | — |
|
|
22
|
+
| ② | 跨会话 | 同一终端里的多个会话之间互通:特征码链、内联邮件、收件箱 | `term_link` `term_unlink` `term_broadcast` `term_inbox` `term_config` |
|
|
23
|
+
| ③ | 跨终端 | 同一台机器上的多个 DSH 终端/进程之间互发邮件与文件 | `term_whoami` `term_list` `term_send` `term_send_files` |
|
|
24
|
+
| ④ | 跨设备 | 经云端总线,在多台机器的 DSH 之间互发邮件与文件 | `term_cloud` |
|
|
25
|
+
| ⑤ | 跨账号 | 预留位:后续用于跨账号互通,当前不挂工具 | — |
|
|
26
|
+
|
|
27
|
+
> 总开关为什么必须用裸包名:浏览器 client 半端只对裸包名行生效(`dsh-client-modules` 的
|
|
28
|
+
> `locatePkgJson()` 对子路径行返回 undefined),而插件页顶部标题与这一行共用同一份 `locale/zh.json` 的 meta,
|
|
29
|
+
> 所以它单独做总开关,标题带「(总开关)」;卡片头用 `shortTitle` 显示短名「跨端互通」。
|
|
30
|
+
|
|
31
|
+
**本版更新(0.7.0)**
|
|
32
|
+
|
|
33
|
+
- **第一个组件变成总开关**:排到列表最前,标注「跨端互通(总开关)」,说明写明关掉它会停掉下面四个组件。
|
|
34
|
+
- 它不再是「只关卡片的入口行」:现在用 cordis 服务注入实现 —— ui 行 `provide('crossterm')`,
|
|
35
|
+
其余四行 `inject` 它;**关掉总开关,那四个组件连回调都不会执行**,工具/心跳/云同步/中转站/备份一起停。
|
|
36
|
+
- 四个业务组件仍可各自单独开关(总开关开着时)。
|
|
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 },
|
|
376
|
+
h("div", { style: S.title }, `${(about && (about.shortTitle || 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# ============================================================================
|
|
2
|
-
# dsh-crossterm —— DSH bundle patch
|
|
2
|
+
# dsh-crossterm —— DSH bundle patch
|
|
3
3
|
# ============================================================================
|
|
4
4
|
# 安装(本地目录,对每个要互通的 profile 各装一次):
|
|
5
5
|
# dsh plugin --profile desktop add link:F:\dsh\001X\dsh-crossterm
|
|
@@ -7,26 +7,43 @@
|
|
|
7
7
|
# 卸载:
|
|
8
8
|
# dsh plugin --profile desktop remove dsh-crossterm
|
|
9
9
|
#
|
|
10
|
-
# 每个 insert 行在 DSH「插件 →
|
|
11
|
-
#
|
|
10
|
+
# 每个 insert 行在 DSH「插件 → 跨端互通 → 包含的组件」里是一行组件,可单独开关。
|
|
11
|
+
# 五行指向同一个包,只是走不同子路径 / 不同 config.part:
|
|
12
12
|
#
|
|
13
|
-
# dsh-crossterm
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
13
|
+
# ① dsh-crossterm 总开关(入口):提供 crossterm 服务,插件页标题/简介/配置卡片,
|
|
14
|
+
# 不挂任何工具(config.part: ui)。
|
|
15
|
+
# 关掉它 → 服务消失 → 下面四个组件全部停用
|
|
16
|
+
# (工具、心跳、云同步、中转站、会话备份一起停)。
|
|
17
|
+
# ② dsh-crossterm/parts/session 跨会话:term_link / term_unlink / term_broadcast / term_inbox / term_config
|
|
18
|
+
# ③ dsh-crossterm/parts/terminal 跨终端:term_whoami / term_list / term_send / term_send_files
|
|
19
|
+
# ④ dsh-crossterm/parts/device 跨设备:term_cloud(云端总线同步)
|
|
20
|
+
# ⑤ dsh-crossterm/parts/account 跨账号:预留占位,暂不挂工具
|
|
17
21
|
#
|
|
18
22
|
# 组件名来自 package.json 的 exports 映射:
|
|
19
23
|
# "./parts/session/package.json" -> "./parts/session.manifest.json"
|
|
20
24
|
# 每个组件显示自己的中文名,实际加载的仍是 lib/index.js,靠 config.part 决定挂哪组工具。
|
|
21
|
-
#
|
|
25
|
+
#
|
|
26
|
+
# 为什么总开关必须用「裸包名」:浏览器的 client 半端只对裸包名行生效
|
|
27
|
+
# (dsh-client-modules 的 locatePkgJson 对子路径行直接返回 undefined),而插件页顶部标题
|
|
28
|
+
# 与裸包名行共用同一份 locale/zh.json 的 meta —— 所以裸包名行单独做总开关,
|
|
29
|
+
# 标题写「跨端互通(总开关)」,四个业务组件排在它后面、名字仍是跨会话/跨终端/跨设备/跨账号。
|
|
30
|
+
#
|
|
31
|
+
# 总开关是「服务注入」实现的:ui 行 root.provide('crossterm'),其余四行 root.inject 它。
|
|
32
|
+
# 谁先加载不影响结果(cordis 的 inject 会等服务出现),顺序只为让列表好看。
|
|
33
|
+
#
|
|
34
|
+
# 五个组件在同一 DSH 进程里共用一份总线实例:心跳只起一次,云同步只在 device 组件启用
|
|
22
35
|
# 且 config.json 里 cloudEnabled=true 时才跑。安装/改动后需重启对应的 DSH 进程。
|
|
23
36
|
# ============================================================================
|
|
24
37
|
|
|
25
38
|
- insert:
|
|
26
|
-
- id: crossterm-
|
|
27
|
-
#
|
|
28
|
-
#
|
|
39
|
+
- id: crossterm-ui
|
|
40
|
+
# 总开关(入口):裸包名,提供 crossterm 服务 + 浏览器半端的配置卡片 + 插件页标题/简介。
|
|
41
|
+
# 关掉这一行等于整插件停用;它自己不挂工具。
|
|
29
42
|
name: dsh-crossterm
|
|
43
|
+
config:
|
|
44
|
+
part: ui
|
|
45
|
+
- id: crossterm-session
|
|
46
|
+
name: dsh-crossterm/parts/session
|
|
30
47
|
config:
|
|
31
48
|
part: session
|
|
32
49
|
- id: crossterm-terminal
|
|
@@ -40,4 +57,4 @@
|
|
|
40
57
|
- id: crossterm-account
|
|
41
58
|
name: dsh-crossterm/parts/account
|
|
42
59
|
config:
|
|
43
|
-
part: account
|
|
60
|
+
part: account
|
package/lib/bus.js
CHANGED
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: '跨设备',
|
|
@@ -1858,7 +1859,24 @@ export default {
|
|
|
1858
1859
|
name: PLUGIN,
|
|
1859
1860
|
apply(root, config) {
|
|
1860
1861
|
const part = normalizePart(config?.part)
|
|
1861
|
-
|
|
1862
|
+
// 「入口」这一行就是总开关:它提供 crossterm 服务,其余四个组件都 inject 它。
|
|
1863
|
+
// 关掉这一行 → 服务不存在 → 另外四个组件的 inject 回调根本不会执行,
|
|
1864
|
+
// 工具、心跳、云同步、中转站、会话备份一起停(不是只关卡片)。
|
|
1865
|
+
// 反过来,只要总开关开着,四个组件各自还能单独关。
|
|
1866
|
+
const isMaster = part === 'ui'
|
|
1867
|
+
if (isMaster) {
|
|
1868
|
+
root.provide('crossterm', { master: true, part, since: Date.now() })
|
|
1869
|
+
} else {
|
|
1870
|
+
const timer = setTimeout(() => {
|
|
1871
|
+
let alive = true
|
|
1872
|
+
try {
|
|
1873
|
+
alive = Boolean(root.get?.('crossterm'))
|
|
1874
|
+
} catch { alive = true }
|
|
1875
|
+
if (!alive) console.warn(`[dsh-crossterm] 总开关(入口组件)未启用,组件「${PART_LABEL[part]}」已跳过挂载`)
|
|
1876
|
+
}, 3000)
|
|
1877
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
1878
|
+
}
|
|
1879
|
+
root.inject(isMaster ? ['agents'] : ['agents', 'crossterm'], (ctx) => {
|
|
1862
1880
|
// 四个组件跑在同一个进程里,共用一份总线实例:各自只挂自己那组工具,
|
|
1863
1881
|
// 心跳/云同步只在第一个组件加载时启动一次,最后一个组件卸载时才停。
|
|
1864
1882
|
const term = acquireTerm(ctx)
|
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,65 @@ 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
|
+
// 插件页顶部标题要带「(总开关)」才看得懂这一行是什么,但卡片头用短名更清爽
|
|
320
|
+
shortTitle: meta.shortTitle || meta.title || PKG,
|
|
321
|
+
description: meta.description || '',
|
|
322
|
+
components,
|
|
323
|
+
changelog: readChangelog(),
|
|
324
|
+
}
|
|
325
|
+
return aboutCache
|
|
326
|
+
}
|
|
327
|
+
|
|
266
328
|
/**
|
|
267
329
|
* 只注册一次;行被关闭时随 fiber 一起注销,重新启用会再注册。
|
|
268
330
|
*/
|
|
@@ -281,6 +343,19 @@ export function registerWeb(ctx, term, log) {
|
|
|
281
343
|
handler: (req, res) => json(res, 200, statusOf(term)),
|
|
282
344
|
}),
|
|
283
345
|
)
|
|
346
|
+
disposers.push(
|
|
347
|
+
webCtx.webServer.register({
|
|
348
|
+
kind: 'exact',
|
|
349
|
+
path: '/crossterm/api/about',
|
|
350
|
+
handler: (req, res) => {
|
|
351
|
+
try {
|
|
352
|
+
json(res, 200, { ok: true, ...aboutOf() })
|
|
353
|
+
} catch (error) {
|
|
354
|
+
json(res, 200, { ok: false, message: String((error && error.message) || error) })
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
}),
|
|
358
|
+
)
|
|
284
359
|
disposers.push(
|
|
285
360
|
webCtx.webServer.register({
|
|
286
361
|
kind: 'exact',
|
|
@@ -525,4 +600,4 @@ export function registerWeb(ctx, term, log) {
|
|
|
525
600
|
}
|
|
526
601
|
|
|
527
602
|
// 只给探针用:把内部小工具导出来,宿主运行时不依赖这些导出
|
|
528
|
-
export { decodeOutput, runtimeTools, looksMissingCommand, updateErrorText }
|
|
603
|
+
export { decodeOutput, runtimeTools, looksMissingCommand, updateErrorText, aboutOf }
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "
|
|
4
|
-
"
|
|
3
|
+
"title": "CrossTerm (master switch)",
|
|
4
|
+
"shortTitle": "CrossTerm",
|
|
5
|
+
"description": "Master switch: turning this off stops everything below — tools, heartbeat, cloud sync, cloud relay and session backups for all four components (cross-session / cross-terminal / cross-device / cross-account). While it is on, each component can still be switched off individually."
|
|
5
6
|
}
|
|
6
7
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "
|
|
4
|
-
"
|
|
3
|
+
"title": "跨端互通(总开关)",
|
|
4
|
+
"shortTitle": "跨端互通",
|
|
5
|
+
"description": "总开关:关掉这一项,下面四个组件(跨会话 / 跨终端 / 跨设备 / 跨账号)全部停用 —— 工具、心跳、云同步、云端中转站与会话备份一起停。开着它时,四个组件可各自单独开关。插件简介:同一 DSH 账号下的会话、终端、设备之间互通。"
|
|
5
6
|
}
|
|
6
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-crossterm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "跨端互通:同一 DSH 账号下的会话、终端、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"exports": {
|