dsh-plugin-marketplace 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.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/client.js +264 -0
  4. package/index.js +11 -0
  5. package/package.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Scorp1o117
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 ADDED
@@ -0,0 +1,49 @@
1
+ # dsh-plugin-marketplace
2
+
3
+ A **plugin marketplace** inside the DeepSeek Harness Web UI: browse
4
+ [github.com/topics/dsh-plugin](https://github.com/topics/dsh-plugin) right
5
+ from the settings page — no terminal needed.
6
+
7
+ - **Search** the topic by keyword, **sort** by stars or last update
8
+ - **Plugin cards** with description, stars, language, and update date
9
+ - **Detail panel**: GitHub README summary, install command, repo/npm links
10
+ - Powered by the public GitHub search API (CORS-enabled, no key needed;
11
+ unauthenticated rate limit 60 req/h)
12
+ - Zero client dependencies (React only), no build step — hand-written
13
+ ModuleLoader bundle
14
+
15
+ ## Install
16
+
17
+ In `$DSH_HOME/profiles/web/cordis.patch.yml`:
18
+
19
+ ```yaml
20
+ - insert:
21
+ - id: plugin-marketplace
22
+ name: 'dsh-plugin-marketplace'
23
+ ```
24
+
25
+ Then restart `dsh web` (new client plugins require a process restart to be
26
+ scanned into the browser roster) and open **Settings → Plugin Marketplace**.
27
+
28
+ ## How it works
29
+
30
+ | Layer | File | Role |
31
+ |---|---|---|
32
+ | host shell | `index.js` | empty apply — makes the package a loader entry |
33
+ | browser half | `client.js` | registers the `settings.section` "marketplace" tab; fetches GitHub search API; renders cards + detail |
34
+ | manifest | `package.json` | `dsh.client: { platform: "web" }` + `exports["./client"]` — discovered by `dsh-client-modules` |
35
+
36
+ The browser half needs no `dsh.client.inject` packages: it only uses `react`
37
+ (provided by the web runtime) and the `slots` / `locale` client services.
38
+
39
+ ## Notes
40
+
41
+ - The GitHub search API returns at most 1000 results; the topic currently has
42
+ 280+ repos, so paging covers everything.
43
+ - READMEs are fetched per plugin on demand and truncated to ~1200 chars.
44
+ - If you see "rate-limited", wait an hour or run the web through a proxy that
45
+ adds a GitHub token.
46
+
47
+ ## License
48
+
49
+ MIT
package/client.js ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * dsh-plugin-marketplace — browser half.
3
+ *
4
+ * A marketplace section inside the Web UI settings page: browses
5
+ * github.com/topics/dsh-plugin through the public GitHub search API
6
+ * (CORS-enabled), with keyword search, star/update sorting, paging, and a
7
+ * per-plugin detail panel (README summary + install instructions).
8
+ *
9
+ * Hand-written ModuleLoader bundle — no build step required.
10
+ */
11
+ window.__ModuleLoader__.load({
12
+ id: "dsh-plugin-marketplace",
13
+ factory: (require) => {
14
+ var module = { exports: {} };
15
+ var exports = module.exports;
16
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
17
+ var react = require("react");
18
+ var h = react.createElement;
19
+
20
+ // ── CSS (theme tokens) ────────────────────────────────────────────────
21
+ var CSS = ".__mp_grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;padding:0;margin:0;list-style:none}" +
22
+ ".__mp_card{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:12px 14px;display:flex;flex-direction:column;gap:6px;background:var(--dsw-alias-bg-layer-2);cursor:pointer;text-align:left;font:inherit;color:inherit}" +
23
+ ".__mp_card:hover{border-color:var(--dsw-alias-brand-primary)}" +
24
+ ".__mp_cardHead{display:flex;align-items:center;gap:8px;min-width:0}" +
25
+ ".__mp_name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}" +
26
+ ".__mp_star{flex:none;font-size:12px;color:var(--dsw-alias-label-secondary)}" +
27
+ ".__mp_desc{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}" +
28
+ ".__mp_meta{display:flex;gap:10px;font-size:11px;color:var(--dsw-alias-label-tertiary)}" +
29
+ ".__mp_toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px}" +
30
+ ".__mp_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;min-width:200px;flex:1}" +
31
+ ".__mp_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 8px;font-size:13px}" +
32
+ ".__mp_more{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer;margin-top:12px}" +
33
+ ".__mp_more:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary)}" +
34
+ ".__mp_more:disabled{opacity:.5;cursor:default}" +
35
+ ".__mp_status{font-size:12px;color:var(--dsw-alias-label-tertiary);margin:8px 0 0}" +
36
+ ".__mp_detail{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:14px;margin-top:10px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:8px;width:100%;box-sizing:border-box}" +
37
+ ".__mp_detailTitle{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}" +
38
+ ".__mp_readme{font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;max-height:260px;overflow:auto;width:100%;box-sizing:border-box;min-width:0}" +
39
+ ".__mp_code{display:block;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:6px;padding:8px 10px;font-family:ui-monospace,Consolas,monospace;font-size:12px;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-all;width:100%;box-sizing:border-box;min-width:0}" +
40
+ ".__mp_link{color:var(--dsw-alias-brand-primary);font-size:12px;text-decoration:none}" +
41
+ ".__mp_error{color:var(--dsw-alias-label-error);font-size:12px;margin:8px 0 0}";
42
+ var tagId = "dsh-plugin-marketplace/main.css";
43
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
44
+ var tag = document.createElement("style");
45
+ tag.dataset.plugin = "dsh-plugin-marketplace";
46
+ tag.dataset.pluginCss = tagId;
47
+ tag.textContent = CSS;
48
+ document.head.appendChild(tag);
49
+ }
50
+
51
+ // ── locale ────────────────────────────────────────────────────────────
52
+ var NS = "marketplace";
53
+ var inject = ["slots", "locale"];
54
+ var zh = {
55
+ nav: "插件市场",
56
+ search: "搜索插件(关键词或留空浏览全部)…",
57
+ sortStars: "按 Star 排序",
58
+ sortUpdated: "按更新时间排序",
59
+ loadMore: "加载更多",
60
+ loading: "加载中…",
61
+ error: "加载失败,请稍后重试",
62
+ empty: "没有找到插件",
63
+ install: "安装方式",
64
+ installHint: "在 profile 目录执行 pnpm 安装,然后在 cordis.patch.yml 挂载:",
65
+ readme: "README 摘要",
66
+ readmeEmpty: "(该仓库没有 README)",
67
+ openRepo: "打开 GitHub 仓库 ↗",
68
+ openNpm: "在 npm 搜索同名包 ↗",
69
+ updated: "更新",
70
+ total: "共 {count} 个插件",
71
+ ghError: "GitHub API 请求失败(未认证限流 60 次/小时),请稍后再试"
72
+ };
73
+ var en = {
74
+ nav: "Plugin Marketplace",
75
+ search: "Search plugins (keyword, or empty to browse all)…",
76
+ sortStars: "Sort by stars",
77
+ sortUpdated: "Sort by updated",
78
+ loadMore: "Load more",
79
+ loading: "Loading…",
80
+ error: "Failed to load, try again later",
81
+ empty: "No plugins found",
82
+ install: "Install",
83
+ installHint: "Run pnpm in your profile dir, then mount in cordis.patch.yml:",
84
+ readme: "README summary",
85
+ readmeEmpty: "(no README in this repo)",
86
+ openRepo: "Open GitHub repo ↗",
87
+ openNpm: "Search npm ↗",
88
+ updated: "Updated",
89
+ total: "{count} plugins",
90
+ ghError: "GitHub API rate-limited (60/hr unauthenticated), try again later"
91
+ };
92
+
93
+ // ── GitHub API ────────────────────────────────────────────────────────
94
+ var BASE = "https://api.github.com/search/repositories";
95
+ var cache = new Map(); // queryKey -> {items, incomplete}
96
+ function queryKey(q, sort, page) { return q + "|" + sort + "|" + page; }
97
+ function fmtTime(iso) {
98
+ try { return new Date(iso).toISOString().slice(0, 10); } catch { return ""; }
99
+ }
100
+ async function fetchPage(q, sort, page) {
101
+ var key = queryKey(q, sort, page);
102
+ var hit = cache.get(key);
103
+ if (hit) return hit;
104
+ var params = new URLSearchParams({
105
+ q: "topic:dsh-plugin" + (q ? " " + q : ""),
106
+ sort: sort,
107
+ order: "desc",
108
+ per_page: "20",
109
+ page: String(page)
110
+ });
111
+ var res = await fetch(BASE + "?" + params.toString(), { headers: { Accept: "application/vnd.github+json" } });
112
+ if (!res.ok) throw new Error("HTTP " + res.status);
113
+ var data = await res.json();
114
+ var items = (data.items || []).map(function (r) {
115
+ return {
116
+ fullName: r.full_name,
117
+ desc: r.description || "",
118
+ stars: r.stargazers_count || 0,
119
+ updated: r.updated_at || "",
120
+ lang: r.language || "",
121
+ htmlUrl: r.html_url
122
+ };
123
+ });
124
+ var out = { items: items, total: data.total_count || 0 };
125
+ cache.set(key, out);
126
+ return out;
127
+ }
128
+ async function fetchReadme(fullName) {
129
+ var url = "https://api.github.com/repos/" + fullName + "/readme";
130
+ var res = await fetch(url, { headers: { Accept: "application/vnd.github+json" } });
131
+ if (!res.ok) throw new Error("no readme");
132
+ var data = await res.json();
133
+ var bin = atob(data.content.replace(/\s+/g, ""));
134
+ var bytes = new Uint8Array(bin.length);
135
+ for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
136
+ return new TextDecoder("utf-8").decode(bytes);
137
+ }
138
+
139
+ // ── components ────────────────────────────────────────────────────────
140
+ function PluginCard(props) {
141
+ return h("li", null,
142
+ h("button", { type: "button", className: "__mp_card", onClick: props.onOpen },
143
+ h("div", { className: "__mp_cardHead" },
144
+ h("span", { className: "__mp_name", title: props.plugin.fullName }, props.plugin.fullName),
145
+ h("span", { className: "__mp_star" }, "★ " + props.plugin.stars)
146
+ ),
147
+ props.plugin.desc ? h("div", { className: "__mp_desc" }, props.plugin.desc) : null,
148
+ h("div", { className: "__mp_meta" },
149
+ props.plugin.lang ? h("span", null, props.plugin.lang) : null,
150
+ h("span", null, props.t("updated") + " " + fmtTime(props.plugin.updated))
151
+ )
152
+ )
153
+ );
154
+ }
155
+
156
+ function DetailPanel(props) {
157
+ var p = props.plugin;
158
+ var installName = p.fullName.split("/")[1];
159
+ return h("div", { className: "__mp_detail" },
160
+ h("div", { className: "__mp_detailTitle" }, p.fullName),
161
+ h("div", { className: "__mp_meta" },
162
+ h("span", null, "★ " + p.stars),
163
+ h("span", null, props.t("updated") + " " + fmtTime(p.updated)),
164
+ props.lang ? h("span", null, p.lang) : null
165
+ ),
166
+ h("div", null,
167
+ h("a", { className: "__mp_link", href: p.htmlUrl, target: "_blank", rel: "noreferrer" }, props.t("openRepo")),
168
+ " · ",
169
+ h("a", { className: "__mp_link", href: "https://www.npmjs.com/search?q=" + encodeURIComponent(installName), target: "_blank", rel: "noreferrer" }, props.t("openNpm"))
170
+ ),
171
+ h("div", { className: "__mp_detailTitle" }, props.t("install")),
172
+ h("div", { className: "__mp_readme" }, props.t("installHint")),
173
+ h("code", { className: "__mp_code" },
174
+ "dsh plugin --profile web add " + installName + "\n" +
175
+ "# then in $DSH_HOME/profiles/web/cordis.patch.yml:\n" +
176
+ "- insert:\n - id: " + installName + "\n name: '" + installName + "'"
177
+ ),
178
+ h("div", { className: "__mp_detailTitle" }, props.t("readme")),
179
+ h("div", { className: "__mp_readme" },
180
+ props.readmeLoading ? props.t("loading")
181
+ : props.readmeError ? props.t("readmeEmpty")
182
+ : (props.readme || "")
183
+ )
184
+ );
185
+ }
186
+
187
+ function MarketplaceSection(props) {
188
+ var t = props.t;
189
+ var state = react.useState({ q: "", sort: "stars", page: 1, items: [], loading: false, error: null, total: 0, open: null, readme: null, readmeLoading: false, readmeError: false });
190
+ var s = state[0], set = state[1];
191
+ var load = react.useCallback(function (q, sort, page, append) {
192
+ set(function (prev) { return Object.assign({}, prev, { loading: true, error: null }); });
193
+ fetchPage(q, sort, page).then(function (out) {
194
+ set(function (prev) {
195
+ var items = append ? prev.items.concat(out.items) : out.items;
196
+ return Object.assign({}, prev, { items: items, total: out.total, loading: false, page: page, q: q, sort: sort });
197
+ });
198
+ }).catch(function () {
199
+ set(function (prev) { return Object.assign({}, prev, { loading: false, error: t("ghError") }); });
200
+ });
201
+ }, [t]);
202
+ react.useEffect(function () {
203
+ load("", "stars", 1, false);
204
+ }, [load]);
205
+ var openDetail = react.useCallback(function (plugin) {
206
+ if (s.open && s.open.fullName === plugin.fullName) { set(function (prev) { return Object.assign({}, prev, { open: null }); }); return; }
207
+ set(function (prev) { return Object.assign({}, prev, { open: plugin, readme: null, readmeError: false, readmeLoading: true }); });
208
+ fetchReadme(plugin.fullName).then(function (text) {
209
+ set(function (prev) {
210
+ if (!prev.open || prev.open.fullName !== plugin.fullName) return prev;
211
+ return Object.assign({}, prev, { readme: text.slice(0, 1200), readmeLoading: false });
212
+ });
213
+ }).catch(function () {
214
+ set(function (prev) {
215
+ if (!prev.open || prev.open.fullName !== plugin.fullName) return prev;
216
+ return Object.assign({}, prev, { readmeLoading: false, readmeError: true });
217
+ });
218
+ });
219
+ }, [s.open]);
220
+ var submit = function (e) {
221
+ e.preventDefault();
222
+ load(s.q, s.sort, 1, false);
223
+ };
224
+ var more = function () { load(s.q, s.sort, s.page + 1, true); };
225
+ return h("div", null,
226
+ h("form", { className: "__mp_toolbar", onSubmit: submit },
227
+ h("input", { className: "__mp_input", type: "search", value: s.q, placeholder: t("search"), onChange: function (e) { set(function (prev) { return Object.assign({}, prev, { q: e.target.value }); }); } }),
228
+ h("select", { className: "__mp_select", value: s.sort, onChange: function (e) { load(s.q, e.target.value, 1, false); } },
229
+ h("option", { value: "stars" }, t("sortStars")),
230
+ h("option", { value: "updated" }, t("sortUpdated"))
231
+ )
232
+ ),
233
+ s.total > 0 ? h("p", { className: "__mp_status" }, t("total").replace("{count}", String(s.total))) : null,
234
+ h("ul", { className: "__mp_grid" },
235
+ s.items.map(function (p) { return h(PluginCard, { key: p.fullName, plugin: p, t: t, onOpen: function () { openDetail(p); } }); })
236
+ ),
237
+ s.loading ? h("p", { className: "__mp_status" }, t("loading")) : null,
238
+ s.error ? h("p", { className: "__mp_error" }, s.error) : null,
239
+ s.items.length === 0 && !s.loading && !s.error ? h("p", { className: "__mp_status" }, t("empty")) : null,
240
+ s.items.length > 0 ? h("button", { type: "button", className: "__mp_more", onClick: more, disabled: s.loading }, t("loadMore")) : null,
241
+ s.open ? h(DetailPanel, { plugin: s.open, t: t, readme: s.readme, readmeLoading: s.readmeLoading, readmeError: s.readmeError, lang: s.open.lang }) : null
242
+ );
243
+ }
244
+
245
+ // ── plugin ────────────────────────────────────────────────────────────
246
+ function apply(ctx) {
247
+ var t = ctx.locale.bind(NS);
248
+ ctx.effect(function () { return ctx.locale.register(NS, { zh: zh, en: en }); }, "dsh-plugin-marketplace: dictionaries");
249
+ ctx.slots.inject("settings.section", function () {
250
+ return ctx.slots.register({
251
+ name: "settings.section",
252
+ id: "marketplace",
253
+ order: 20,
254
+ label: function () { return t("nav"); },
255
+ locale: NS
256
+ }, MarketplaceSection);
257
+ });
258
+ }
259
+
260
+ exports.apply = apply;
261
+ exports.inject = inject;
262
+ return module.exports;
263
+ }
264
+ });
package/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * dsh-plugin-marketplace — node half.
3
+ *
4
+ * The empty apply exists so the plugin appears in the host Loader; the
5
+ * browser half owns the marketplace section through exports["./client"],
6
+ * discovered from the package.json `dsh.client` declaration.
7
+ */
8
+ /** Host plugin body — no host-side behavior for this surface plugin. */
9
+ function apply() {}
10
+
11
+ export { apply };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "dsh-plugin-marketplace",
3
+ "version": "0.1.0",
4
+ "description": "Plugin marketplace for DeepSeek Harness Web UI: browse github.com/topics/dsh-plugin right inside the settings page — search, sort by stars, and view install instructions.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./client": "./client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "dsh": {
13
+ "client": {
14
+ "platform": "web"
15
+ }
16
+ },
17
+ "files": [
18
+ "index.js",
19
+ "client.js",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "keywords": [
24
+ "dsh",
25
+ "dsh-plugin",
26
+ "deepseek-harness",
27
+ "marketplace",
28
+ "web-ui"
29
+ ],
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=20.18"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/Scorp1o117/dsh-plugin-marketplace.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/Scorp1o117/dsh-plugin-marketplace/issues"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^18.2.0"
43
+ }
44
+ }