dsh-crossterm 0.3.31 → 0.3.33

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.
@@ -0,0 +1,296 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-crossterm",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ const React = require("react");
9
+ const h = React.createElement;
10
+
11
+ const S = {
12
+ card: {
13
+ border: "1px solid rgba(128,128,128,.28)",
14
+ borderRadius: 12,
15
+ background: "rgba(128,128,128,.06)",
16
+ color: "inherit",
17
+ fontSize: 13,
18
+ overflow: "hidden",
19
+ },
20
+ head: {
21
+ display: "flex",
22
+ alignItems: "center",
23
+ gap: 10,
24
+ padding: "12px 14px",
25
+ cursor: "pointer",
26
+ userSelect: "none",
27
+ },
28
+ title: { fontWeight: 600 },
29
+ sub: { opacity: 0.62, fontSize: 12, marginTop: 2 },
30
+ chevron: { marginLeft: "auto", opacity: 0.65, fontSize: 11, lineHeight: 1 },
31
+ body: {
32
+ borderTop: "1px solid rgba(128,128,128,.22)",
33
+ padding: "12px 14px",
34
+ display: "flex",
35
+ flexDirection: "column",
36
+ gap: 10,
37
+ },
38
+ grid: {
39
+ display: "grid",
40
+ gridTemplateColumns: "max-content 1fr",
41
+ gap: "6px 16px",
42
+ alignItems: "baseline",
43
+ },
44
+ key: { opacity: 0.6 },
45
+ val: { wordBreak: "break-all" },
46
+ actions: { display: "flex", gap: 8, flexWrap: "wrap" },
47
+ btn: {
48
+ padding: "5px 12px",
49
+ borderRadius: 8,
50
+ border: "1px solid rgba(128,128,128,.35)",
51
+ background: "rgba(128,128,128,.1)",
52
+ color: "inherit",
53
+ cursor: "pointer",
54
+ fontSize: 12,
55
+ },
56
+ btnPrimary: {
57
+ padding: "5px 12px",
58
+ borderRadius: 8,
59
+ border: "1px solid rgba(110,160,255,.5)",
60
+ background: "rgba(110,160,255,.16)",
61
+ color: "inherit",
62
+ cursor: "pointer",
63
+ fontSize: 12,
64
+ },
65
+ msg: { fontSize: 12, opacity: 0.8, whiteSpace: "pre-wrap", wordBreak: "break-word" },
66
+ switchOn: {
67
+ width: 34,
68
+ height: 18,
69
+ marginLeft: 8,
70
+ padding: 0,
71
+ position: "relative",
72
+ borderRadius: 9,
73
+ border: "1px solid rgba(110,160,255,.6)",
74
+ background: "rgba(110,160,255,.55)",
75
+ cursor: "pointer",
76
+ verticalAlign: "middle",
77
+ },
78
+ switchOff: {
79
+ width: 34,
80
+ height: 18,
81
+ marginLeft: 8,
82
+ padding: 0,
83
+ position: "relative",
84
+ borderRadius: 9,
85
+ border: "1px solid rgba(128,128,128,.45)",
86
+ background: "rgba(128,128,128,.22)",
87
+ cursor: "pointer",
88
+ verticalAlign: "middle",
89
+ },
90
+ knobOn: {
91
+ position: "absolute",
92
+ top: 2,
93
+ left: 17,
94
+ width: 12,
95
+ height: 12,
96
+ borderRadius: "50%",
97
+ background: "#fff",
98
+ transition: "left .15s",
99
+ },
100
+ knobOff: {
101
+ position: "absolute",
102
+ top: 2,
103
+ left: 3,
104
+ width: 12,
105
+ height: 12,
106
+ borderRadius: "50%",
107
+ background: "#fff",
108
+ transition: "left .15s",
109
+ },
110
+ };
111
+
112
+ async function call(pathname, method) {
113
+ const res = await fetch(pathname, { method: method || "GET" });
114
+ const text = await res.text();
115
+ let data;
116
+ try {
117
+ data = JSON.parse(text);
118
+ } catch {
119
+ data = { ok: false, message: text.slice(0, 200) };
120
+ }
121
+ if (!res.ok && data && !data.message) data.message = "HTTP " + res.status;
122
+ return data;
123
+ }
124
+
125
+ function FriendlyRow(props) {
126
+ return h(
127
+ "div",
128
+ { style: S.grid },
129
+ h("div", { style: S.key }, props.label),
130
+ h("div", { style: S.val }, props.value || "—"),
131
+ );
132
+ }
133
+
134
+ function Card() {
135
+ const [open, setOpen] = React.useState(false);
136
+ const [status, setStatus] = React.useState(null);
137
+ const [busy, setBusy] = React.useState("");
138
+ const [msg, setMsg] = React.useState("");
139
+
140
+ const load = React.useCallback(() => {
141
+ call("/crossterm/api/status")
142
+ .then((data) => {
143
+ if (data && data.ok) setStatus(data);
144
+ else setMsg((data && data.message) || "状态不可用");
145
+ })
146
+ .catch((error) => setMsg("状态不可用:" + String((error && error.message) || error)));
147
+ }, []);
148
+
149
+ React.useEffect(() => {
150
+ load();
151
+ }, [load]);
152
+
153
+ const onCheck = async () => {
154
+ setBusy("check");
155
+ setMsg("正在检查更新…");
156
+ try {
157
+ const data = await call("/crossterm/api/check", "POST");
158
+ if (!data || !data.ok) setMsg("检查失败:" + ((data && data.message) || "未知错误"));
159
+ else if (data.updateAvailable) setMsg(`发现新版本 ${data.latest}(当前 ${data.current})`);
160
+ else if (data.ahead) setMsg(`本地 ${data.current} 领先已发布版 ${data.latest}`);
161
+ else setMsg(`已是最新版本 ${data.current}`);
162
+ } catch (error) {
163
+ setMsg("检查失败:" + String((error && error.message) || error));
164
+ } finally {
165
+ setBusy("");
166
+ }
167
+ };
168
+
169
+ const onUpdate = async () => {
170
+ setBusy("update");
171
+ setMsg("正在更新…");
172
+ try {
173
+ const data = await call("/crossterm/api/update", "POST");
174
+ setMsg((data && data.message) || (data && data.ok ? "已更新" : "未能更新"));
175
+ } catch (error) {
176
+ setMsg("更新失败:" + String((error && error.message) || error));
177
+ } finally {
178
+ setBusy("");
179
+ }
180
+ };
181
+
182
+ const onToggleCloud = async () => {
183
+ const running = Boolean((status && status.cloud && status.cloud.running) || false);
184
+ setBusy("cloud");
185
+ setMsg(running ? "正在关闭云同步…" : "正在开启云同步…");
186
+ try {
187
+ const data = await call(running ? "/crossterm/api/cloud/stop" : "/crossterm/api/cloud/start", "POST");
188
+ setMsg((data && data.message) || (data && data.ok ? "已切换" : "未能切换"));
189
+ if (data && data.cloud && status) setStatus({ ...status, cloud: data.cloud });
190
+ } catch (error) {
191
+ setMsg("切换失败:" + String((error && error.message) || error));
192
+ } finally {
193
+ setBusy("");
194
+ }
195
+ };
196
+
197
+ const version = status ? "v" + status.version : "";
198
+ const mode = status && status.install ? status.install.mode : "";
199
+ const cloud = (status && status.cloud) || {};
200
+ const subtitle = status
201
+ ? `${status.node || "本机"} · ${status.chains && status.chains.length ? status.chains.join(" / ") : "未入链"}`
202
+ : "加载中…";
203
+
204
+ return h(
205
+ "div",
206
+ { style: S.card },
207
+ h(
208
+ "div",
209
+ {
210
+ style: S.head,
211
+ onClick: () => setOpen((v) => !v),
212
+ role: "button",
213
+ tabIndex: 0,
214
+ },
215
+ h(
216
+ "div",
217
+ null,
218
+ h("div", { style: S.title }, `dsh-crossterm ${version}`),
219
+ h("div", { style: S.sub }, subtitle),
220
+ ),
221
+ // h("span", { style: S.chevron }, open ? "\u2303" : "\u2304"),
222
+ h("span", { style: S.chevron }, open ? "▲" : "▼"),
223
+ ),
224
+ open &&
225
+ h(
226
+ "div",
227
+ { style: S.body },
228
+ h(FriendlyRow, { label: "当前版本", value: status ? `v${status.version}${mode === "link" ? "(源码 link:)" : ""}` : "" }),
229
+ h(FriendlyRow, {
230
+ label: "当前终端",
231
+ value: status ? `${status.node}(${status.nodeId})· ${status.hostname}` : "",
232
+ }),
233
+ h(
234
+ "div",
235
+ { style: S.grid },
236
+ h("div", { style: S.key }, "云端位置"),
237
+ h(
238
+ "div",
239
+ { style: S.val },
240
+ h(
241
+ "span",
242
+ null,
243
+ status
244
+ ? `${(cloud.provider && cloud.provider.name) || "云端"} · ${cloud.root || "—"}${cloud.running ? "(同步中)" : "(未同步)"}`
245
+ : "—",
246
+ ),
247
+ status
248
+ ? h(
249
+ "button",
250
+ {
251
+ style: cloud.running ? S.switchOn : S.switchOff,
252
+ onClick: (event) => {
253
+ event.stopPropagation();
254
+ void onToggleCloud();
255
+ },
256
+ disabled: busy !== "",
257
+ title: cloud.running ? "点击关闭云同步" : "点击开启云同步",
258
+ "aria-label": cloud.running ? "云同步开启中" : "云同步已关闭",
259
+ },
260
+ h("span", { style: cloud.running ? S.knobOn : S.knobOff }),
261
+ )
262
+ : null,
263
+ ),
264
+ ),
265
+ h(
266
+ "div",
267
+ { style: S.actions },
268
+ h("button", { style: S.btn, onClick: onCheck, disabled: busy !== "" }, busy === "check" ? "检查中…" : "检查更新"),
269
+ h("button", { style: S.btnPrimary, onClick: onUpdate, disabled: busy !== "" }, busy === "update" ? "更新中…" : "更新"),
270
+ ),
271
+ msg ? h("div", { style: S.msg }, msg) : null,
272
+ ),
273
+ );
274
+ }
275
+
276
+ const name = "dsh-crossterm";
277
+ const inject = ["slots"];
278
+
279
+ function apply(ctx) {
280
+ ctx.slots.inject("plugins.bundle.config", () =>
281
+ ctx.slots.register(
282
+ {
283
+ name: "plugins.bundle.config",
284
+ key: "dsh-crossterm",
285
+ },
286
+ Card,
287
+ ),
288
+ );
289
+ }
290
+
291
+ exports.name = name;
292
+ exports.inject = inject;
293
+ exports.apply = apply;
294
+ return module.exports;
295
+ },
296
+ });
package/cordis.patch.yml CHANGED
@@ -12,7 +12,7 @@
12
12
  #
13
13
  # dsh-crossterm/parts/session 跨会话:term_link / term_unlink / term_broadcast / term_inbox / term_config
14
14
  # dsh-crossterm/parts/terminal 跨终端:term_whoami / term_list / term_send / term_send_files
15
- # dsh-crossterm/parts/device 跨设备:term_cloud(百度网盘云总线)
15
+ # dsh-crossterm/parts/device 跨设备:term_cloud(云端总线同步)
16
16
  # dsh-crossterm/parts/account 跨账号:预留占位,暂不挂工具
17
17
  #
18
18
  # 组件名来自 package.json 的 exports 映射:
@@ -24,7 +24,9 @@
24
24
 
25
25
  - insert:
26
26
  - id: crossterm-session
27
- name: dsh-crossterm/parts/session
27
+ # 第一行用裸包名:浏览器的 client 半端只对「裸包名」行生效,
28
+ # 插件页里的配置卡片就挂在这一行上。名字仍由 locale/zh.json 的 meta.title 提供。
29
+ name: dsh-crossterm
28
30
  config:
29
31
  part: session
30
32
  - id: crossterm-terminal
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.3.31'
25
+ export const VERSION = '0.3.33'
26
26
 
27
27
  /** DSH 的家目录:与宿主其它状态(sessions / profiles)同一个根,多终端天然共享 */
28
28
  export function dshHome() {
package/lib/index.js CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  import { deriveNameFromFile, findTranscript } from './names.js'
37
37
  import { createPan } from './cloud/pan.js'
38
38
  import { createCloud } from './cloud/sync.js'
39
+ import { registerWeb } from './web.js'
39
40
 
40
41
  const PLUGIN = 'dsh-crossterm'
41
42
 
@@ -1615,7 +1616,7 @@ function buildTools(term, agent, part = 'all') {
1615
1616
 
1616
1617
  const cloud = defineTool({
1617
1618
  name: 'term_cloud',
1618
- description: '云总线同步(百度网盘):查看/开启/关闭本机总线与网盘目录的镜像同步,或立刻同步一次。跨设备互通靠它把邮件、收件箱、链、链接、在线状态搬到云端。',
1619
+ description: '云总线同步:查看/开启/关闭本机总线与云端目录的镜像同步,或立刻同步一次。跨设备互通靠它把邮件、收件箱、链、链接、在线状态搬到云端。',
1619
1620
  parameters: {
1620
1621
  action: { type: 'string', enum: ['status', 'start', 'stop', 'once'], description: 'status=查看状态;start=开启(本进程内生效);stop=关闭;once=立刻同步一次' },
1621
1622
  },
@@ -1703,6 +1704,10 @@ export default {
1703
1704
  // 心跳/云同步只在第一个组件加载时启动一次,最后一个组件卸载时才停。
1704
1705
  const term = acquireTerm(ctx)
1705
1706
  term.parts.add(part)
1707
+ // 插件页卡片的宿主半端:三条路由只注册一次,读版本/终端/云端位置,并支持检查更新与更新
1708
+ try {
1709
+ registerWeb(ctx, term, (line) => void term.warn(line))
1710
+ } catch { /* ignore */ }
1706
1711
  const mounted = new Map()
1707
1712
 
1708
1713
  const mount = (agent) => {
package/lib/web.js ADDED
@@ -0,0 +1,292 @@
1
+ // 插件页卡片的宿主半端:注册三条本地 HTTP 路由,供浏览器半端(client/client.js)读取
2
+ // 状态、检查 npm 上的新版本、一键更新(仅 registry 安装模式可用)。
3
+ import fs from 'node:fs'
4
+ import os from 'node:os'
5
+ import path from 'node:path'
6
+ import { spawn } from 'node:child_process'
7
+ import { VERSION } from './bus.js'
8
+
9
+ const PKG = 'dsh-crossterm'
10
+ const NPM_REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org']
11
+
12
+ let installed = false
13
+
14
+ function json(res, code, data) {
15
+ const body = JSON.stringify(data)
16
+ res.writeHead(code, {
17
+ 'content-type': 'application/json; charset=utf-8',
18
+ 'cache-control': 'no-store',
19
+ })
20
+ res.end(body)
21
+ }
22
+
23
+ /** 找到装着本插件的 profile(<dsh-home>/profiles/<name>/package.json) */
24
+ function profileInfo(busDir) {
25
+ // 先找「正在运行的那个 profile」:桌面宿主把 profile 目录作为 argv 传给自己,
26
+ // 形如 "...DeepSeek Harness.exe <app.asar/dsh> <dsh-home>/profiles/desktop <runtime>..."。
27
+ // 这台机器上 desktop / crossterm-test / crossterm-web 三个 profile 都装了本插件,
28
+ // 只按目录字母序扫会一直报 crossterm-test(DSH_PROFILE 只注入给工具子进程,宿主进程没有)。
29
+ for (const arg of process.argv.slice(0, 10)) {
30
+ if (typeof arg !== 'string') continue
31
+ const hit = /[\\/]profiles[\\/]([^\\/]+)$/.exec(arg)
32
+ if (!hit) continue
33
+ try {
34
+ const doc = JSON.parse(fs.readFileSync(path.join(arg, 'package.json'), 'utf8'))
35
+ const spec = doc && doc.dependencies && doc.dependencies[PKG]
36
+ if (typeof spec === 'string' && spec) return { name: hit[1], dir: arg, spec }
37
+ } catch {
38
+ /* ignore */
39
+ }
40
+ }
41
+ const envName = process.env.DSH_PROFILE
42
+ const envDir = process.env.DSH_PROFILE_DIR
43
+ if (envDir) {
44
+ try {
45
+ const doc = JSON.parse(fs.readFileSync(path.join(envDir, 'package.json'), 'utf8'))
46
+ const spec = doc && doc.dependencies && doc.dependencies[PKG]
47
+ if (typeof spec === 'string' && spec) {
48
+ return { name: envName || path.basename(envDir), dir: envDir, spec }
49
+ }
50
+ } catch {
51
+ /* ignore */
52
+ }
53
+ }
54
+ const home = path.dirname(busDir || '')
55
+ const dir = path.join(home, 'profiles')
56
+ try {
57
+ for (const name of fs.readdirSync(dir)) {
58
+ let doc
59
+ try {
60
+ doc = JSON.parse(fs.readFileSync(path.join(dir, name, 'package.json'), 'utf8'))
61
+ } catch {
62
+ continue
63
+ }
64
+ const spec = doc && doc.dependencies && doc.dependencies[PKG]
65
+ if (typeof spec === 'string' && spec) {
66
+ return { name, dir: path.join(dir, name), spec }
67
+ }
68
+ }
69
+ } catch {
70
+ /* ignore */
71
+ }
72
+ return null
73
+ }
74
+
75
+ function installMode(spec) {
76
+ if (!spec) return 'unknown'
77
+ return spec.startsWith('link:') || spec.startsWith('file:') ? 'link' : 'registry'
78
+ }
79
+
80
+ /** 云端用哪家网盘:现在只接了百度,靠凭证文件认;以后加别家在这里扩展 */
81
+ function cloudProvider(cfg) {
82
+ if (!cfg) return { id: '', name: '' }
83
+ if (cfg.baiduAppFile) return { id: 'baidu', name: '百度网盘' }
84
+ if (cfg.cloudRoot) return { id: 'custom', name: '自定义云端' }
85
+ return { id: '', name: '' }
86
+ }
87
+
88
+ /** 语义化版本比较:只比 主.次.补(忽略预发布尾巴),a>b 返回正数 */
89
+ function compareVersion(a, b) {
90
+ const num = (v) => String(v || '').split('-')[0].split('.').map((x) => parseInt(x, 10) || 0)
91
+ const x = num(a)
92
+ const y = num(b)
93
+ for (let i = 0; i < 3; i++) {
94
+ const d = (x[i] || 0) - (y[i] || 0)
95
+ if (d !== 0) return d
96
+ }
97
+ return 0
98
+ }
99
+
100
+ async function latestVersion() {
101
+ let last = ''
102
+ for (const registry of NPM_REGISTRIES) {
103
+ const ctrl = new AbortController()
104
+ const timer = setTimeout(() => ctrl.abort(), 8000)
105
+ try {
106
+ const res = await fetch(`${registry}/${PKG}/latest`, {
107
+ signal: ctrl.signal,
108
+ headers: { accept: 'application/json' },
109
+ })
110
+ if (!res.ok) {
111
+ last = `HTTP ${res.status}`
112
+ continue
113
+ }
114
+ const doc = await res.json()
115
+ if (doc && typeof doc.version === 'string') return doc.version
116
+ last = 'no version field'
117
+ } catch (error) {
118
+ last = String((error && error.message) || error)
119
+ } finally {
120
+ clearTimeout(timer)
121
+ }
122
+ }
123
+ throw new Error(last || 'unknown')
124
+ }
125
+
126
+ function run(cmd, args, cwd) {
127
+ return new Promise((resolve) => {
128
+ let out = ''
129
+ let child
130
+ try {
131
+ child = spawn(cmd, args, {
132
+ cwd,
133
+ shell: process.platform === 'win32',
134
+ windowsHide: true,
135
+ })
136
+ } catch (error) {
137
+ resolve({ code: -1, out: String((error && error.message) || error) })
138
+ return
139
+ }
140
+ const push = (chunk) => {
141
+ out += String(chunk)
142
+ if (out.length > 20000) out = out.slice(-20000)
143
+ }
144
+ child.stdout && child.stdout.on('data', push)
145
+ child.stderr && child.stderr.on('data', push)
146
+ child.on('error', (error) => resolve({ code: -1, out: out + String((error && error.message) || error) }))
147
+ child.on('close', (code) => resolve({ code, out }))
148
+ })
149
+ }
150
+
151
+ function statusOf(term) {
152
+ const cfg = term.cfg || {}
153
+ const info = profileInfo(cfg.busDir)
154
+ const mode = installMode(info && info.spec)
155
+ const chains = []
156
+ try {
157
+ for (const link of term.links.values()) if (!chains.includes(link.code)) chains.push(link.code)
158
+ } catch {
159
+ /* ignore */
160
+ }
161
+ const cloud = term.cloud
162
+ return {
163
+ ok: true,
164
+ version: VERSION,
165
+ node: cfg.name || '',
166
+ nodeId: term.nodeId || '',
167
+ hostname: os.hostname(),
168
+ platform: process.platform,
169
+ workspace: cfg.workspace || '',
170
+ install: { profile: (info && info.name) || '', spec: (info && info.spec) || '', mode },
171
+ cloud: {
172
+ enabled: cfg.cloudEnabled === true,
173
+ running: Boolean(cloud && cloud.running),
174
+ root: cfg.cloudRoot || '',
175
+ intervalMs: cfg.cloudIntervalMs || 0,
176
+ provider: cloudProvider(cfg),
177
+ },
178
+ chains,
179
+ }
180
+ }
181
+
182
+ /**
183
+ * 只注册一次;行被关闭时随 fiber 一起注销,重新启用会再注册。
184
+ */
185
+ export function registerWeb(ctx, term, log) {
186
+ if (installed) return
187
+ installed = true
188
+ try {
189
+ ctx.inject(['webServer'], (webCtx) => {
190
+ webCtx.effect(() => {
191
+ const disposers = []
192
+ disposers.push(
193
+ webCtx.webServer.register({
194
+ kind: 'exact',
195
+ path: '/crossterm/api/status',
196
+ handler: (req, res) => json(res, 200, statusOf(term)),
197
+ }),
198
+ )
199
+ disposers.push(
200
+ webCtx.webServer.register({
201
+ kind: 'exact',
202
+ path: '/crossterm/api/check',
203
+ handler: async (req, res) => {
204
+ try {
205
+ const latest = await latestVersion()
206
+ // 只有 npm 上的版本「更新」才算有更新:本地源码版常领先于已发布版,
207
+ // 用 !== 会把 0.3.32 说成「发现新版本 0.3.31」。
208
+ const updateAvailable = compareVersion(latest, VERSION) > 0
209
+ const ahead = compareVersion(VERSION, latest) > 0
210
+ json(res, 200, { ok: true, current: VERSION, latest, updateAvailable, ahead })
211
+ } catch (error) {
212
+ json(res, 200, { ok: false, message: String((error && error.message) || error) })
213
+ }
214
+ },
215
+ }),
216
+ )
217
+ disposers.push(
218
+ webCtx.webServer.register({
219
+ kind: 'exact',
220
+ path: '/crossterm/api/cloud/start',
221
+ handler: (req, res) => {
222
+ try {
223
+ term.enableCloud()
224
+ json(res, 200, { ok: true, message: '云同步已开启', cloud: statusOf(term).cloud })
225
+ } catch (error) {
226
+ json(res, 200, { ok: false, message: `开启失败:${String((error && error.message) || error)}` })
227
+ }
228
+ },
229
+ }),
230
+ )
231
+ disposers.push(
232
+ webCtx.webServer.register({
233
+ kind: 'exact',
234
+ path: '/crossterm/api/cloud/stop',
235
+ handler: (req, res) => {
236
+ try {
237
+ term.disableCloud()
238
+ json(res, 200, { ok: true, message: '云同步已关闭(本进程内生效,重启后按配置恢复)', cloud: statusOf(term).cloud })
239
+ } catch (error) {
240
+ json(res, 200, { ok: false, message: `关闭失败:${String((error && error.message) || error)}` })
241
+ }
242
+ },
243
+ }),
244
+ )
245
+ disposers.push(
246
+ webCtx.webServer.register({
247
+ kind: 'exact',
248
+ path: '/crossterm/api/update',
249
+ handler: async (req, res) => {
250
+ const info = profileInfo(term.cfg && term.cfg.busDir)
251
+ if (!info) {
252
+ json(res, 200, { ok: false, message: '找不到安装本插件的 profile' })
253
+ return
254
+ }
255
+ if (installMode(info.spec) === 'link') {
256
+ json(res, 200, {
257
+ ok: false,
258
+ mode: 'link',
259
+ message: '本机是源码(link:)安装,代码即工作区里的源码;更新请直接改源码后重启 DSH',
260
+ })
261
+ return
262
+ }
263
+ const result = await run('dsh', ['plugin', '--profile', info.name, 'add', `${PKG}@latest`], info.dir)
264
+ json(res, 200, {
265
+ ok: result.code === 0,
266
+ mode: 'registry',
267
+ needRestart: result.code === 0,
268
+ message:
269
+ result.code === 0
270
+ ? '已更新,重启 DSH 后生效'
271
+ : (result.out || `dsh 退出码 ${result.code}`).slice(-1200),
272
+ })
273
+ },
274
+ }),
275
+ )
276
+ return () => {
277
+ installed = false
278
+ for (const dispose of disposers) {
279
+ try {
280
+ dispose()
281
+ } catch {
282
+ /* ignore */
283
+ }
284
+ }
285
+ }
286
+ }, 'dsh-crossterm: 插件页卡片路由')
287
+ })
288
+ } catch (error) {
289
+ installed = false
290
+ if (typeof log === 'function') log(`web 路由注册失败:${(error && error.message) || error}`)
291
+ }
292
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Cross-session",
4
+ "description": "Link the sessions inside this terminal: chain codes, inline mail, inbox"
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "跨会话",
4
+ "description": "同一终端里的多个会话之间互通:特征码链、内联邮件、收件箱"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "dsh-crossterm",
3
- "version": "0.3.31",
3
+ "version": "0.3.33",
4
4
  "description": "DSH 跨终端互联:同一账号下的多个 DSH 终端/进程互发邮件与文件(主题内联、正文落盘,收件方自己读)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "exports": {
8
8
  ".": "./lib/index.js",
9
+ "./client": "./client/client.js",
9
10
  "./package.json": "./package.json",
11
+ "./locale/en.json": "./locale/en.json",
12
+ "./locale/zh.json": "./locale/zh.json",
10
13
  "./parts/session": "./lib/index.js",
11
14
  "./parts/terminal": "./lib/index.js",
12
15
  "./parts/device": "./lib/index.js",
@@ -18,6 +21,8 @@
18
21
  },
19
22
  "files": [
20
23
  "lib",
24
+ "client",
25
+ "locale",
21
26
  "parts",
22
27
  "cordis.patch.yml",
23
28
  "README.md"
@@ -42,6 +47,9 @@
42
47
  "dsh": {
43
48
  "bundle": {
44
49
  "patch": "./cordis.patch.yml"
50
+ },
51
+ "client": {
52
+ "platform": "web"
45
53
  }
46
54
  },
47
55
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "跨账号",
3
- "description": "预留位:后续用于跨百度账号互通,当前不挂工具"
3
+ "description": "预留位:后续用于跨账号互通,当前不挂工具"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "跨设备",
3
- "description": "经百度网盘云总线,在多台机器的 DSH 之间互发邮件与文件"
3
+ "description": "经云端总线,在多台机器的 DSH 之间互发邮件与文件"
4
4
  }