@michengai/dsh-skills-manager 0.1.18 → 0.1.20
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 +98 -57
- package/README.zh-CN.md +98 -57
- package/assets/branding/dsh-logo.png +0 -0
- package/assets/screenshots/skills-manager-en.png +0 -0
- package/assets/screenshots/skills-manager.png +0 -0
- package/assets/screenshots/skills-public.png +0 -0
- package/assets/screenshots/slash-command.png +0 -0
- package/assets/screenshots/upload-plugin.png +0 -0
- package/lib/client.js +20 -5
- package/lib/core.js +57 -22
- package/lib/index.js +24 -9
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,41 +1,66 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<table cellpadding="0" cellspacing="0">
|
|
3
|
+
<tr>
|
|
4
|
+
<td valign="middle" align="center">
|
|
5
|
+
<img src="assets/branding/dsh-logo.png" alt="DeepSeek Harness logo" width="168">
|
|
6
|
+
</td>
|
|
7
|
+
<td valign="middle">
|
|
8
|
+
<pre>
|
|
9
|
+
____ ____ _ _
|
|
10
|
+
| _ \/ ___|| | | |
|
|
11
|
+
| | | \___ \| |_| |
|
|
12
|
+
| |_| |___) | _ |
|
|
13
|
+
|____/|____/|_| |_|
|
|
14
|
+
SKILLS MANAGER
|
|
15
|
+
Local + shared Agent skills
|
|
16
|
+
DeepSeek Harness plugin
|
|
17
|
+
</pre>
|
|
18
|
+
</td>
|
|
19
|
+
</tr>
|
|
20
|
+
</table>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div align="center">
|
|
24
|
+
|
|
25
|
+
# DSH Skills Manager
|
|
26
|
+
|
|
27
|
+
**Safely manage local skills and inspect shared Agent skills in DeepSeek Harness**
|
|
28
|
+
|
|
29
|
+
[简体中文](README.zh-CN.md) · [Apache-2.0](LICENSE)
|
|
30
|
+
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](https://www.npmjs.com/package/@michengai/dsh-skills-manager)
|
|
33
|
+
[](https://github.com/MichengAI/dsh-skills-manager)
|
|
34
|
+
[](https://nodejs.org/)
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
> DSH Skills Manager is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
|
|
4
38
|
|
|
5
|
-
|
|
39
|
+
## Features
|
|
6
40
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
41
|
+
- Filter by category or search, then inspect DSH-local and shared Agent skills in **Settings → Skills**.
|
|
42
|
+
- Enable, disable, upload, replace, and delete DSH-local skills.
|
|
43
|
+
- Keep shared Agent skills strictly read-only and never change their global metadata.
|
|
44
|
+
- Import a plugin directory containing `SKILL.md` through the native file picker and confirm every name collision.
|
|
45
|
+
- Paste one sentence into DSH, Codex, or WorkBuddy and let that agent install the plugin locally.
|
|
10
46
|
|
|
11
|
-
|
|
12
|
-
<a href="https://github.com/MichengAI/dsh-skills-manager/issues">Report an issue</a>
|
|
13
|
-
· <a href="https://www.npmjs.com/package/@michengai/dsh-skills-manager">View on npm</a>
|
|
14
|
-
· <a href="README.zh-CN.md">简体中文</a>
|
|
15
|
-
</p>
|
|
47
|
+
## Screenshots
|
|
16
48
|
|
|
17
|
-
|
|
18
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="Apache License 2.0"></a>
|
|
19
|
-
<a href="https://www.npmjs.com/package/@michengai/dsh-skills-manager"><img src="https://img.shields.io/npm/v/%40michengai/dsh-skills-manager?label=npm" alt="npm package"></a>
|
|
20
|
-
<img src="https://img.shields.io/badge/DSH-Web%20Plugin-10b981" alt="DSH Web Plugin">
|
|
21
|
-
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933?logo=nodedotjs&logoColor=white" alt="Node.js 20 or later">
|
|
22
|
-
</p>
|
|
49
|
+
Filter by category or search in **Settings → Skills**. DSH-local skills can be enabled, disabled, or deleted; shared Agent skills stay read-only:
|
|
23
50
|
|
|
24
|
-
|
|
51
|
+

|
|
25
52
|
|
|
26
|
-
|
|
27
|
-
<img src="assets/screenshots/skills-manager.png" alt="Skills Manager settings page" width="960">
|
|
28
|
-
</p>
|
|
53
|
+
Shared Agent skills stay visible when the DSH-local group is empty:
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+

|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
The compact upload dialog accepts a plugin `SKILL.md`, a plugin directory, or drag-and-drop:
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
Enabling a DSH-local skill restores its `/` command in the chat composer:
|
|
33
62
|
|
|
34
|
-
|
|
35
|
-
- Enables, disables, uploads, replaces, and deletes DSH-local skills.
|
|
36
|
-
- Keeps shared Agent skills strictly read-only and never changes their global metadata.
|
|
37
|
-
- Imports a plugin directory containing `SKILL.md` through the native file picker and confirms every name collision.
|
|
38
|
-
- Rejects traversal names, imports from the DSH skills directory itself, and symbolic links.
|
|
63
|
+

|
|
39
64
|
|
|
40
65
|
## Prerequisites
|
|
41
66
|
|
|
@@ -45,18 +70,54 @@
|
|
|
45
70
|
|
|
46
71
|
## Installation
|
|
47
72
|
|
|
48
|
-
|
|
73
|
+
`dsh plugin add` forwards to `pnpm add` in the profile directory. Without a version and official registry, a local mirror or minimum-release-age policy can leave you on an older build.
|
|
74
|
+
|
|
75
|
+
### Ask another agent to install it
|
|
76
|
+
|
|
77
|
+
This plugin runs inside DeepSeek Harness Web. Copy one of the sentences below into DSH, Codex, or WorkBuddy and let that agent install it into your local `web` profile.
|
|
78
|
+
|
|
79
|
+
From npm:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Install the latest DSH plugin @michengai/dsh-skills-manager into my local web profile using the official npm registry: dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=https://registry.npmjs.org/. Then run dsh --profile web --dump-config, confirm skills-manager is mounted, and remind me to restart DSH Web and hard-refresh the browser.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
From source:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
Install the DSH plugin from source at https://github.com/MichengAI/dsh-skills-manager: clone it, run npm install and npm test, then run dsh plugin --profile web add . from that directory. Do not copy lib by itself. Then run dsh --profile web --dump-config, confirm skills-manager is mounted, and remind me to restart DSH Web and hard-refresh the browser.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Product | How to use it |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| DSH | Send one of the sentences above to the current session. |
|
|
94
|
+
| Codex | Send one of the sentences above to Codex and let it install locally. |
|
|
95
|
+
| WorkBuddy | Send one of the sentences above to WorkBuddy; for a source install you can also paste `https://github.com/MichengAI/dsh-skills-manager`. |
|
|
96
|
+
|
|
97
|
+
Codex and WorkBuddy only install the plugin. After that, open DSH Web and use **Settings → Skills**.
|
|
49
98
|
|
|
50
|
-
|
|
99
|
+
You can also run the same npm command yourself:
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=https://registry.npmjs.org/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If `dsh` is not on PATH, replace the leading `dsh` with `npx --yes @deepseek-ai/dsh`.
|
|
106
|
+
|
|
107
|
+
### Install the latest package from the official npm registry
|
|
108
|
+
|
|
109
|
+
Run this from any PowerShell directory:
|
|
51
110
|
|
|
52
111
|
```powershell
|
|
53
112
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
54
113
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
55
|
-
dsh plugin --profile web add @michengai/dsh-skills-manager
|
|
114
|
+
dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=https://registry.npmjs.org/
|
|
56
115
|
dsh --profile web --dump-config
|
|
57
116
|
```
|
|
58
117
|
|
|
59
|
-
|
|
118
|
+
To pin a release, replace `@latest` with a version such as `@0.1.20`.
|
|
119
|
+
|
|
120
|
+
The configuration output should contain `skills-manager`. Restart DSH Web and hard-refresh the browser. Do not copy client files manually: `dsh plugin add` also applies `cordis.patch.yml`.
|
|
60
121
|
|
|
61
122
|
### Install from source
|
|
62
123
|
|
|
@@ -74,7 +135,7 @@ dsh plugin --profile web add .
|
|
|
74
135
|
dsh --profile web --dump-config
|
|
75
136
|
```
|
|
76
137
|
|
|
77
|
-
Restart DSH Web
|
|
138
|
+
Restart DSH Web and hard-refresh the browser. `dsh plugin ... add .` reads the package metadata and `cordis.patch.yml`; do not install by copying `lib` directly.
|
|
78
139
|
|
|
79
140
|
## Usage
|
|
80
141
|
|
|
@@ -82,37 +143,18 @@ Open **Settings → Skills**, then use the panel as follows:
|
|
|
82
143
|
|
|
83
144
|
| Goal | Action | Scope |
|
|
84
145
|
| --- | --- | --- |
|
|
146
|
+
| Search or filter | Use **Category** and **Search** to narrow by directory, name, kind, or description. | DSH and shared Agent skills |
|
|
85
147
|
| Inspect a skill | Review its name, form, description, and invocation state. | DSH and shared Agent skills |
|
|
86
148
|
| Enable or disable | Select **Enable** or **Disable**; this controls model invocation and the `/` command. | DSH-local skills only |
|
|
87
|
-
| Upload a plugin | Select **Upload
|
|
149
|
+
| Upload a plugin | Select **Upload**, then choose the plugin directory’s `SKILL.md`. Its scripts and resources are copied too. | DSH-local skills only |
|
|
88
150
|
| Choose the directory | If the selected file has no usable path, choose the directory containing `SKILL.md`. | DSH-local skills only |
|
|
89
151
|
| Replace or delete | Confirm a name collision, or select **Delete** for an unneeded local skill. | DSH-local skills only |
|
|
90
152
|
| Inspect shared skills | Review shared Agent skills without changing their metadata. | Read-only |
|
|
91
153
|
|
|
92
|
-
|
|
93
|
-
<img src="assets/screenshots/skills-public.png" alt="Shared Agent skills remain visible when no local skills are installed" width="960">
|
|
94
|
-
</p>
|
|
95
|
-
|
|
96
|
-
> Shared Agent skills stay visible when the DSH-local group is empty.
|
|
97
|
-
|
|
98
|
-
<p align="center">
|
|
99
|
-
<img src="assets/screenshots/upload-plugin.png" alt="Upload plugin dialog" width="960">
|
|
100
|
-
</p>
|
|
101
|
-
|
|
102
|
-
> The compact upload dialog accepts a plugin `SKILL.md`, a plugin directory, or drag-and-drop.
|
|
103
|
-
|
|
104
|
-
<p align="center">
|
|
105
|
-
<img src="assets/screenshots/delete-plugin.png" alt="Delete plugin confirmation" width="960">
|
|
106
|
-
</p>
|
|
154
|
+

|
|
107
155
|
|
|
108
156
|
> Deleting a DSH-local skill requires confirmation and cannot be undone.
|
|
109
157
|
|
|
110
|
-
<p align="center">
|
|
111
|
-
<img src="assets/screenshots/slash-command.png" alt="Chat slash command after enabling a local skill" width="640">
|
|
112
|
-
</p>
|
|
113
|
-
|
|
114
|
-
> Enabling a DSH-local skill restores its `/` command in the chat composer.
|
|
115
|
-
|
|
116
158
|
Escape closes only the frontmost upload or confirmation dialog and leaves Settings open.
|
|
117
159
|
|
|
118
160
|
## Permissions and safety limits
|
|
@@ -165,4 +207,3 @@ npm run pack:check
|
|
|
165
207
|
Project status, usage boundaries, architecture, and iteration records begin at the [documentation entry point](docs/00-交接入口/00-阅读导航.md). The detailed operational guide is `docs\02-产品与业务\01-使用说明.md`.
|
|
166
208
|
|
|
167
209
|
Licensed under [Apache License 2.0](LICENSE).
|
|
168
|
-
|
package/README.zh-CN.md
CHANGED
|
@@ -1,41 +1,66 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<table cellpadding="0" cellspacing="0">
|
|
3
|
+
<tr>
|
|
4
|
+
<td valign="middle" align="center">
|
|
5
|
+
<img src="assets/branding/dsh-logo.png" alt="DeepSeek Harness 标识" width="168">
|
|
6
|
+
</td>
|
|
7
|
+
<td valign="middle">
|
|
8
|
+
<pre>
|
|
9
|
+
____ ____ _ _
|
|
10
|
+
| _ \/ ___|| | | |
|
|
11
|
+
| | | \___ \| |_| |
|
|
12
|
+
| |_| |___) | _ |
|
|
13
|
+
|____/|____/|_| |_|
|
|
14
|
+
SKILLS MANAGER
|
|
15
|
+
Local + shared Agent skills
|
|
16
|
+
DeepSeek Harness plugin
|
|
17
|
+
</pre>
|
|
18
|
+
</td>
|
|
19
|
+
</tr>
|
|
20
|
+
</table>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div align="center">
|
|
24
|
+
|
|
25
|
+
# DSH Skills Manager
|
|
26
|
+
|
|
27
|
+
**在 DeepSeek Harness 中安全管理本地技能,并查看公共 Agent 技能**
|
|
28
|
+
|
|
29
|
+
[English](README.md) · [Apache-2.0](LICENSE)
|
|
30
|
+
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](https://www.npmjs.com/package/@michengai/dsh-skills-manager)
|
|
33
|
+
[](https://github.com/MichengAI/dsh-skills-manager)
|
|
34
|
+
[](https://nodejs.org/)
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
> DSH Skills Manager 是社区维护的 DeepSeek Harness(DSH)插件,并非 DeepSeek AI 官方产品。
|
|
4
38
|
|
|
5
|
-
|
|
39
|
+
## 功能概览
|
|
6
40
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
41
|
+
- 在「设置 → 技能」中按分类筛选或搜索,查看 DSH 本地技能与公共 Agent 技能。
|
|
42
|
+
- 对 DSH 本地技能执行启用、停用、上传、覆盖和删除。
|
|
43
|
+
- 公共 Agent 技能始终只读,不改写共享的全局元数据。
|
|
44
|
+
- 使用系统文件选择器导入包含 `SKILL.md` 的插件目录,并对同名覆盖强制确认。
|
|
45
|
+
- 可把一句话复制到 DSH、Codex 或 WorkBuddy,让对方代装到本机 DSH。
|
|
10
46
|
|
|
11
|
-
|
|
12
|
-
<a href="https://github.com/MichengAI/dsh-skills-manager/issues">反馈问题</a>
|
|
13
|
-
· <a href="https://www.npmjs.com/package/@michengai/dsh-skills-manager">在 npm 查看</a>
|
|
14
|
-
· <a href="README.md">English</a>
|
|
15
|
-
</p>
|
|
47
|
+
## 界面预览
|
|
16
48
|
|
|
17
|
-
|
|
18
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="Apache License 2.0"></a>
|
|
19
|
-
<a href="https://www.npmjs.com/package/@michengai/dsh-skills-manager"><img src="https://img.shields.io/npm/v/%40michengai/dsh-skills-manager?label=npm" alt="npm 包"></a>
|
|
20
|
-
<img src="https://img.shields.io/badge/DSH-Web%20Plugin-10b981" alt="DSH Web Plugin">
|
|
21
|
-
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933?logo=nodedotjs&logoColor=white" alt="Node.js 20 或更高版本">
|
|
22
|
-
</p>
|
|
49
|
+
在「设置 → 技能」中按分类筛选或搜索。DSH 本地技能可启用、停用和删除;公共 Agent 技能只读:
|
|
23
50
|
|
|
24
|
-
|
|
51
|
+

|
|
25
52
|
|
|
26
|
-
|
|
27
|
-
<img src="assets/screenshots/skills-manager.png" alt="技能管理设置页面" width="960">
|
|
28
|
-
</p>
|
|
53
|
+
本地 DSH 技能为空时,公共 Agent 技能仍然可见:
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+

|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
紧凑上传弹窗支持选择 `SKILL.md`、选择插件目录,或直接拖放文件:
|
|
33
58
|
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
启用 DSH 本地技能后,聊天输入框会恢复对应的 `/` 命令:
|
|
62
|
+
|
|
63
|
+

|
|
39
64
|
|
|
40
65
|
## 前置条件
|
|
41
66
|
|
|
@@ -45,22 +70,58 @@
|
|
|
45
70
|
|
|
46
71
|
## 安装
|
|
47
72
|
|
|
48
|
-
|
|
73
|
+
`dsh plugin add` 会转发到 profile 目录里的 `pnpm add`。不写版本、不指定官方源时,本机镜像和最短发布间隔可能让你停在旧版。
|
|
74
|
+
|
|
75
|
+
### 交给其他 Agent 一句话安装
|
|
76
|
+
|
|
77
|
+
本插件运行在 DeepSeek Harness Web 里。把下面其中一句复制到 DSH、Codex 或 WorkBuddy,让它代你安装到本机 `web` profile。
|
|
78
|
+
|
|
79
|
+
从 npm 安装:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
请把 DSH 插件 @michengai/dsh-skills-manager 最新版装进本机 web profile,使用官方 npm 源执行:dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=https://registry.npmjs.org/。装完执行 dsh --profile web --dump-config,确认已挂载 skills-manager,并提醒我重启 DSH Web 后硬刷新浏览器。
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
从源码安装:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
请从 https://github.com/MichengAI/dsh-skills-manager 安装 DSH 插件:克隆仓库,执行 npm install 和 npm test,再在该目录执行 dsh plugin --profile web add .。不要只复制 lib。然后执行 dsh --profile web --dump-config,确认已挂载 skills-manager,并提醒我重启 DSH Web 后硬刷新浏览器。
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| 产品 | 怎么用 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| DSH | 把上面其中一句发给当前会话。 |
|
|
94
|
+
| Codex | 把上面其中一句发给 Codex,让它在本机执行安装。 |
|
|
95
|
+
| WorkBuddy | 把上面其中一句发给 WorkBuddy;源码安装也可同时粘贴仓库地址 `https://github.com/MichengAI/dsh-skills-manager`。 |
|
|
96
|
+
|
|
97
|
+
Codex 和 WorkBuddy 只负责代装;装好后仍要打开 DSH Web 使用「设置 → 技能」。
|
|
98
|
+
|
|
99
|
+
也可以自己执行同一条 npm 命令:
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=https://registry.npmjs.org/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
未把 `dsh` 装进 PATH 时,把开头的 `dsh` 换成 `npx --yes @deepseek-ai/dsh`。
|
|
106
|
+
|
|
107
|
+
### 从官方 npm 安装最新版
|
|
49
108
|
|
|
50
|
-
在任意 PowerShell
|
|
109
|
+
在任意 PowerShell 目录执行:
|
|
51
110
|
|
|
52
111
|
```powershell
|
|
53
112
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
54
113
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
55
|
-
dsh plugin --profile web add @michengai/dsh-skills-manager
|
|
114
|
+
dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=https://registry.npmjs.org/
|
|
56
115
|
dsh --profile web --dump-config
|
|
57
116
|
```
|
|
58
117
|
|
|
59
|
-
|
|
118
|
+
需要钉死某一版时,把 `@latest` 换成具体版本,例如 `@0.1.20`。
|
|
119
|
+
|
|
120
|
+
配置输出中应包含 `skills-manager`。安装后重启 DSH Web 并在浏览器硬刷新。不要手工复制客户端文件,`dsh plugin add` 会同时应用 `cordis.patch.yml`。
|
|
60
121
|
|
|
61
122
|
### 从源码安装
|
|
62
123
|
|
|
63
|
-
|
|
124
|
+
适用于调试或使用未发布改动。克隆后的本地路径就是插件安装路径:
|
|
64
125
|
|
|
65
126
|
```powershell
|
|
66
127
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
@@ -74,7 +135,7 @@ dsh plugin --profile web add .
|
|
|
74
135
|
dsh --profile web --dump-config
|
|
75
136
|
```
|
|
76
137
|
|
|
77
|
-
完成后重启 DSH Web
|
|
138
|
+
完成后重启 DSH Web 并硬刷新浏览器。`dsh plugin ... add .` 会读取当前目录的包信息和 `cordis.patch.yml`;不要改为直接复制 `lib` 目录。
|
|
78
139
|
|
|
79
140
|
## 使用
|
|
80
141
|
|
|
@@ -82,6 +143,7 @@ dsh --profile web --dump-config
|
|
|
82
143
|
|
|
83
144
|
| 目标 | 操作 | 范围 |
|
|
84
145
|
| --- | --- | --- |
|
|
146
|
+
| 搜索或筛选 | 用「分类」和「搜索」按目录、名称、形态或简介收窄列表。 | DSH 与公共 Agent 技能 |
|
|
85
147
|
| 查看技能 | 查看名称、形态、说明和调用状态。 | DSH 与公共 Agent 技能 |
|
|
86
148
|
| 启用或停用 | 点击「启用」或「停用」。它会同步控制模型调用和 `/` 手动命令。 | 仅 DSH 本地技能 |
|
|
87
149
|
| 上传插件 | 点击「上传插件」,选择插件目录内的 `SKILL.md`。完整目录、脚本和资源都会被复制。 | 仅 DSH 本地技能 |
|
|
@@ -89,30 +151,10 @@ dsh --profile web --dump-config
|
|
|
89
151
|
| 覆盖或删除 | 同名时确认覆盖;使用「删除」移除不再需要的本地技能。 | 仅 DSH 本地技能 |
|
|
90
152
|
| 查看公共技能 | 查看公共 Agent 技能,不修改其元数据。 | 只读 |
|
|
91
153
|
|
|
92
|
-
|
|
93
|
-
<img src="assets/screenshots/skills-public.png" alt="未安装本地技能时仍可查看公共 Agent 技能" width="960">
|
|
94
|
-
</p>
|
|
95
|
-
|
|
96
|
-
> 本地 DSH 技能为空时,公共 Agent 技能仍然可见。
|
|
97
|
-
|
|
98
|
-
<p align="center">
|
|
99
|
-
<img src="assets/screenshots/upload-plugin.png" alt="上传插件弹窗" width="960">
|
|
100
|
-
</p>
|
|
101
|
-
|
|
102
|
-
> 紧凑上传弹窗支持选择 `SKILL.md`、选择插件目录,或直接拖放文件。
|
|
103
|
-
|
|
104
|
-
<p align="center">
|
|
105
|
-
<img src="assets/screenshots/delete-plugin.png" alt="删除插件确认框" width="960">
|
|
106
|
-
</p>
|
|
154
|
+

|
|
107
155
|
|
|
108
156
|
> 删除 DSH 本地技能需要确认,删除后无法恢复。
|
|
109
157
|
|
|
110
|
-
<p align="center">
|
|
111
|
-
<img src="assets/screenshots/slash-command.png" alt="启用本地技能后的斜杠命令" width="640">
|
|
112
|
-
</p>
|
|
113
|
-
|
|
114
|
-
> 启用 DSH 本地技能后,聊天输入框会恢复对应的 `/` 命令。
|
|
115
|
-
|
|
116
158
|
按 ESC 只关闭最上层上传框或确认框,设置页会保持打开。
|
|
117
159
|
|
|
118
160
|
## 权限与安全边界
|
|
@@ -165,4 +207,3 @@ npm run pack:check
|
|
|
165
207
|
项目状态、使用边界、技术架构和迭代记录从[文档交接入口](docs/00-交接入口/00-阅读导航.md)开始。详细操作说明见 `docs\02-产品与业务\01-使用说明.md`。
|
|
166
208
|
|
|
167
209
|
本项目采用 [Apache License 2.0](LICENSE)。
|
|
168
|
-
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -478,6 +478,7 @@ window.__ModuleLoader__.load({
|
|
|
478
478
|
var deleteState = react.useState(null);
|
|
479
479
|
var confirmDelete = deleteState[0];
|
|
480
480
|
var setConfirmDelete = deleteState[1];
|
|
481
|
+
var inflightRef = react.useRef(false);
|
|
481
482
|
var queryState = react.useState("");
|
|
482
483
|
var query = queryState[0];
|
|
483
484
|
var setQuery = queryState[1];
|
|
@@ -524,12 +525,15 @@ window.__ModuleLoader__.load({
|
|
|
524
525
|
}, [uploadOpen, confirmImport, confirmDelete]);
|
|
525
526
|
|
|
526
527
|
function action(path, body) {
|
|
527
|
-
if (busy) return;
|
|
528
|
+
if (busy || inflightRef.current) return;
|
|
529
|
+
inflightRef.current = true;
|
|
528
530
|
setBusy(path);
|
|
529
531
|
callApi(path, { method: "POST", headers: MUTATION_HEADERS, body: JSON.stringify(body || {}) }).then(function () {
|
|
532
|
+
inflightRef.current = false;
|
|
530
533
|
setBusy(null);
|
|
531
534
|
refresh();
|
|
532
535
|
}).catch(function (error) {
|
|
536
|
+
inflightRef.current = false;
|
|
533
537
|
setBusy(null);
|
|
534
538
|
setSnapshot({ loading: false, error: error, data: snapshot.data });
|
|
535
539
|
});
|
|
@@ -574,31 +578,39 @@ window.__ModuleLoader__.load({
|
|
|
574
578
|
}
|
|
575
579
|
|
|
576
580
|
function executeImport(source, conflict) {
|
|
581
|
+
if (inflightRef.current && busy !== "import-check") return;
|
|
582
|
+
inflightRef.current = true;
|
|
577
583
|
setBusy("import");
|
|
578
584
|
setConfirmImport(null);
|
|
579
585
|
callApi("/import", { method: "POST", headers: MUTATION_HEADERS, body: JSON.stringify({ source: source, conflict: conflict }) }).then(function (data) {
|
|
586
|
+
inflightRef.current = false;
|
|
580
587
|
setBusy(null);
|
|
581
588
|
setImportResult(data);
|
|
582
589
|
setSelected(null);
|
|
583
590
|
setUploadOpen(false);
|
|
584
591
|
refresh();
|
|
585
592
|
}).catch(function (error) {
|
|
593
|
+
inflightRef.current = false;
|
|
586
594
|
setBusy(null);
|
|
595
|
+
setUploadOpen(false);
|
|
587
596
|
setImportResult({ error: error });
|
|
588
597
|
});
|
|
589
598
|
}
|
|
590
599
|
|
|
591
600
|
function installSelected() {
|
|
592
|
-
if (!selected || busy) return;
|
|
601
|
+
if (!selected || busy || inflightRef.current) return;
|
|
602
|
+
inflightRef.current = true;
|
|
593
603
|
setBusy("import-check");
|
|
594
604
|
callApi("/import", { method: "POST", headers: MUTATION_HEADERS, body: JSON.stringify({ source: selected.source, dryRun: true }) }).then(function (data) {
|
|
595
605
|
if (data.conflicts && data.conflicts.length) {
|
|
606
|
+
inflightRef.current = false;
|
|
596
607
|
setBusy(null);
|
|
597
608
|
setConfirmImport({ source: selected.source, conflicts: data.conflicts });
|
|
598
609
|
return;
|
|
599
610
|
}
|
|
600
611
|
executeImport(selected.source, "skip");
|
|
601
612
|
}).catch(function (error) {
|
|
613
|
+
inflightRef.current = false;
|
|
602
614
|
setBusy(null);
|
|
603
615
|
setImportResult({ error: error });
|
|
604
616
|
});
|
|
@@ -616,7 +628,7 @@ window.__ModuleLoader__.load({
|
|
|
616
628
|
h("span", { className: "dssm-tag " + (policyValid && enabled ? "dssm-tag-on" : "dssm-tag-off") }, policyValid ? (enabled ? t("status.enabled") : t("status.disabled")) : t("status.invalid"))),
|
|
617
629
|
h("div", { className: "dssm-note", title: skill.description || "" }, skill.description || t("note.missing"))),
|
|
618
630
|
root.mutable ? h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: function () { action(actionEnabled ? "/disable" : "/enable", { name: skill.name, root: root.key }); } }, actionEnabled ? t("btn.disable") : policyValid ? t("btn.enable") : t("btn.repair.enable")) : null,
|
|
619
|
-
root.mutable ? h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete(skill); } }, t("btn.delete")) : null);
|
|
631
|
+
root.mutable ? h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete({ name: skill.name, root: root.key }); } }, t("btn.delete")) : null);
|
|
620
632
|
}
|
|
621
633
|
|
|
622
634
|
var nodes = [h("style", { key: "css" }, CSS)];
|
|
@@ -692,7 +704,10 @@ window.__ModuleLoader__.load({
|
|
|
692
704
|
} else if (snapshot.loading) {
|
|
693
705
|
nodes.push(h("div", { key: "loading", className: "dssm-note" }, t("loading")));
|
|
694
706
|
}
|
|
695
|
-
|
|
707
|
+
function isUploadPickerError(result) {
|
|
708
|
+
return !!(result && typeof result.code === "string" && (result.code.indexOf("select.") === 0 || result.code.indexOf("dir.") === 0));
|
|
709
|
+
}
|
|
710
|
+
if (importResult && !uploadOpen && !isUploadPickerError(importResult)) {
|
|
696
711
|
var nameSeparator = t("sep.names");
|
|
697
712
|
var errorSeparator = t("sep.errors");
|
|
698
713
|
// 错误形态:业务/协议错误带 code,或 host 失败带 error(Error 对象);结果形态只有 imported/failed/skipped。
|
|
@@ -739,7 +754,7 @@ window.__ModuleLoader__.load({
|
|
|
739
754
|
nodes.push(h("div", { key: "import-confirm", className: "dssm-mask" }, h("div", { ref: modalRef, tabIndex: -1, className: "dssm-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "dssm-import-title" }, h("div", { className: "dssm-modal-head" }, h("h3", { id: "dssm-import-title", className: "dssm-modal-title" }, t("confirm.overwrite.title"))), h("p", { className: "dssm-desc" }, t("confirm.overwrite.desc", { names: confirmImport.conflicts.map(function (item) { return item.name; }).join(t("sep.names")) })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", onClick: function () { setConfirmImport(null); } }, t("btn.cancel")), h("button", { className: "dssm-btn", disabled: !!busy, onClick: function () { executeImport(confirmImport.source, "overwrite"); } }, t("btn.overwrite.upload"))))));
|
|
740
755
|
}
|
|
741
756
|
if (confirmDelete) {
|
|
742
|
-
nodes.push(h("div", { key: "delete-confirm", className: "dssm-mask" }, h("div", { ref: modalRef, tabIndex: -1, className: "dssm-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "dssm-delete-title" }, h("div", { className: "dssm-modal-head" }, h("h3", { id: "dssm-delete-title", className: "dssm-modal-title" }, t("confirm.delete.title"))), h("p", { className: "dssm-desc" }, t("confirm.delete.desc", { name: confirmDelete.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: function () { setConfirmDelete(null); } }, t("btn.cancel")), h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete(null); action("/delete", { name: confirmDelete.name }); } }, t("btn.delete.confirm"))))));
|
|
757
|
+
nodes.push(h("div", { key: "delete-confirm", className: "dssm-mask" }, h("div", { ref: modalRef, tabIndex: -1, className: "dssm-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "dssm-delete-title" }, h("div", { className: "dssm-modal-head" }, h("h3", { id: "dssm-delete-title", className: "dssm-modal-title" }, t("confirm.delete.title"))), h("p", { className: "dssm-desc" }, t("confirm.delete.desc", { name: confirmDelete.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: function () { setConfirmDelete(null); } }, t("btn.cancel")), h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete(null); action("/delete", { name: confirmDelete.name, root: confirmDelete.root || "dsh" }); } }, t("btn.delete.confirm"))))));
|
|
743
758
|
}
|
|
744
759
|
return h("section", { className: "dssm-section" }, nodes);
|
|
745
760
|
}
|
package/lib/core.js
CHANGED
|
@@ -18,6 +18,7 @@ import { randomUUID } from "node:crypto";
|
|
|
18
18
|
const KEBAB_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
19
19
|
const WINDOWS_DEVICE_NAME_RE = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
20
20
|
const MAX_SOURCE_DEPTH = 64;
|
|
21
|
+
const MAX_ENTRY_NAME_LENGTH = 128;
|
|
21
22
|
|
|
22
23
|
// ── 业务错误码 ────────────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -78,14 +79,38 @@ function isSameOrDescendant(parent, child) {
|
|
|
78
79
|
return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/** 解析真实路径;中间若有目录链接,按落地目录比较重叠。 */
|
|
83
|
+
async function resolvedPath(path) {
|
|
84
|
+
try {
|
|
85
|
+
return await fs.realpath(path);
|
|
86
|
+
} catch {
|
|
87
|
+
return resolve(path);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
/** 两个路径重叠时,覆盖导入可能删除自身来源,必须拒绝。 */
|
|
82
|
-
function pathsOverlap(a, b) {
|
|
83
|
-
|
|
92
|
+
async function pathsOverlap(a, b) {
|
|
93
|
+
const left = await resolvedPath(a);
|
|
94
|
+
const right = await resolvedPath(b);
|
|
95
|
+
return isSameOrDescendant(left, right) || isSameOrDescendant(right, left);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 真实落地路径必须仍在技能根内,避免符号链接写出根外。 */
|
|
99
|
+
async function isInsideRoot(root, path) {
|
|
100
|
+
return isSameOrDescendant(await resolvedPath(root), await resolvedPath(path));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function lstatOrNull(path) {
|
|
104
|
+
try {
|
|
105
|
+
return await fs.lstat(path);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
/** 名称只允许一个普通路径段;不把既有技能名称限制为 kebab-case。 */
|
|
87
112
|
export function entryPath(root, name) {
|
|
88
|
-
if (typeof name !== "string" || name === "" || name === "." || name === ".." || /[\\/:*?"<>|\0]/.test(name) || /[. ]$/.test(name) || WINDOWS_DEVICE_NAME_RE.test(name) || basename(name) !== name) return null;
|
|
113
|
+
if (typeof name !== "string" || name === "" || name === "." || name === ".." || name.startsWith(".") || name.length > MAX_ENTRY_NAME_LENGTH || /[\\/:*?"<>|\0]/.test(name) || /[. ]$/.test(name) || WINDOWS_DEVICE_NAME_RE.test(name) || basename(name) !== name) return null;
|
|
89
114
|
const rootPath = resolve(root);
|
|
90
115
|
const path = resolve(rootPath, name);
|
|
91
116
|
return isSameOrDescendant(rootPath, path) && rootPath !== path ? path : null;
|
|
@@ -122,7 +147,7 @@ function stripBom(text) {
|
|
|
122
147
|
export function parseSkillDoc(text) {
|
|
123
148
|
const src = stripBom(String(text));
|
|
124
149
|
const lines = src.split(/\r?\n/);
|
|
125
|
-
const map =
|
|
150
|
+
const map = Object.create(null);
|
|
126
151
|
const fields = [];
|
|
127
152
|
let body = src;
|
|
128
153
|
if (lines.length > 0 && lines[0].trim() === "---") {
|
|
@@ -223,17 +248,20 @@ export async function resolveEntry(root, name) {
|
|
|
223
248
|
const bundlePath = entryPath(root, name);
|
|
224
249
|
if (bundlePath === null) return null;
|
|
225
250
|
const rootPath = resolve(root);
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
const bundleStat = await lstatOrNull(bundlePath);
|
|
252
|
+
if (bundleStat && bundleStat.isDirectory() && !bundleStat.isSymbolicLink()) {
|
|
253
|
+
const bundleDoc = join(bundlePath, "SKILL.md");
|
|
254
|
+
const docStat = await lstatOrNull(bundleDoc);
|
|
255
|
+
if (docStat && docStat.isFile() && !docStat.isSymbolicLink() && await isInsideRoot(rootPath, bundleDoc)) {
|
|
256
|
+
return { kind: "bundle", docPath: bundleDoc, entryPath: bundlePath };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
231
259
|
const flatDoc = resolve(rootPath, `${name}.md`);
|
|
232
260
|
if (!isSameOrDescendant(rootPath, flatDoc) || rootPath === flatDoc) return null;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
261
|
+
const flatStat = await lstatOrNull(flatDoc);
|
|
262
|
+
if (flatStat && flatStat.isFile() && !flatStat.isSymbolicLink() && await isInsideRoot(rootPath, flatDoc)) {
|
|
263
|
+
return { kind: "flat", docPath: flatDoc, entryPath: flatDoc };
|
|
264
|
+
}
|
|
237
265
|
return null;
|
|
238
266
|
}
|
|
239
267
|
|
|
@@ -263,32 +291,36 @@ export async function scanEntries(root) {
|
|
|
263
291
|
} catch {
|
|
264
292
|
return { exists: false, entries: [] };
|
|
265
293
|
}
|
|
266
|
-
const
|
|
294
|
+
const byName = new Map();
|
|
267
295
|
for (const it of items) {
|
|
268
296
|
try {
|
|
297
|
+
if (it.isSymbolicLink()) continue;
|
|
269
298
|
if (it.isDirectory() && entryPath(root, it.name) !== null) {
|
|
270
299
|
const docPath = join(root, it.name, "SKILL.md");
|
|
271
|
-
const st = await
|
|
272
|
-
if (!st.isFile()) continue;
|
|
300
|
+
const st = await lstatOrNull(docPath);
|
|
301
|
+
if (!st || !st.isFile() || st.isSymbolicLink() || !(await isInsideRoot(root, docPath))) continue;
|
|
273
302
|
const doc = parseSkillDoc(await fs.readFile(docPath, "utf8"));
|
|
274
|
-
|
|
303
|
+
byName.set(it.name, entryOf(it.name, "bundle", docPath, doc));
|
|
275
304
|
} else if (it.isFile() && it.name.toLowerCase().endsWith(".md") && it.name.toLowerCase() !== "skill.md" && entryPath(root, it.name.slice(0, -3)) !== null) {
|
|
305
|
+
const skillName = it.name.slice(0, -3);
|
|
306
|
+
if (byName.has(skillName)) continue;
|
|
276
307
|
const docPath = join(root, it.name);
|
|
308
|
+
if (!(await isInsideRoot(root, docPath))) continue;
|
|
277
309
|
const doc = parseSkillDoc(await fs.readFile(docPath, "utf8"));
|
|
278
|
-
|
|
310
|
+
byName.set(skillName, entryOf(skillName, "flat", docPath, doc));
|
|
279
311
|
}
|
|
280
312
|
} catch {
|
|
281
313
|
/* 跳过不可读条目 */
|
|
282
314
|
}
|
|
283
315
|
}
|
|
284
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
316
|
+
const entries = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
285
317
|
return { exists: true, entries };
|
|
286
318
|
}
|
|
287
319
|
|
|
288
320
|
// ── 启用 / 停用(同时控制模型与 / 手动调用,非破坏)──────────────────────────
|
|
289
321
|
|
|
290
322
|
/** enabled=true 恢复模型与 / 手动调用;false 同时停用两种调用入口。 */
|
|
291
|
-
export async function setSkillEnabled(root, name, enabled) {
|
|
323
|
+
export async function setSkillEnabled(root, name, enabled, log) {
|
|
292
324
|
if (!isDshRoot(root)) return readonlyError("toggle");
|
|
293
325
|
const resolved = await resolveEntry(root, name);
|
|
294
326
|
if (resolved === null) return { ok: false, error: `技能不存在: ${name}`, code: "error.skill.notFound", params: { name } };
|
|
@@ -296,6 +328,7 @@ export async function setSkillEnabled(root, name, enabled) {
|
|
|
296
328
|
const updated = updateInvocationPolicy(source, enabled);
|
|
297
329
|
if (updated === null) return { ok: false, error: `技能缺少完整 frontmatter,无法${enabled ? "启用" : "停用"}: ${name}`, code: "error.skill.noFrontmatter", params: { name, action: enabled ? "enable" : "disable" } };
|
|
298
330
|
await writeFileAtomically(resolved.docPath, updated);
|
|
331
|
+
if (log) log(enabled ? "enable" : "disable", `${enabled ? "启用" : "停用"} ${resolved.docPath}`);
|
|
299
332
|
return { name, enabled };
|
|
300
333
|
}
|
|
301
334
|
|
|
@@ -466,7 +499,7 @@ export async function importSkill(source, log, options = {}) {
|
|
|
466
499
|
|
|
467
500
|
const analysis = await analyzeSource(source);
|
|
468
501
|
if (analysis.kind === "none") return { ok: false, error: analysis.error || "无法识别的 skill 来源", code: analysis.code || "error.source.unrecognized", params: analysis.params };
|
|
469
|
-
if (pathsOverlap(analysis.source, targetRoot)) return { ok: false, error: "导入来源不能与 DSH 技能目录相同、包含或位于其中", code: "error.import.overlap" };
|
|
502
|
+
if (await pathsOverlap(analysis.source, targetRoot)) return { ok: false, error: "导入来源不能与 DSH 技能目录相同、包含或位于其中", code: "error.import.overlap" };
|
|
470
503
|
|
|
471
504
|
let candidates = [];
|
|
472
505
|
if (analysis.kind === "single") {
|
|
@@ -536,7 +569,9 @@ export async function importSkill(source, log, options = {}) {
|
|
|
536
569
|
return { kind: analysis.kind, pending, conflicts, failed };
|
|
537
570
|
}
|
|
538
571
|
|
|
539
|
-
|
|
572
|
+
if (pending.length > 0 || (conflict === "overwrite" && conflicts.length > 0)) {
|
|
573
|
+
await fs.mkdir(targetRoot, { recursive: true });
|
|
574
|
+
}
|
|
540
575
|
|
|
541
576
|
for (const p of pending) {
|
|
542
577
|
try {
|
package/lib/index.js
CHANGED
|
@@ -91,12 +91,18 @@ function validateMutationRequest(req) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function json(res, code, payload) {
|
|
94
|
-
|
|
95
|
-
res.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
if (!res || res.writableEnded || res.destroyed) return;
|
|
95
|
+
res.once("error", () => {});
|
|
96
|
+
try {
|
|
97
|
+
const body = JSON.stringify(payload);
|
|
98
|
+
res.writeHead(code, {
|
|
99
|
+
"content-type": "application/json; charset=utf-8",
|
|
100
|
+
"content-length": Buffer.byteLength(body),
|
|
101
|
+
});
|
|
102
|
+
res.end(body);
|
|
103
|
+
} catch {
|
|
104
|
+
/* 客户端已断开 */
|
|
105
|
+
}
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/** 成功统一包成 { ok: true, data };核心返回 { ok:false, error } 时透传为 400。 */
|
|
@@ -190,6 +196,15 @@ function apply(ctx) {
|
|
|
190
196
|
if (req.method === "GET" && path === "/api/dsh-skills-manager/state") {
|
|
191
197
|
return run(res, state);
|
|
192
198
|
}
|
|
199
|
+
if (req.method === "HEAD") {
|
|
200
|
+
res.writeHead(path === "/api/dsh-skills-manager/state" ? 200 : 405, {
|
|
201
|
+
"content-type": "application/json; charset=utf-8",
|
|
202
|
+
"content-length": 0,
|
|
203
|
+
allow: "GET, HEAD, POST",
|
|
204
|
+
});
|
|
205
|
+
res.end();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
193
208
|
if (req.method !== "POST") {
|
|
194
209
|
json(res, 405, { ok: false, code: "error.proto.method", error: `method not allowed: ${req.method}` });
|
|
195
210
|
return;
|
|
@@ -203,11 +218,11 @@ function apply(ctx) {
|
|
|
203
218
|
return enqueueMutation(() => {
|
|
204
219
|
switch (path) {
|
|
205
220
|
case "/api/dsh-skills-manager/enable":
|
|
206
|
-
return run(res, () => setSkillEnabled(rootByKey[String(body.root || "dsh")], String(body.name || ""), true), afterWrite);
|
|
221
|
+
return run(res, () => setSkillEnabled(rootByKey[String(body.root || "dsh")], String(body.name || ""), true, log), afterWrite);
|
|
207
222
|
case "/api/dsh-skills-manager/disable":
|
|
208
|
-
return run(res, () => setSkillEnabled(rootByKey[String(body.root || "dsh")], String(body.name || ""), false), afterWrite);
|
|
223
|
+
return run(res, () => setSkillEnabled(rootByKey[String(body.root || "dsh")], String(body.name || ""), false, log), afterWrite);
|
|
209
224
|
case "/api/dsh-skills-manager/delete":
|
|
210
|
-
return run(res, () => deleteSkill(rootByKey.dsh, String(body.name || ""), log), afterWrite);
|
|
225
|
+
return run(res, () => deleteSkill(rootByKey[String(body.root || "dsh")], String(body.name || ""), log), afterWrite);
|
|
211
226
|
case "/api/dsh-skills-manager/import":
|
|
212
227
|
return run(res, () => importSkill(String(body.source || ""), log, {
|
|
213
228
|
conflict: body.conflict === "overwrite" ? "overwrite" : "skip",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-skills-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "NPM-installable DSH Web plugin for managing local skills and viewing shared Agent skills safely.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.0 <0.2.0",
|
|
52
52
|
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.0 <0.2.0",
|
|
53
53
|
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.0 <0.2.0",
|
|
54
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.5",
|
|
55
|
-
"@deepseek-ai/dsh-skill": ">=0.1.0-rc.5",
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-workspace": ">=0.1.0-rc.5",
|
|
54
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.5 <0.2.0",
|
|
55
|
+
"@deepseek-ai/dsh-skill": ">=0.1.0-rc.5 <0.2.0",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-workspace": ">=0.1.0-rc.5 <0.2.0",
|
|
57
57
|
"react": "^18.2.0"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|