dsh-himarket 0.1.1

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/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # dsh-himarket
2
+
3
+ 把 [HiMarket](https://github.com/higress-group/himarket)(企业 AI 能力市场)接入 [DeepSeek Harness(DSH)](https://github.com/deepseek-ai/deepseek-harness) 的桥接插件。
4
+
5
+ - **同步已订阅的 MCP Server** → 自动注册成模型可调用的工具(`mcp__<name>__<tool>`)
6
+ - **一键安装已发布的 Skill** → 落盘到 `~/.dsh/skills/`,装完即可用,无需重启
7
+ - **小白友好**:设置页一个「HiMarket」卡片 + 对话式入口(说「同步 HiMarket」「安装 xx 技能」即可)
8
+
9
+ ## 给同事(小白)用:三步
10
+
11
+ 1. 打开 DSH Web → **设置 → HiMarket**。
12
+ 2. 填三样:**HiMarket 地址**、**用户名**、**密码**(找 HiMarket 管理员要开发者账号),点「保存」。
13
+ 3. 点「**同步能力**」,然后:
14
+ - 已订阅的 MCP 工具会自动出现在对话里,直接说「用 xxx 查一下…」即可;
15
+ - 想装技能,点技能卡片上的「**安装**」,装完在对话里说「帮我用 xx 技能…」。
16
+
17
+ > 也可以在对话里直接说「同步 HiMarket」或「安装 xx 技能」,效果一样。
18
+
19
+ ## 给 DSH 管理员:安装
20
+
21
+ ```sh
22
+ # 方式一:公司内网 registry 发布后
23
+ dsh plugin --profile web add dsh-himarket
24
+
25
+ # 方式二:本地源码(开发)
26
+ dsh plugin --profile web add link:E:/path/to/dsh-himarket
27
+ ```
28
+
29
+ 然后在 `~/.dsh/profiles/web/cordis.patch.yml` 里加一行(`dsh plugin add` 会自动写):
30
+
31
+ ```yaml
32
+ - insert:
33
+ - id: himarket
34
+ name: dsh-himarket
35
+ ```
36
+
37
+ 重启 `dsh web`,刷新页面,设置里会出现「HiMarket」卡片。
38
+
39
+ ## 配置
40
+
41
+ 所有配置走 DSH 的 settings(`~/.dsh/settings.yaml`,热加载),也可以在桥接行里给默认值:
42
+
43
+ ```yaml
44
+ - insert:
45
+ - id: himarket
46
+ name: dsh-himarket
47
+ config:
48
+ skillInstallDir: '' # 留空 = ~/.dsh/skills;可改成公司统一技能目录
49
+ ```
50
+
51
+ | 字段 | 默认 | 说明 |
52
+ |---|---|---|
53
+ | `baseUrl` | 空 | HiMarket 后端地址(不含尾斜杠),如 `http://10.0.0.8:8080` |
54
+ | `username` | 空 | 开发者账号用户名 |
55
+ | `password` | 空 | 开发者账号密码(明文,仅存本机 settings.yaml) |
56
+ | `skillInstallDir` | 空 | Skill 安装根;留空回退 `~/.dsh/skills` |
57
+
58
+ ## 架构与实现要点
59
+
60
+ - **不发布服务**:桥接只消费 `webServer`/`tools`/`settings`,放 host 平面、行 loose、无 isolate realm(与 `dsh-matrix` 同形)。
61
+ - **MCP 复用官方客户端**:不重造 MCP 客户端,运行时经 `createRequire(ctx.baseUrl)` + 动态 `import()` 解析 `@deepseek-ai/dsh-mcp-client`,用 Cordis `ctx.plugin()` 动态建/拆 fiber,`serverName` 由 HiMarket `name` 清洗得到。
62
+ - **Skill 靠写文件生效**:下载 ZIP → 系统 `bsdtar` 解压 → 定位 `SKILL.md` → 落盘 `~/.dsh/skills/<name>/`,由 DSH 自带的 `@deepseek-ai/dsh-skill-filesystem` 扫描发现(无需重启)。
63
+ - **凭证走 settings**:与 DSH Web 的 Models 页同一机制,热加载、重启自动恢复。
64
+ - **HiMarket API 契约**(由 HiMarket 后端提供,本插件只做字段容错):
65
+ - `POST /developers/login` → `{code, data:{access_token}}`
66
+ - `GET /cli-providers/market-mcps` → `{data:{mcpServers:[{name,url,transportType}], authHeaders}}`
67
+ - `GET /cli-providers/market-skills` → `{data:{items:[{productId,name,description}]}}`
68
+ - `GET /skills/{productId}/download` → ZIP
69
+
70
+ ## 开发
71
+
72
+ ```sh
73
+ pnpm install
74
+ pnpm build # tsc 编译 src → lib + esbuild 打包 client → lib/client.js
75
+ pnpm test # tsc + esbuild + node --test(单元测试)
76
+ ```
77
+
78
+ - host 端源码在 `src/`(TypeScript,编译到 `lib/`)。
79
+ - 浏览器端源码在 `src/client-main.js`(ES module,`import React`),由 `scripts/build-client.mjs` 用 esbuild 打包为 `lib/client.js`(`__ModuleLoader__.load` 格式,react 外部化),与 `dsh-matrix-agent` 同款。
80
+ - 端到端冒烟:`node tests/mock-himarket.mjs 18080` 起假后端,再对运行中的 web 实例调 `/himarket/save-config`、`/himarket/sync`、`/himarket/install-skill`。
81
+
82
+ ## 发布
83
+
84
+ 推 `v*` tag(或手动触发)后,`.github/workflows/npm-publish.yml` 自动构建、测试、`npm publish` 并建 GitHub Release。需在仓库配 `NPM_TOKEN` secret。
85
+
86
+ ## 已知限制
87
+
88
+ - `stdio` 型 MCP 暂不支持(HiMarket 的 `market-mcps` 目前只暴露 http 型 URL)。
89
+ - 密码明文存本机 `settings.yaml`(与 DSH Models 页的 apiKey 同一风险等级);后续可换 token 直填或系统凭据库。
90
+ - `transportType: sse` 统一按 `streamable-http` 处理(官方 mcp-client 的 Streamable HTTP 传输兼容 SSE 端点)。
91
+ - 清空配置需重启或重新 sync 才会完全清除内存态(token 已在 save-config 清空时一并处理)。
92
+
93
+ ## 许可
94
+
95
+ MIT
@@ -0,0 +1,10 @@
1
+ # dsh-himarket bundle layer: inserts the HiMarket bridge row into the composition.
2
+ # The bridge publishes no service; it only consumes webServer/tools/settings, so it
3
+ # sits loose in the host plane (same shape as dsh-matrix).
4
+ - insert:
5
+ - id: himarket
6
+ name: dsh-himarket
7
+ config:
8
+ # 桥接包在运行时经 createRequire(ctx.baseUrl) 解析官方 @deepseek-ai/* 包,
9
+ # 无需在此声明它们;这里只放桥接自身可调的默认值。
10
+ skillInstallDir: ''
package/lib/client.js ADDED
@@ -0,0 +1,283 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-himarket",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ "use strict";
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
+
36
+ // src/client-main.js
37
+ var client_main_exports = {};
38
+ __export(client_main_exports, {
39
+ apply: () => apply,
40
+ inject: () => inject
41
+ });
42
+ module.exports = __toCommonJS(client_main_exports);
43
+ var import_react = __toESM(require("react"), 1);
44
+ var inject = ["slots", "locale"];
45
+ var el = import_react.default.createElement;
46
+ var NS = "settings.himarket";
47
+ var zh = {
48
+ tab: "HiMarket",
49
+ title: "HiMarket \u80FD\u529B\u5E02\u573A",
50
+ subtitle: "\u586B\u4E00\u6B21\u5730\u5740\u548C\u8D26\u53F7\uFF0C\u70B9\u300C\u540C\u6B65\u300D\uFF0C\u5C31\u80FD\u5728\u5BF9\u8BDD\u91CC\u7528\u4E0A\u516C\u53F8\u4E0A\u67B6\u7684 MCP \u5DE5\u5177\u548C\u6280\u80FD\u3002",
51
+ configTitle: "\u2460 \u8FDE\u63A5\u914D\u7F6E",
52
+ baseUrlLabel: "HiMarket \u5730\u5740",
53
+ baseUrlPlaceholder: "\u4F8B\u5982 http://ai-market.ict.cmcc",
54
+ usernameLabel: "\u7528\u6237\u540D",
55
+ usernamePlaceholder: "\u5F00\u53D1\u8005\u8D26\u53F7\u7528\u6237\u540D",
56
+ passwordLabel: "\u5BC6\u7801",
57
+ passwordPlaceholder: "\u5F00\u53D1\u8005\u8D26\u53F7\u5BC6\u7801",
58
+ save: "\u4FDD\u5B58\u914D\u7F6E",
59
+ saved: "\u5DF2\u4FDD\u5B58",
60
+ sync: "\u540C\u6B65\u80FD\u529B",
61
+ syncing: "\u540C\u6B65\u4E2D\u2026",
62
+ mcpTitle: "\u2461 \u5DF2\u8BA2\u9605 MCP\uFF08\u540C\u6B65\u540E\u81EA\u52A8\u63A5\u5165\u4F1A\u8BDD\uFF09",
63
+ skillTitle: "\u2462 \u53EF\u5B89\u88C5\u6280\u80FD",
64
+ install: "\u5B89\u88C5",
65
+ installed: "\u5DF2\u88C5",
66
+ empty: "\u6682\u65E0\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u540C\u6B65\u300D\u6216\u68C0\u67E5\u8D26\u53F7\u662F\u5426\u5DF2\u8BA2\u9605/\u4E0A\u67B6\u3002",
67
+ error: "\u51FA\u9519\u4E86",
68
+ ok: "\u5B8C\u6210"
69
+ };
70
+ var en = {
71
+ tab: "HiMarket",
72
+ title: "HiMarket Capability Market",
73
+ subtitle: "Configure once, sync, and use subscribed MCP tools and skills in chat.",
74
+ configTitle: "1. Connection",
75
+ baseUrlLabel: "HiMarket URL",
76
+ usernameLabel: "Username",
77
+ passwordLabel: "Password",
78
+ save: "Save",
79
+ saved: "Saved",
80
+ sync: "Sync",
81
+ syncing: "Syncing\u2026",
82
+ mcpTitle: "2. Subscribed MCP (auto-attached)",
83
+ skillTitle: "3. Installable Skills",
84
+ install: "Install",
85
+ installed: "Installed",
86
+ empty: "Nothing yet. Sync first, or check subscriptions/published items.",
87
+ error: "Error",
88
+ ok: "Done"
89
+ };
90
+ var CSS = [
91
+ ".hm_section{width:100%;max-width:760px;display:flex;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary)}",
92
+ ".hm_section h3{margin:0;font-size:13px;font-weight:600;line-height:20px}",
93
+ ".hm_message{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;margin:0}",
94
+ ".hm_message[data-error=true]{color:var(--dsw-alias-state-error-primary)}",
95
+ ".hm_field{display:flex;flex-direction:column;gap:6px}",
96
+ ".hm_field label{font-size:12px;color:var(--dsw-alias-label-secondary)}",
97
+ ".hm_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:8px 10px;font-size:13px;color:var(--dsw-alias-label-primary)}",
98
+ ".hm_row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}",
99
+ ".hm_btn{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 12px;font-size:13px;cursor:pointer}",
100
+ ".hm_btn:hover{background:var(--dsw-alias-bg-layer-2)}",
101
+ ".hm_btn[data-primary=true]{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:#fff}",
102
+ ".hm_list{margin:0;padding:0;list-style:none;display:grid;gap:8px}",
103
+ ".hm_item{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:6px}",
104
+ ".hm_itemTop{display:flex;align-items:center;gap:8px}",
105
+ ".hm_name{font-size:13px;font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
106
+ ".hm_desc{font-size:12px;color:var(--dsw-alias-label-tertiary);margin:0}",
107
+ ".hm_tag{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:16px;flex:none}",
108
+ ".hm_tag[data-ok=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}"
109
+ ].join("\n");
110
+ function injectCss() {
111
+ const tagId = "dsh-himarket/client.css";
112
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
113
+ const tag = document.createElement("style");
114
+ tag.dataset.plugin = "dsh-himarket";
115
+ tag.dataset.pluginCss = tagId;
116
+ tag.textContent = CSS;
117
+ document.head.appendChild(tag);
118
+ }
119
+ }
120
+ function call(path, body) {
121
+ return fetch(path, body === void 0 ? {} : { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }).then((res) => res.json()).then((data) => {
122
+ if (data && data.ok === false) throw new Error(data.error || "request failed");
123
+ return data;
124
+ });
125
+ }
126
+ function HimarketTab(props) {
127
+ const t = props.t;
128
+ const [state, setState] = import_react.default.useState({ status: "loading" });
129
+ const [message, setMessage] = import_react.default.useState(null);
130
+ const [busy, setBusy] = import_react.default.useState(false);
131
+ const [baseUrl, setBaseUrl] = import_react.default.useState("");
132
+ const [username, setUsername] = import_react.default.useState("");
133
+ const [password, setPassword] = import_react.default.useState("");
134
+ const refresh = import_react.default.useCallback(function() {
135
+ call("/himarket/state").then(function(data2) {
136
+ setState({ status: "ready", data: data2 });
137
+ if (data2 && data2.baseUrl) setBaseUrl(data2.baseUrl);
138
+ if (data2 && data2.username) setUsername(data2.username);
139
+ }, function(err) {
140
+ setState({ status: "error", error: err.message });
141
+ });
142
+ }, []);
143
+ import_react.default.useEffect(function() {
144
+ refresh();
145
+ }, [refresh]);
146
+ function saveConfig() {
147
+ setBusy(true);
148
+ call("/himarket/save-config", { baseUrl, username, password }).then(
149
+ function() {
150
+ setMessage(t("saved"));
151
+ setBusy(false);
152
+ refresh();
153
+ },
154
+ function(err) {
155
+ setMessage(t("error") + "\uFF1A" + err.message);
156
+ setBusy(false);
157
+ }
158
+ );
159
+ }
160
+ function doSync() {
161
+ setBusy(true);
162
+ setMessage(t("syncing"));
163
+ call("/himarket/sync", {}).then(function(data2) {
164
+ setMessage(data2.summary || t("ok"));
165
+ setBusy(false);
166
+ refresh();
167
+ }, function(err) {
168
+ setMessage(t("error") + "\uFF1A" + err.message);
169
+ setBusy(false);
170
+ });
171
+ }
172
+ function installSkill(nameOrId) {
173
+ setBusy(true);
174
+ call("/himarket/install-skill", { nameOrId }).then(function(data2) {
175
+ setMessage(data2.summary || t("ok"));
176
+ setBusy(false);
177
+ refresh();
178
+ }, function(err) {
179
+ setMessage(t("error") + "\uFF1A" + err.message);
180
+ setBusy(false);
181
+ });
182
+ }
183
+ const data = state.status === "ready" ? state.data : null;
184
+ const mcpServers = data && data.mcpServers || [];
185
+ const activeNames = data && data.activeMcpNames || [];
186
+ const publishedSkills = data && data.publishedSkills || [];
187
+ const installedSkills = data && data.installedSkills || [];
188
+ function isInstalled(skill) {
189
+ const name = skill && skill.name;
190
+ return installedSkills.indexOf(name) >= 0;
191
+ }
192
+ return el(
193
+ "div",
194
+ { className: "hm_section" },
195
+ el("h3", null, t("title")),
196
+ el("p", { className: "hm_message" }, t("subtitle")),
197
+ el("h3", null, t("configTitle")),
198
+ el(
199
+ "div",
200
+ { className: "hm_field" },
201
+ el("label", null, t("baseUrlLabel")),
202
+ el("input", { className: "hm_input", type: "text", placeholder: t("baseUrlPlaceholder"), value: baseUrl, onChange: (e) => setBaseUrl(e.target.value) })
203
+ ),
204
+ el(
205
+ "div",
206
+ { className: "hm_field" },
207
+ el("label", null, t("usernameLabel")),
208
+ el("input", { className: "hm_input", type: "text", placeholder: t("usernamePlaceholder"), value: username, onChange: (e) => setUsername(e.target.value) })
209
+ ),
210
+ el(
211
+ "div",
212
+ { className: "hm_field" },
213
+ el("label", null, t("passwordLabel")),
214
+ el("input", { className: "hm_input", type: "password", placeholder: t("passwordPlaceholder"), value: password, onChange: (e) => setPassword(e.target.value) })
215
+ ),
216
+ el(
217
+ "div",
218
+ { className: "hm_row" },
219
+ el("button", { className: "hm_btn", type: "button", disabled: busy, onClick: saveConfig }, t("save")),
220
+ el("button", { className: "hm_btn", type: "button", "data-primary": "true", disabled: busy, onClick: doSync }, busy ? t("syncing") : t("sync"))
221
+ ),
222
+ message !== null ? el("p", { className: "hm_message", "data-error": message.indexOf(t("error")) === 0 ? "true" : void 0 }, message) : null,
223
+ el("h3", null, t("mcpTitle")),
224
+ mcpServers.length === 0 && activeNames.length === 0 ? el("p", { className: "hm_message" }, t("empty")) : el(
225
+ "ul",
226
+ { className: "hm_list" },
227
+ mcpServers.map((mm) => {
228
+ const active = activeNames.indexOf(mm.name) >= 0;
229
+ return el(
230
+ "li",
231
+ { className: "hm_item", key: mm.name },
232
+ el(
233
+ "div",
234
+ { className: "hm_itemTop" },
235
+ el("span", { className: "hm_name" }, mm.name),
236
+ el("span", { className: "hm_tag", "data-ok": active ? "true" : void 0 }, active ? "\u5DF2\u63A5\u5165" : "\u672A\u8FDE\u63A5")
237
+ ),
238
+ mm.description ? el("p", { className: "hm_desc" }, mm.description) : null
239
+ );
240
+ })
241
+ ),
242
+ el("h3", null, t("skillTitle")),
243
+ publishedSkills.length === 0 ? el("p", { className: "hm_message" }, t("empty")) : el(
244
+ "ul",
245
+ { className: "hm_list" },
246
+ publishedSkills.map((sk) => {
247
+ const done = isInstalled(sk);
248
+ return el(
249
+ "li",
250
+ { className: "hm_item", key: sk.productId },
251
+ el(
252
+ "div",
253
+ { className: "hm_itemTop" },
254
+ el("span", { className: "hm_name" }, sk.name),
255
+ el("button", { className: "hm_btn", type: "button", disabled: busy || done, onClick: () => installSkill(sk.productId) }, done ? t("installed") : t("install"))
256
+ ),
257
+ sk.description ? el("p", { className: "hm_desc" }, sk.description) : null
258
+ );
259
+ })
260
+ ),
261
+ state.status === "error" ? el("p", { className: "hm_message", "data-error": "true" }, t("error") + "\uFF1A" + state.error) : null
262
+ );
263
+ }
264
+ function apply(ctx) {
265
+ ctx.effect(function() {
266
+ return ctx.locale.register(NS, { zh, en });
267
+ }, "himarket: dictionaries");
268
+ const t = ctx.locale.bind(NS);
269
+ injectCss();
270
+ ctx.slots.inject("settings.plugins.tab", function() {
271
+ return ctx.slots.register({
272
+ name: "settings.plugins.tab",
273
+ id: "himarket",
274
+ order: 30,
275
+ label: () => t("tab"),
276
+ locale: NS,
277
+ inject: () => ({ t })
278
+ }, HimarketTab);
279
+ });
280
+ }
281
+ return module.exports;
282
+ }
283
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * HiMarket REST 客户端:登录 + 拉取已订阅 MCP + 拉取已发布 Skill + 下载 Skill ZIP。
3
+ *
4
+ * 契约(由同事部署的 HiMarket 后端提供,本模块只做字段容错):
5
+ * - 统一响应包装 Response<T> = { code: 'SUCCESS', message?, data }
6
+ * - POST /developers/login data = { access_token, token_type, expires_in }
7
+ * - GET /cli-providers/market-mcps data = { mcpServers: [{name,url,transportType,description}], authHeaders }
8
+ * - GET /cli-providers/market-skills data = [{ productId, name, description, skillTags }]
9
+ * - GET /skills/{productId}/download 二进制 ZIP(@PublicAccess)
10
+ *
11
+ * @module dsh-himarket/himarket-client
12
+ */
13
+ /** 单个已订阅 MCP Server(market-mcps 条目)。 */
14
+ export interface SubscribedMcp {
15
+ productId: string;
16
+ name: string;
17
+ url: string;
18
+ transportType: string;
19
+ description: string;
20
+ }
21
+ /** 单个已发布 Skill(market-skills 条目)。 */
22
+ export interface PublishedSkill {
23
+ productId: string;
24
+ name: string;
25
+ description: string;
26
+ skillTags: string[];
27
+ }
28
+ export declare class HimarketError extends Error {
29
+ }
30
+ /**
31
+ * HiMarket REST 客户端。持有 baseUrl + 用户名/密码 + 缓存 token,
32
+ * 401 自动重登一次。所有方法抛 HimarketError(含可读中文原因)。
33
+ */
34
+ export declare class HimarketClient {
35
+ private readonly baseUrl;
36
+ private readonly username;
37
+ private readonly password;
38
+ private token;
39
+ private fetchFn;
40
+ constructor(opts: {
41
+ baseUrl: string;
42
+ username: string;
43
+ password: string;
44
+ token?: string;
45
+ fetchFn?: typeof fetch;
46
+ });
47
+ get hasToken(): boolean;
48
+ get currentToken(): string;
49
+ /** 登录并缓存 token;返回 access_token。 */
50
+ login(): Promise<string>;
51
+ /** 拉取已订阅(且已批准)的 MCP 列表 + 认证头。 */
52
+ listSubscribedMcps(): Promise<{
53
+ mcpServers: SubscribedMcp[];
54
+ authHeaders: Record<string, string>;
55
+ }>;
56
+ /** 拉取已发布 Skill 清单。 */
57
+ listPublishedSkills(): Promise<PublishedSkill[]>;
58
+ /** 下载 Skill ZIP,返回字节。 */
59
+ downloadSkill(productId: string): Promise<Uint8Array>;
60
+ /** 通用请求:带 401 自动重登一次;解析 {code,data} 包装。 */
61
+ private request;
62
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * HiMarket REST 客户端:登录 + 拉取已订阅 MCP + 拉取已发布 Skill + 下载 Skill ZIP。
3
+ *
4
+ * 契约(由同事部署的 HiMarket 后端提供,本模块只做字段容错):
5
+ * - 统一响应包装 Response<T> = { code: 'SUCCESS', message?, data }
6
+ * - POST /developers/login data = { access_token, token_type, expires_in }
7
+ * - GET /cli-providers/market-mcps data = { mcpServers: [{name,url,transportType,description}], authHeaders }
8
+ * - GET /cli-providers/market-skills data = [{ productId, name, description, skillTags }]
9
+ * - GET /skills/{productId}/download 二进制 ZIP(@PublicAccess)
10
+ *
11
+ * @module dsh-himarket/himarket-client
12
+ */
13
+ export class HimarketError extends Error {
14
+ }
15
+ /** 真实 HiMarket 后端的 API 前缀(前端同域反向代理到 /api/v1)。 */
16
+ const API_PREFIX = '/api/v1';
17
+ /** 规范 baseUrl(去尾斜杠;空则抛)。 */
18
+ function normalizeBaseUrl(baseUrl) {
19
+ const trimmed = baseUrl.trim().replace(/\/+$/u, '');
20
+ if (trimmed === '')
21
+ throw new HimarketError('未配置 HiMarket 地址(baseUrl 为空)');
22
+ return trimmed;
23
+ }
24
+ /**
25
+ * HiMarket REST 客户端。持有 baseUrl + 用户名/密码 + 缓存 token,
26
+ * 401 自动重登一次。所有方法抛 HimarketError(含可读中文原因)。
27
+ */
28
+ export class HimarketClient {
29
+ baseUrl;
30
+ username;
31
+ password;
32
+ token;
33
+ fetchFn;
34
+ constructor(opts) {
35
+ this.baseUrl = normalizeBaseUrl(opts.baseUrl);
36
+ this.username = opts.username;
37
+ this.password = opts.password;
38
+ this.token = opts.token ?? '';
39
+ this.fetchFn = opts.fetchFn ?? globalThis.fetch;
40
+ }
41
+ get hasToken() {
42
+ return this.token !== '';
43
+ }
44
+ get currentToken() {
45
+ return this.token;
46
+ }
47
+ /** 登录并缓存 token;返回 access_token。 */
48
+ async login() {
49
+ const wrapped = await this.request('/developers/login', {
50
+ method: 'POST',
51
+ body: JSON.stringify({ username: this.username, password: this.password }),
52
+ auth: false,
53
+ });
54
+ const token = wrapped.access_token;
55
+ if (token === undefined || token === '') {
56
+ throw new HimarketError('登录成功但未返回 access_token');
57
+ }
58
+ this.token = token;
59
+ return token;
60
+ }
61
+ /** 拉取已订阅(且已批准)的 MCP 列表 + 认证头。 */
62
+ async listSubscribedMcps() {
63
+ const data = await this.request('/cli-providers/market-mcps', { auth: true });
64
+ const mcpServers = (data.mcpServers ?? [])
65
+ .filter((m) => typeof m.name === 'string' && m.name !== '' && typeof m.url === 'string' && m.url !== '')
66
+ .map((m) => ({
67
+ productId: String(m.productId ?? ''),
68
+ name: String(m.name),
69
+ url: String(m.url),
70
+ transportType: String(m.transportType ?? 'streamable-http'),
71
+ description: String(m.description ?? ''),
72
+ }));
73
+ return { mcpServers, authHeaders: data.authHeaders ?? {} };
74
+ }
75
+ /** 拉取已发布 Skill 清单。 */
76
+ async listPublishedSkills() {
77
+ const data = await this.request('/cli-providers/market-skills', { auth: true });
78
+ const list = Array.isArray(data) ? data : (data.items ?? []);
79
+ return list
80
+ .filter((s) => typeof s.productId === 'string' && s.productId !== '' && typeof s.name === 'string' && s.name !== '')
81
+ .map((s) => ({
82
+ productId: String(s.productId),
83
+ name: String(s.name),
84
+ description: String(s.description ?? ''),
85
+ skillTags: Array.isArray(s.skillTags) ? s.skillTags.filter((t) => typeof t === 'string') : [],
86
+ }));
87
+ }
88
+ /** 下载 Skill ZIP,返回字节。 */
89
+ async downloadSkill(productId) {
90
+ const url = `${this.baseUrl}${API_PREFIX}/skills/${encodeURIComponent(productId)}/download`;
91
+ const res = await this.fetchFn(url, { method: 'GET' });
92
+ if (!res.ok) {
93
+ throw new HimarketError(`下载技能失败(HTTP ${res.status})`);
94
+ }
95
+ const buf = new Uint8Array(await res.arrayBuffer());
96
+ if (buf.length === 0)
97
+ throw new HimarketError('下载到的技能包为空');
98
+ return buf;
99
+ }
100
+ /** 通用请求:带 401 自动重登一次;解析 {code,data} 包装。 */
101
+ async request(path, opts) {
102
+ const doFetch = async () => {
103
+ const headers = { 'Content-Type': 'application/json' };
104
+ if (opts.auth && this.token !== '')
105
+ headers.Authorization = `Bearer ${this.token}`;
106
+ const res = await this.fetchFn(this.baseUrl + API_PREFIX + path, {
107
+ method: opts.method ?? 'GET',
108
+ headers,
109
+ ...(opts.body !== undefined ? { body: opts.body } : {}),
110
+ });
111
+ return res;
112
+ };
113
+ let res = await doFetch();
114
+ if (res.status === 401 && opts.auth) {
115
+ await this.login();
116
+ res = await doFetch();
117
+ }
118
+ if (!res.ok) {
119
+ const body = await res.text().catch(() => '');
120
+ throw new HimarketError(`HiMarket 请求失败(HTTP ${res.status})${body.slice(0, 200)}`);
121
+ }
122
+ const wrapped = (await res.json().catch(() => ({ code: 'PARSE_ERROR' })));
123
+ if (wrapped.code !== undefined && wrapped.code !== 'SUCCESS') {
124
+ throw new HimarketError(wrapped.message ?? `HiMarket 返回错误码 ${wrapped.code}`);
125
+ }
126
+ return (wrapped.data ?? wrapped);
127
+ }
128
+ }
129
+ //# sourceMappingURL=himarket-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"himarket-client.js","sourceRoot":"","sources":["../src/himarket-client.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * dsh-himarket 宿主端:HiMarket 桥接的编排器。
3
+ *
4
+ * 职责:
5
+ * - 注册 settings 命名空间持久化凭证;
6
+ * - 注册环回 HTTP 路由 /himarket/* 供设置页卡片调用(state/save-config/sync/install-skill);
7
+ * - 注册对话式工具 himarket_sync / himarket_install_skill;
8
+ * - 用 McpManager 把已订阅 MCP 映射为官方 dsh-mcp-client 动态 fiber;
9
+ * - 用 installSkill 把 Skill ZIP 落盘到 ~/.dsh/skills/。
10
+ *
11
+ * 归属:host 平面、不发布服务(只消费 webServer/tools/settings),行 loose、无 isolate realm。
12
+ *
13
+ * @module dsh-himarket
14
+ */
15
+ import type { Context } from '@deepseek-ai/cordis';
16
+ export declare const name = "himarket";
17
+ /** cordis.patch.yml 行 config(默认值,settings 覆盖)。 */
18
+ export interface Config {
19
+ skillInstallDir: string;
20
+ }
21
+ export declare function apply(ctx: Context, config: Config): void;
22
+ export { defaultSkillRoot } from './skill.js';
23
+ export { sanitizeServerName } from './mcp.js';