dsh-plugin-versions 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +51 -2
- package/client.js +331 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +5 -0
- package/index.js +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +55 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ACGNnsj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 插件版本一览 · dsh-plugin-versions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
给 DeepSeek Harness 的「插件」页加一个标签页:列出所有已安装组合包的版本,标出哪些能更新,点一行直接跳到官方组合包详情页的「版本」区块完成更新。
|
|
4
|
+
|
|
5
|
+
## 它解决什么
|
|
6
|
+
|
|
7
|
+
DSH 官方的「版本」区块和更新按钮只出现在 **从 registry 安装、且被 Host 标为 `updatable`** 的第三方组合包详情页里。想确认「我这堆插件哪个该更新」,得逐个点进去看。这个插件把这件事摊平成一屏。
|
|
8
|
+
|
|
9
|
+
## 功能
|
|
10
|
+
|
|
11
|
+
- 插件页新增第 4 个标签页「版本一览」(`plugins.tab`,`order: 40`)
|
|
12
|
+
- 先渲染本地版本,再逐行后台查 registry;只查 `updatable && !official` 的行,串行执行,避免同时打出一堆 pnpm 子进程
|
|
13
|
+
- 状态分档:`可更新到 x` / `已是最新` / `随 DSH 更新` / `无官方版本区块` / 检查中… / 检查失败(附错误原文前 300 字)
|
|
14
|
+
- 可更新的行排前面,其余按包名排序
|
|
15
|
+
- 点一行 → `openPackage(name)` 打开官方组合包详情页,并自动滚到底,直接落在「版本」区块
|
|
16
|
+
- 详情页底部追加「↑ 回到顶部」(`plugins.detail.section`,`order: 999`)
|
|
17
|
+
|
|
18
|
+
更新动作本身完全走官方界面,本插件不实现任何更新流程。
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
仓库即 bundle。二选一:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 已在本机:用 DSH 的插件管理器装这个目录
|
|
26
|
+
plugin_manager install_bundle target=<绝对路径>/dsh-plugin-versions
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
或作为 npm 包安装到 profile(需先发到 registry)。注册靠 `cordis.patch.yml` 的 `- insert:` 一行,插件不会改写 profile 的任何文件。
|
|
30
|
+
|
|
31
|
+
## 结构
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
package.json 清单:dsh.bundle.patch / dsh.client / exports ./client
|
|
35
|
+
cordis.patch.yml 注册一行:- insert: [{ id, name }]
|
|
36
|
+
index.js Host half(空实现,只为满足 bundle 契约)
|
|
37
|
+
client.js 浏览器半:纯 JS,window.__ModuleLoader__.load 懒加载工厂
|
|
38
|
+
locale/{zh,en}.json 显示元数据 meta.title / meta.description
|
|
39
|
+
icon.svg 列表图标
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 开发
|
|
43
|
+
|
|
44
|
+
客户端没有构建步骤:`client.js` 直接是产物,改完在「插件」页点刷新即可生效(`Ctrl+R` 在桌面端不触发重载)。渲染逻辑可以用 `vm` + 假 React 离线自证。
|
|
45
|
+
|
|
46
|
+
## 数据来源与权限
|
|
47
|
+
|
|
48
|
+
只读调用 Host 的 `pluginManager.listBundles()` 与 `pluginManager.listVersions(name, {})`。前者不触网;后者每个包最多打 2 个 registry(含 fallback 镜像),单包最坏约 140 秒,因此必须串行且只查可更新的行。
|
|
49
|
+
|
|
50
|
+
## 许可
|
|
51
|
+
|
|
52
|
+
MIT
|
package/client.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-plugin-versions",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
const ReactMod = require("react");
|
|
5
|
+
const React = (ReactMod && ReactMod.createElement) ? ReactMod : ((ReactMod && ReactMod.default && ReactMod.default.createElement) ? ReactMod.default : ReactMod);
|
|
6
|
+
const h = React.createElement;
|
|
7
|
+
|
|
8
|
+
const S = {
|
|
9
|
+
root: { display: "flex", flexDirection: "column", gap: "10px", padding: "4px 2px", fontSize: "13px", lineHeight: 1.5, color: "inherit" },
|
|
10
|
+
bar: { display: "flex", alignItems: "center", gap: "8px" },
|
|
11
|
+
muted: { opacity: 0.62 },
|
|
12
|
+
table: { display: "flex", flexDirection: "column", border: "1px solid rgba(128,128,128,0.22)", borderRadius: "8px", overflow: "hidden" },
|
|
13
|
+
row: { display: "grid", gridTemplateColumns: "minmax(180px,1.6fr) 110px 120px minmax(110px,1fr)", gap: "8px", padding: "9px 12px", borderTop: "1px solid rgba(128,128,128,0.16)", alignItems: "center" },
|
|
14
|
+
head: { fontWeight: 600, background: "rgba(128,128,128,0.07)", borderTop: "none" },
|
|
15
|
+
name: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
16
|
+
badge: { display: "inline-block", padding: "1px 7px", borderRadius: "999px", fontSize: "12px", border: "1px solid currentColor" },
|
|
17
|
+
upd: { color: "#2f9e63" },
|
|
18
|
+
btn: { font: "inherit", color: "inherit", background: "transparent", border: "1px solid rgba(128,128,128,0.35)", borderRadius: "6px", padding: "2px 9px", cursor: "pointer" },
|
|
19
|
+
err: { color: "#d9534f" },
|
|
20
|
+
click: { cursor: "pointer" }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function describe(b) {
|
|
24
|
+
if (b.updatable) return "可更新";
|
|
25
|
+
if (!b.installed) return "未安装";
|
|
26
|
+
if (b.official) return "跟随 DSH";
|
|
27
|
+
if (b.optional) return "按需安装";
|
|
28
|
+
return "来源不支持更新";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function latestText(slot) {
|
|
32
|
+
if (!slot) return "—";
|
|
33
|
+
if (slot.status === "loading") return "检查中…";
|
|
34
|
+
if (slot.status === "error") return "检查失败";
|
|
35
|
+
const list = slot.list;
|
|
36
|
+
if (!list) return "—";
|
|
37
|
+
if (list.status === "refused") return list.problem === "not-updatable" ? "—" : "无法检查";
|
|
38
|
+
const first = list.versions && list.versions[0];
|
|
39
|
+
return first ? first.version : "已是最新";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class TabBoundary extends React.Component {
|
|
43
|
+
constructor(props) {
|
|
44
|
+
super(props);
|
|
45
|
+
this.state = { err: null };
|
|
46
|
+
}
|
|
47
|
+
static getDerivedStateFromError(err) {
|
|
48
|
+
return { err: err };
|
|
49
|
+
}
|
|
50
|
+
render() {
|
|
51
|
+
if (this.state.err) {
|
|
52
|
+
return h("div", { style: { color: "#d9534f", whiteSpace: "pre-wrap", fontSize: "12px" } }, "渲染失败:" + String((this.state.err && this.state.err.stack) || this.state.err));
|
|
53
|
+
}
|
|
54
|
+
return this.props.children;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function scrollPluginPageToBottom() {
|
|
59
|
+
const scrollables = () => {
|
|
60
|
+
const out = [];
|
|
61
|
+
const all = document.querySelectorAll("div, main, section, article");
|
|
62
|
+
for (let i = 0; i < all.length; i += 1) {
|
|
63
|
+
const el = all[i];
|
|
64
|
+
if (el.scrollHeight - el.clientHeight <= 60) continue;
|
|
65
|
+
const cs = window.getComputedStyle(el);
|
|
66
|
+
if (!/(auto|scroll)/.test(cs.overflowY)) continue;
|
|
67
|
+
const r = el.getBoundingClientRect();
|
|
68
|
+
if (r.width < 400 || r.left < 180) continue;
|
|
69
|
+
out.push(el);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
};
|
|
73
|
+
const attempt = () => {
|
|
74
|
+
try {
|
|
75
|
+
const els = scrollables();
|
|
76
|
+
els.sort((a, b) => (b.clientHeight * b.clientWidth) - (a.clientHeight * a.clientWidth));
|
|
77
|
+
if (els.length > 0) els[0].scrollTop = els[0].scrollHeight;
|
|
78
|
+
const doc = document.scrollingElement || document.documentElement;
|
|
79
|
+
if (doc) doc.scrollTop = doc.scrollHeight;
|
|
80
|
+
} catch (e) { /* 忽略:滚动只是锦上添花 */ }
|
|
81
|
+
};
|
|
82
|
+
setTimeout(attempt, 120);
|
|
83
|
+
setTimeout(attempt, 400);
|
|
84
|
+
setTimeout(attempt, 900);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function scrollPluginPageToTop() {
|
|
88
|
+
const attempt = () => {
|
|
89
|
+
try {
|
|
90
|
+
const all = document.querySelectorAll("div, main, section, article");
|
|
91
|
+
const out = [];
|
|
92
|
+
for (let i = 0; i < all.length; i += 1) {
|
|
93
|
+
const el = all[i];
|
|
94
|
+
if (el.scrollHeight - el.clientHeight <= 60) continue;
|
|
95
|
+
const cs = window.getComputedStyle(el);
|
|
96
|
+
if (!/(auto|scroll)/.test(cs.overflowY)) continue;
|
|
97
|
+
const r = el.getBoundingClientRect();
|
|
98
|
+
if (r.width < 400 || r.left < 180) continue;
|
|
99
|
+
out.push(el);
|
|
100
|
+
}
|
|
101
|
+
out.sort((a, b) => (b.clientHeight * b.clientWidth) - (a.clientHeight * a.clientWidth));
|
|
102
|
+
if (out.length > 0) out[0].scrollTop = 0;
|
|
103
|
+
const doc = document.scrollingElement || document.documentElement;
|
|
104
|
+
if (doc) doc.scrollTop = 0;
|
|
105
|
+
} catch (e) { /* 忽略:滚动失败不影响页面 */ }
|
|
106
|
+
};
|
|
107
|
+
attempt();
|
|
108
|
+
setTimeout(attempt, 80);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function BackToTopSection(props) {
|
|
112
|
+
const subject = props && props.subject;
|
|
113
|
+
if (subject && subject.kind && subject.kind !== "bundle") return null;
|
|
114
|
+
return h("div", { style: { display: "flex", justifyContent: "flex-end", padding: "10px 0 6px" } },
|
|
115
|
+
h("button", {
|
|
116
|
+
type: "button",
|
|
117
|
+
style: {
|
|
118
|
+
background: "transparent",
|
|
119
|
+
border: "1px solid currentColor",
|
|
120
|
+
borderRadius: "6px",
|
|
121
|
+
color: "inherit",
|
|
122
|
+
cursor: "pointer",
|
|
123
|
+
fontSize: "12px",
|
|
124
|
+
opacity: 0.72,
|
|
125
|
+
padding: "4px 10px"
|
|
126
|
+
},
|
|
127
|
+
onClick: () => scrollPluginPageToTop()
|
|
128
|
+
}, "↑ 回到顶部")
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function VersionsTab(props) {
|
|
133
|
+
const ctx = props.ctx;
|
|
134
|
+
const openPackage = props.openPackage;
|
|
135
|
+
const [state, setState] = React.useState({ status: "loading", bundles: [], error: null });
|
|
136
|
+
const [rows, setRows] = React.useState({});
|
|
137
|
+
const [nonce, setNonce] = React.useState(0);
|
|
138
|
+
const [pmState, setPmState] = React.useState(null);
|
|
139
|
+
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
let cancelled = false;
|
|
142
|
+
let timer = null;
|
|
143
|
+
let tries = 0;
|
|
144
|
+
|
|
145
|
+
const describe = (err) => {
|
|
146
|
+
let r = null;
|
|
147
|
+
try { r = ctx && ctx.remote; } catch (e) { return "取 ctx.remote 抛错:" + String(e && e.message || e); }
|
|
148
|
+
if (!r) return "ctx.remote 为空" + (err ? ";" + String(err && err.message || err) : "");
|
|
149
|
+
let keys = [];
|
|
150
|
+
try { keys = Object.keys(r); } catch (e) { keys = ["<Object.keys 抛错:" + String(e && e.message || e) + ">"]; }
|
|
151
|
+
return "ctx.remote 的命名空间: [" + keys.join(", ") + "]" + (err ? ";取 pluginManager 抛错: " + String(err && err.message || err) : "");
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const load = (pm) => {
|
|
155
|
+
setPmState(pm);
|
|
156
|
+
setState({ status: "loading", bundles: [], error: null });
|
|
157
|
+
setRows({});
|
|
158
|
+
Promise.resolve().then(() => pm.listBundles()).then((res) => {
|
|
159
|
+
if (cancelled) return;
|
|
160
|
+
if (res && res.ok === false) {
|
|
161
|
+
const e = res.error || {};
|
|
162
|
+
setState({ status: "error", bundles: [], error: String(e.message || e.code || "listBundles 失败") });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const value = res && res.ok === true ? res.value : res;
|
|
166
|
+
setState({ status: "ready", bundles: Array.isArray(value) ? value : [], error: null });
|
|
167
|
+
}).catch((err) => {
|
|
168
|
+
if (cancelled) return;
|
|
169
|
+
setState({ status: "error", bundles: [], error: String((err && err.message) || err) });
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const attempt = () => {
|
|
174
|
+
if (cancelled) return;
|
|
175
|
+
let pm = null;
|
|
176
|
+
let err = null;
|
|
177
|
+
try { pm = ctx && ctx.remote && ctx.remote.pluginManager; } catch (e) { err = e; }
|
|
178
|
+
if (pm) { load(pm); return; }
|
|
179
|
+
tries += 1;
|
|
180
|
+
if (tries < 12) { timer = setTimeout(attempt, 250); return; }
|
|
181
|
+
const why = describe(err);
|
|
182
|
+
setState({ status: "error", bundles: [], error: "拿不到 pluginManager。" + why });
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
setState({ status: "loading", bundles: [], error: null });
|
|
186
|
+
attempt();
|
|
187
|
+
return () => { cancelled = true; if (timer) clearTimeout(timer); };
|
|
188
|
+
}, [nonce]);
|
|
189
|
+
|
|
190
|
+
React.useEffect(() => {
|
|
191
|
+
if (state.status !== "ready" || !pmState) return undefined;
|
|
192
|
+
let cancelled = false;
|
|
193
|
+
const queue = state.bundles.filter((b) => b && b.installed && b.name && b.updatable && !b.official);
|
|
194
|
+
(async () => {
|
|
195
|
+
for (const b of queue) {
|
|
196
|
+
if (cancelled) return;
|
|
197
|
+
setRows((prev) => Object.assign({}, prev, { [b.name]: { status: "loading" } }));
|
|
198
|
+
let next;
|
|
199
|
+
try {
|
|
200
|
+
const res = await pmState.listVersions(b.name, {});
|
|
201
|
+
if (res && res.ok === false) {
|
|
202
|
+
const e = res.error || {};
|
|
203
|
+
next = { status: "error", error: String(e.message || e.code || "listVersions 失败") };
|
|
204
|
+
} else {
|
|
205
|
+
next = { status: "ready", list: (res && res.ok === true ? res.value : res) || null };
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
next = { status: "error", error: String((err && err.message) || err) };
|
|
209
|
+
}
|
|
210
|
+
if (cancelled) return;
|
|
211
|
+
setRows((prev) => Object.assign({}, prev, { [b.name]: next }));
|
|
212
|
+
}
|
|
213
|
+
})();
|
|
214
|
+
return () => { cancelled = true; };
|
|
215
|
+
}, [state.status, state.bundles, pmState]);
|
|
216
|
+
|
|
217
|
+
const installed = state.bundles.filter((b) => b && b.installed === true);
|
|
218
|
+
const checked = installed.filter((b) => rows[b.name] && rows[b.name].status === "ready" && rows[b.name].list && rows[b.name].list.status === "listed");
|
|
219
|
+
const newer = checked.filter((b) => (rows[b.name].list.versions || []).length > 0).length;
|
|
220
|
+
const failed = installed.filter((b) => rows[b.name] && rows[b.name].status === "error").length;
|
|
221
|
+
const pending = installed.filter((b) => b.updatable === true && b.official !== true && !rows[b.name]).length;
|
|
222
|
+
const header = h("div", { style: S.bar },
|
|
223
|
+
h("span", { style: { opacity: 0.45, fontSize: "11px" } }, "版本一览 v10"),
|
|
224
|
+
h("span", { style: S.muted },
|
|
225
|
+
state.status === "loading" ? "正在读取已安装组合包…" :
|
|
226
|
+
state.status === "error" ? "读取失败" :
|
|
227
|
+
((newer > 0 ? newer + " 个可更新" : (pending > 0 ? "正在检查…" : "全部已是最新")) + (failed > 0 ? " · " + failed + " 个检查失败" : "") + " · 共 " + installed.length + " 个已安装")),
|
|
228
|
+
h("span", { style: { flex: "1 1 auto" } }),
|
|
229
|
+
h("button", { style: S.btn, onClick: () => setNonce(nonce + 1), disabled: state.status === "loading" }, "刷新")
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (state.status === "error") {
|
|
233
|
+
return h("div", { style: S.root }, header, h("div", { style: S.err }, state.error));
|
|
234
|
+
}
|
|
235
|
+
if (state.status === "loading") {
|
|
236
|
+
return h("div", { style: S.root }, header, h("div", { style: S.muted }, "加载中…"));
|
|
237
|
+
}
|
|
238
|
+
if (state.bundles.length === 0) {
|
|
239
|
+
return h("div", { style: S.root }, header, h("div", { style: S.muted }, "没有已安装的组合包。"));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const head = h("div", { style: Object.assign({}, S.row, S.head) },
|
|
243
|
+
h("span", null, "组合包"), h("span", null, "当前版本"), h("span", null, "最新版本"), h("span", null, "状态"));
|
|
244
|
+
|
|
245
|
+
const ordered = installed.slice().sort((a, b) => {
|
|
246
|
+
const ua = rows[a.name] && rows[a.name].list && (rows[a.name].list.versions || []).length > 0;
|
|
247
|
+
const ub = rows[b.name] && rows[b.name].list && (rows[b.name].list.versions || []).length > 0;
|
|
248
|
+
if (ua === ub) return String(a.name).localeCompare(String(b.name));
|
|
249
|
+
return ua ? -1 : 1;
|
|
250
|
+
});
|
|
251
|
+
const list = ordered.map((b) => {
|
|
252
|
+
const slot = rows[b.name] || null;
|
|
253
|
+
const canCheck = b.updatable === true && b.official !== true;
|
|
254
|
+
const canOpen = typeof openPackage === "function" && canCheck;
|
|
255
|
+
const listed = slot && slot.status === "ready" && slot.list && slot.list.status === "listed" ? slot.list : null;
|
|
256
|
+
const newest = listed && Array.isArray(listed.versions) && listed.versions.length > 0 ? listed.versions[0] : null;
|
|
257
|
+
let latestCell = "—";
|
|
258
|
+
let latestTitle = "";
|
|
259
|
+
let statusText = "";
|
|
260
|
+
let tone = null;
|
|
261
|
+
if (b.official === true) {
|
|
262
|
+
statusText = "随 DSH 更新";
|
|
263
|
+
} else if (b.updatable !== true) {
|
|
264
|
+
statusText = "无官方版本区块";
|
|
265
|
+
latestTitle = "这个包不是从 registry 安装的,官方不给版本区块";
|
|
266
|
+
} else if (!slot) {
|
|
267
|
+
statusText = "检查中…";
|
|
268
|
+
latestCell = "…";
|
|
269
|
+
} else if (slot.status === "error") {
|
|
270
|
+
statusText = "检查失败";
|
|
271
|
+
latestCell = String(slot.error || "未知错误").slice(0, 34);
|
|
272
|
+
latestTitle = String(slot.error || "未知错误");
|
|
273
|
+
tone = S.upd;
|
|
274
|
+
} else if (!listed) {
|
|
275
|
+
const pr = slot.list && slot.list.problem;
|
|
276
|
+
statusText = pr === "network" ? "网络不可达" : pr === "not-found" ? "注册表未找到" : pr === "not-updatable" ? "无可查版本" : "无法查询";
|
|
277
|
+
latestTitle = (slot.list && slot.list.reason) || "";
|
|
278
|
+
} else if (newest) {
|
|
279
|
+
statusText = "可更新到 " + newest.version;
|
|
280
|
+
latestCell = newest.version;
|
|
281
|
+
tone = S.upd;
|
|
282
|
+
} else {
|
|
283
|
+
statusText = "已是最新";
|
|
284
|
+
}
|
|
285
|
+
const badge = Object.assign({}, S.badge, tone || {});
|
|
286
|
+
return h("div", {
|
|
287
|
+
key: b.name,
|
|
288
|
+
style: Object.assign({}, S.row, canOpen ? S.click : {}),
|
|
289
|
+
title: (b.description || b.name) + (latestTitle ? " · " + latestTitle : ""),
|
|
290
|
+
onClick: canOpen ? () => { openPackage(b.name); scrollPluginPageToBottom(); } : undefined
|
|
291
|
+
},
|
|
292
|
+
h("span", { style: S.name }, b.name),
|
|
293
|
+
h("span", null, b.version || "—"),
|
|
294
|
+
h("span", { title: latestTitle || undefined }, latestCell),
|
|
295
|
+
h("span", null, h("span", { style: badge }, statusText))
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const firstErr = installed.map((b) => rows[b.name]).filter((r) => r && r.status === "error")[0];
|
|
300
|
+
return h("div", { style: S.root },
|
|
301
|
+
header,
|
|
302
|
+
h("div", { style: S.table }, head, list),
|
|
303
|
+
h("div", { style: S.muted }, "点一行打开官方组合包页面;更新在官方的「版本」区块里完成。"),
|
|
304
|
+
firstErr ? h("div", { style: Object.assign({}, S.muted, { color: "#e06c75", whiteSpace: "pre-wrap" }) }, "首个错误原文:" + String(firstErr.error || "").slice(0, 300)) : null
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
inject: ["slots", "remote", "remote.pluginManager"],
|
|
310
|
+
apply(ctx) {
|
|
311
|
+
ctx.slots.inject("plugins.tab", () => {
|
|
312
|
+
const off = ctx.slots.register({
|
|
313
|
+
name: "plugins.tab",
|
|
314
|
+
id: "dsh-plugin-versions",
|
|
315
|
+
order: 40,
|
|
316
|
+
label: () => "版本一览"
|
|
317
|
+
}, (ownerProps) => h(TabBoundary, null, h(VersionsTab, Object.assign({ ctx }, ownerProps || {}))));
|
|
318
|
+
return typeof off === "function" ? off : () => {};
|
|
319
|
+
});
|
|
320
|
+
ctx.slots.inject("plugins.detail.section", () => {
|
|
321
|
+
const off = ctx.slots.register({
|
|
322
|
+
name: "plugins.detail.section",
|
|
323
|
+
id: "dsh-plugin-versions-top",
|
|
324
|
+
order: 999
|
|
325
|
+
}, (ownerProps) => h(BackToTopSection, ownerProps || {}));
|
|
326
|
+
return typeof off === "function" ? off : () => {};
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
});
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
2
|
+
<rect x="8" y="10" width="48" height="44" rx="8" stroke="currentColor" stroke-width="3"/>
|
|
3
|
+
<path d="M18 24h18M18 32h12" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
|
4
|
+
<path d="M36 42l7-7 7 7" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function apply() {}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-versions",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "List installed DeepSeek Harness bundles with versions and update status, and jump straight to the official update page.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "ACGNnsj",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/ACGNnsj/dsh-plugin-versions.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/ACGNnsj/dsh-plugin-versions#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/ACGNnsj/dsh-plugin-versions/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"deepseek",
|
|
20
|
+
"harness",
|
|
21
|
+
"dsh",
|
|
22
|
+
"dsh-plugin",
|
|
23
|
+
"plugin-manager",
|
|
24
|
+
"versions",
|
|
25
|
+
"update"
|
|
26
|
+
],
|
|
27
|
+
"dsh": {
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"platform": "web",
|
|
33
|
+
"inject": []
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
".": "./index.js",
|
|
38
|
+
"./client": "./client.js",
|
|
39
|
+
"./locale/*.json": "./locale/*.json",
|
|
40
|
+
"./icon": "./icon.svg",
|
|
41
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"index.js",
|
|
47
|
+
"client.js",
|
|
48
|
+
"cordis.patch.yml",
|
|
49
|
+
"locale",
|
|
50
|
+
"icon.svg",
|
|
51
|
+
"README.md"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"registry": "https://registry.npmjs.org/"
|
|
56
|
+
}
|
|
57
|
+
}
|