@morehao/dsh-skills-console 0.2.1 → 0.2.3
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 +18 -39
- package/client.js +195 -17
- package/lib/core/cli.js +3 -1
- package/lib/core/git.js +282 -0
- package/lib/core/links.js +1 -1
- package/lib/core/service.js +620 -87
- package/lib/core/updates.js +100 -12
- package/lib/routes.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ DSH(DeepSeek Harness)的**技能控制台**:装、更新、删除、接入
|
|
|
29
29
|
| 接入 | 软链四态判定(`managed` / `managed-external` / `foreign-link` / `real`),**绝不覆盖**别人创建的东西;只列本机已安装目录,已删的 Agent 自动从列表消失 |
|
|
30
30
|
| 启停 | `mode: on/off` 只摘或重建 DSH 侧链接,不动 `SKILL.md`,其他 Agent 的链接也不受影响 |
|
|
31
31
|
| 作用范围 | 全局 `~/.dsh/skills`,或仅指定项目;项目可直接勾选 DSH 已登记的工作区,也可手动选目录 |
|
|
32
|
-
| 订阅 | 关注 `owner/repo`(可指定 ref /
|
|
32
|
+
| 订阅 | 关注 `owner/repo`(可指定 ref / 子目录;ref 缺省时跟随仓库默认分支,`master` 仓库也能用),只记快照、不装技能;上游读不到快照时直接报错,不会留下空订阅 |
|
|
33
33
|
|
|
34
34
|
## 界面
|
|
35
35
|
|
|
@@ -57,18 +57,16 @@ plugin_manager install_bundle(target = <本仓库绝对路径>)
|
|
|
57
57
|
|
|
58
58
|
客户端 half 只注册 `platform: web`,面板出现在 DSH Web 的设置页 / 侧栏。
|
|
59
59
|
|
|
60
|
-
> 本机可能还装着 `@michengai/dsh-skills-manager`(显示名「技能管理」)。两者互不干扰(路由前缀 `/api/morehao/skills-console/*`、状态目录 `$DSH_HOME/skills-console/` 都不同),但会各出一个设置分区;只想留一个时把另一个 `plugin_manager set_plugin enabled:false` 即可。
|
|
61
|
-
|
|
62
60
|
## 快速上手
|
|
63
61
|
|
|
64
|
-
1. **添加技能**:点右上角「添加技能」,填 `owner/repo
|
|
62
|
+
1. **添加技能**:点右上角「添加技能」,填 `owner/repo` 或 GitHub URL,点「检测技能」;仓库里有多个技能时勾选要装的(支持多选,默认全选,列表带每个技能的说明),只有一个会默认勾上。检测不到时可以直接手填技能名,留空则装仓库内全部。
|
|
65
63
|
2. **接到别的 Agent**:点行内「接入设置」,勾选目标 Agent;需要限定项目时改选「仅以下项目」并勾工作区。之后新装技能默认接哪些 Agent,用它旁边的「自动接入」设定。
|
|
66
64
|
3. **检查更新**:点列表头上的刷新图标;有更新的行会亮起「更新」,也可以一次「全部更新」。本地改过的技能会先要求确认。
|
|
67
65
|
4. **临时停用**:关掉行内开关。DSH 看不到它,但文件和其他 Agent 的接入都还在;再打开就恢复。
|
|
68
66
|
|
|
69
|
-
##
|
|
67
|
+
## 可靠性
|
|
70
68
|
|
|
71
|
-
- **canonical 与 CLI lock 只读**:`~/.agents/skills`、`~/.agents/.skill-lock.json` 的增删改只经 `skills` CLI
|
|
69
|
+
- **canonical 与 CLI lock 只读**:`~/.agents/skills`、`~/.agents/.skill-lock.json` 的增删改只经 `skills` CLI,插件不直接写;探测 / 更新检查会把仓库临时 clone 到 `os.tmpdir()`(只读、用完即删),结果绝不落进 canonical 或 lock。
|
|
72
70
|
- **只动自己的东西**:只处理本插件创建的 `managed` 链接,以及已记账的 `managed-external`;`real` 目录和 `foreign-link` 永不改写,删除前体检直接中止。
|
|
73
71
|
- **启停不改正文**:`mode` 只增删 DSH 侧链接,避免本地修改被下一次 `add` 覆盖。
|
|
74
72
|
- **可回滚**:删除进回收站(7 天);更新前快照到 `backups/`,失败自动还原(接口另提供手动回滚)。
|
|
@@ -86,45 +84,26 @@ plugin_manager install_bundle(target = <本仓库绝对路径>)
|
|
|
86
84
|
| 备份 / 回收站 | `$DSH_HOME/skills-console/{backups,trash}` | 本插件 |
|
|
87
85
|
| 事件日志 | `$DSH_HOME/skills-console/dsh-skills-console.log` | 本插件(JSONL,1MB 滚动) |
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
## 开发
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
npm install # 只装构建期依赖(esbuild);运行时依赖仍为零
|
|
95
|
-
npm run build:client # 从 client/ 生成根 client.js(生成物要提交,不要手改)
|
|
96
|
-
npm run dev:client # 监听 client/ 自动重建(配合 link: 接入,改完刷新即可)
|
|
97
|
-
npm test # node --test,80 个离线用例,临时 HOME / DSH_HOME,不碰真实目录
|
|
98
|
-
npm run check # 语法自检 + 生成物一致性 + 命名/导出接线 + 打包白名单 + Release 说明投影冒烟
|
|
99
|
-
npm run sync:agents -- <skills/dist/cli.mjs> # 重新导出 agent 目录表(默认写回 lib/agent-table.json)
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Web Client 的源码在 `client/`(`host` / `copy` / `styles` / `api` / `icons` / `hooks` / `actions` / `views/*` / `section` / `surface` / `app`),仓根 `client.js` 是 `npm run build:client` 的产物;只改产物不改源码,`npm run check` 会直接红。
|
|
103
|
-
|
|
104
|
-
目录结构、测试策略与调试手段见 [docs/development.md](docs/development.md);在本仓工作的 AI agent 请先读 [AGENTS.md](AGENTS.md)。
|
|
87
|
+
### 环境变量
|
|
105
88
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
89
|
+
| 变量 | 默认 | 作用 |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `DSH_SKILLS_CONSOLE_HOME` | `$DSH_HOME/skills-console` | 状态、备份、回收站与事件日志的根 |
|
|
92
|
+
| `DSH_SKILLS_CONSOLE_CLI` | 自动定位 | 覆盖 `skills` CLI(测试桩也走这里) |
|
|
93
|
+
| `DSH_SKILLS_CONSOLE_NPX_SPEC` | `skills` | 需要 `npx` 兜底时用的包名 / 版本 |
|
|
94
|
+
| `DSH_SKILLS_CONSOLE_TIMEOUT_MS` | `120000` | 单次 CLI 调用超时 |
|
|
95
|
+
| `DSH_SKILLS_CONSOLE_GIT` | `auto` | 设为 `off` 关闭 git 通道,强制只走 GitHub REST |
|
|
96
|
+
| `DSH_SKILLS_CONSOLE_GIT_TIMEOUT_MS` | `60000` | git 单次操作(`ls-remote` / `clone`)超时 |
|
|
97
|
+
| `GITHUB_TOKEN` / `GH_TOKEN` | 无 | 只读环境变量;带上后 REST 配额从匿名 60 次/小时提到 5000 次/小时 |
|
|
116
98
|
|
|
117
|
-
|
|
99
|
+
关于 GitHub 配额:**匿名 REST 是 60 次/小时,按出口 IP 共享**(公司、校园网出口常被同网段的人一起打满),打满后探测与更新会收到 403。本插件默认让探测、更新检查与 git 兜底走系统 `git`(`ls-remote` + `clone --filter=blob:none --no-checkout`),不占 REST 配额、也不需要 token;git 不可用时才回退 REST,所以原本能用的环境不会因为这条路径变坏。限流且 git 也不可用时,错误文案会给出恢复时间,并提示设置 `GITHUB_TOKEN`。
|
|
118
100
|
|
|
119
|
-
|
|
101
|
+
> git 通道只在 `os.tmpdir()` 里读一份临时仓库,用完即删 —— 不写 canonical、不写 CLI lock,落盘始终只交给 `skills` CLI。
|
|
120
102
|
|
|
121
|
-
`v0.2.0` 起包名、插件 id、路由前缀、状态目录与环境变量统一改为 `skills-console`,**不提供兼容读旧值**。保住旧账本(订阅、回收站、链接记录、版本缓存)只需搬一次目录:
|
|
122
103
|
|
|
123
|
-
|
|
124
|
-
mv ~/.dsh/skills-manager ~/.dsh/skills-console
|
|
125
|
-
```
|
|
104
|
+
## 开发与发布
|
|
126
105
|
|
|
127
|
-
|
|
106
|
+
源码结构、测试策略与调试手段见 [docs/development.md](docs/development.md)(含本地 `link:` 验证与回退步骤);发布流程与排障清单见 [docs/publishing.md](docs/publishing.md)。在本仓工作的 AI agent 请先读 [AGENTS.md](AGENTS.md)。
|
|
128
107
|
|
|
129
108
|
## 边界(明确不做)
|
|
130
109
|
|
package/client.js
CHANGED
|
@@ -148,11 +148,23 @@ var DICT = {
|
|
|
148
148
|
"banner.visible": "模型可见技能与本地账本不一致,差异已列在下方",
|
|
149
149
|
"form.source": "来源(owner/repo 或 GitHub URL)",
|
|
150
150
|
"form.sourceHint": "例如 morehao/ai-kit",
|
|
151
|
+
"form.probe": "检测技能",
|
|
152
|
+
"form.probing": "读取中…",
|
|
153
|
+
"form.probingHint": "正在读仓库的文件树,稍等一下…",
|
|
154
|
+
"form.detected": "检测到 {n} 个技能,勾选要安装的",
|
|
155
|
+
"form.skipped": "另有 {n} 个 SKILL.md 缺少 name/description,CLI 无法安装,已跳过",
|
|
156
|
+
"form.selectAll": "全选",
|
|
157
|
+
"form.selectNone": "全不选",
|
|
158
|
+
"form.pickHint": "先填来源并检测;仓库里有多个技能时可以多选,只有一个会默认勾上。",
|
|
159
|
+
"form.noSkills": "仓库里没找到 SKILL.md,可以直接填技能名安装",
|
|
160
|
+
"form.probeFailed": "检测失败,仍可手动填技能名",
|
|
151
161
|
"form.skills": "技能名(多个用逗号分隔)",
|
|
152
162
|
"form.ref": "ref(分支 / tag,可空)",
|
|
163
|
+
"form.refHint": "留空则自动跟随仓库默认分支",
|
|
153
164
|
"form.subpath": "子目录(可空)",
|
|
154
165
|
"form.hint": "会立即下载并写入 ~/.agents/skills,并按「自动接入」设置接到对应 Agent。",
|
|
155
166
|
"form.subHint": "只记录你关注这个仓库,不会安装任何技能。",
|
|
167
|
+
"subscription.defaultRef": "默认分支",
|
|
156
168
|
"confirm.deleteTitle": "删除技能",
|
|
157
169
|
"confirm.deleteBody": "会从本机移除,断开本插件建立的软链并调 skills CLI 卸载;目录先备份到回收站(保留 7 天)。不会删除你的源仓库。",
|
|
158
170
|
"confirm.updateTitle": "本地有改动",
|
|
@@ -266,11 +278,23 @@ var DICT = {
|
|
|
266
278
|
"banner.visible": "Model-visible skills differ from the local ledger",
|
|
267
279
|
"form.source": "Source (owner/repo or GitHub URL)",
|
|
268
280
|
"form.sourceHint": "e.g. morehao/ai-kit",
|
|
281
|
+
"form.probe": "Detect",
|
|
282
|
+
"form.probing": "Reading…",
|
|
283
|
+
"form.probingHint": "Reading the repository tree…",
|
|
284
|
+
"form.detected": "Found {n} skills; pick the ones to install",
|
|
285
|
+
"form.skipped": "{n} SKILL.md file(s) skipped: missing name/description",
|
|
286
|
+
"form.selectAll": "Select all",
|
|
287
|
+
"form.selectNone": "Clear",
|
|
288
|
+
"form.pickHint": "Enter a source and detect first. Pick one or more when the repo has several.",
|
|
289
|
+
"form.noSkills": "No SKILL.md found; type skill names instead",
|
|
290
|
+
"form.probeFailed": "Detection failed; you can still type names",
|
|
269
291
|
"form.skills": "Skill names (comma separated)",
|
|
270
292
|
"form.ref": "ref (branch / tag, optional)",
|
|
293
|
+
"form.refHint": "empty follows the repository default branch",
|
|
271
294
|
"form.subpath": "Subdirectory (optional)",
|
|
272
295
|
"form.hint": "Downloads into ~/.agents/skills and links agents per the auto-link setting.",
|
|
273
296
|
"form.subHint": "Only records the repository; installs nothing.",
|
|
297
|
+
"subscription.defaultRef": "default branch",
|
|
274
298
|
"confirm.deleteTitle": "Delete skill",
|
|
275
299
|
"confirm.deleteBody": "Removes it locally, disconnects this plugin’s links and calls skills remove. A snapshot stays in trash for 7 days. Your source repo is untouched.",
|
|
276
300
|
"confirm.updateTitle": "Local changes",
|
|
@@ -888,6 +912,10 @@ var CSS = `
|
|
|
888
912
|
border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); outline: none; }
|
|
889
913
|
.dshsc-field input:focus { border-color: var(--dsw-alias-brand-primary);
|
|
890
914
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent); }
|
|
915
|
+
/* 来源输入 + 检测按钮同一行;输入框吃掉剩余宽度,按钮不被挤压 */
|
|
916
|
+
.dshsc-inline { display: flex; gap: 6px; align-items: stretch; }
|
|
917
|
+
.dshsc-inline input { flex: 1; min-width: 0; }
|
|
918
|
+
.dshsc-inline .dshsc-btn { flex: none; }
|
|
891
919
|
|
|
892
920
|
.dshsc-check { display: flex; align-items: center; gap: 9px; font-size: 13px; padding: 5px 8px; border-radius: 8px; cursor: pointer; }
|
|
893
921
|
.dshsc-check:hover { background: var(--dsw-alias-bg-layer-1); }
|
|
@@ -905,6 +933,14 @@ var CSS = `
|
|
|
905
933
|
.dshsc-check input:disabled { opacity: .55; cursor: not-allowed; }
|
|
906
934
|
.dshsc-check small { color: var(--dsw-alias-label-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
907
935
|
.dshsc-check span { white-space: nowrap; }
|
|
936
|
+
/* 添加技能:每个技能一行,名字 + 两行描述 + 路径;描述太长时截断,不把弹窗撑高 */
|
|
937
|
+
.dshsc-skill { align-items: flex-start; }
|
|
938
|
+
.dshsc-skill input[type="checkbox"] { margin-top: 3px; }
|
|
939
|
+
.dshsc-skill-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
|
|
940
|
+
.dshsc-skill-name { font-weight: 600; }
|
|
941
|
+
.dshsc-check .dshsc-skill-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
942
|
+
overflow: hidden; white-space: normal; line-height: 1.4; }
|
|
943
|
+
.dshsc-check .dshsc-skill-path { font-size: 11px; opacity: .75; }
|
|
908
944
|
|
|
909
945
|
.dshsc-panel { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-1);
|
|
910
946
|
padding: 4px; max-height: 300px; overflow: auto; display: flex; flex-direction: column; }
|
|
@@ -1009,11 +1045,13 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1009
1045
|
function requestAddSubscription() {
|
|
1010
1046
|
setModal({ type: "add-subscription" });
|
|
1011
1047
|
}
|
|
1012
|
-
async function
|
|
1013
|
-
|
|
1048
|
+
async function probeSkills(source) {
|
|
1049
|
+
return apiCall("POST", "/probe", { source: String(source || "").trim() });
|
|
1050
|
+
}
|
|
1051
|
+
async function installSkills({ source, skills }) {
|
|
1014
1052
|
setModal(null);
|
|
1015
1053
|
await run("install", async () => {
|
|
1016
|
-
const result = await apiCall("POST", "/install", { source:
|
|
1054
|
+
const result = await apiCall("POST", "/install", { source: String(source || "").trim(), skills });
|
|
1017
1055
|
const warnings = result.warnings?.length || 0;
|
|
1018
1056
|
setToast({
|
|
1019
1057
|
kind: warnings ? "err" : "ok",
|
|
@@ -1060,7 +1098,8 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1060
1098
|
saveLinks,
|
|
1061
1099
|
requestAddSkill,
|
|
1062
1100
|
requestAddSubscription,
|
|
1063
|
-
|
|
1101
|
+
probeSkills,
|
|
1102
|
+
installSkills,
|
|
1064
1103
|
addSubscription,
|
|
1065
1104
|
refreshSubscription,
|
|
1066
1105
|
deleteSubscription,
|
|
@@ -1768,7 +1807,7 @@ function SubscriptionsTab({
|
|
|
1768
1807
|
onClick: () => onToggle(subscription.id)
|
|
1769
1808
|
}, h(CaretIcon, { open })),
|
|
1770
1809
|
h("span", { className: "dshsc-name" }, subscription.source),
|
|
1771
|
-
h("span", { className: "dshsc-tag" }, subscription.ref || "
|
|
1810
|
+
h("span", { className: "dshsc-tag" }, subscription.ref || t("subscription.defaultRef")),
|
|
1772
1811
|
h(
|
|
1773
1812
|
"span",
|
|
1774
1813
|
{ className: "dshsc-substat" },
|
|
@@ -1848,6 +1887,9 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
1848
1887
|
}
|
|
1849
1888
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
1850
1889
|
const [form, setForm] = import_react3.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
1890
|
+
const incomplete = fields.some(
|
|
1891
|
+
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
1892
|
+
);
|
|
1851
1893
|
return h(
|
|
1852
1894
|
Overlay,
|
|
1853
1895
|
{
|
|
@@ -1859,7 +1901,7 @@ function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
|
1859
1901
|
h("button", {
|
|
1860
1902
|
key: "s",
|
|
1861
1903
|
className: "dshsc-btn primary",
|
|
1862
|
-
disabled:
|
|
1904
|
+
disabled: incomplete,
|
|
1863
1905
|
onClick: () => onSubmit(form)
|
|
1864
1906
|
}, t("action.save"))
|
|
1865
1907
|
]
|
|
@@ -2029,18 +2071,153 @@ function AutoLinkModal({ t, settings, agents, onCancel, onSave }) {
|
|
|
2029
2071
|
h("p", { className: "dshsc-sub" }, t("autoLink.hint"))
|
|
2030
2072
|
);
|
|
2031
2073
|
}
|
|
2074
|
+
function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
2075
|
+
const [source, setSource] = import_react3.default.useState("");
|
|
2076
|
+
const [phase, setPhase] = import_react3.default.useState("idle");
|
|
2077
|
+
const [options, setOptions] = import_react3.default.useState([]);
|
|
2078
|
+
const [selected, setSelected] = import_react3.default.useState(() => /* @__PURE__ */ new Set());
|
|
2079
|
+
const [manual, setManual] = import_react3.default.useState("");
|
|
2080
|
+
const [skipped, setSkipped] = import_react3.default.useState(0);
|
|
2081
|
+
const [error, setError] = import_react3.default.useState(null);
|
|
2082
|
+
const reset = (value) => {
|
|
2083
|
+
setSource(value);
|
|
2084
|
+
setPhase("idle");
|
|
2085
|
+
setOptions([]);
|
|
2086
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
2087
|
+
setSkipped(0);
|
|
2088
|
+
setError(null);
|
|
2089
|
+
};
|
|
2090
|
+
const probe = async () => {
|
|
2091
|
+
const value = source.trim();
|
|
2092
|
+
if (!value || phase === "probing") return;
|
|
2093
|
+
setPhase("probing");
|
|
2094
|
+
setError(null);
|
|
2095
|
+
try {
|
|
2096
|
+
const result = await onProbe(value);
|
|
2097
|
+
const found = Array.isArray(result?.skills) ? result.skills : [];
|
|
2098
|
+
setOptions(found);
|
|
2099
|
+
setSelected(new Set(found.map((item) => item.name)));
|
|
2100
|
+
setSkipped(Math.max(0, Number(result?.skipped) || 0));
|
|
2101
|
+
setPhase(found.length > 0 ? "picked" : "empty");
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
setError(err?.message || String(err));
|
|
2104
|
+
setPhase("failed");
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
const toggle = (name) => setSelected((prev) => {
|
|
2108
|
+
const next = new Set(prev);
|
|
2109
|
+
if (next.has(name)) next.delete(name);
|
|
2110
|
+
else next.add(name);
|
|
2111
|
+
return next;
|
|
2112
|
+
});
|
|
2113
|
+
const names = manual.split(/[,,\s]+/).map((item) => item.trim()).filter(Boolean);
|
|
2114
|
+
const skippedText = skipped > 0 ? t("form.skipped").replace("{n}", String(skipped)) : null;
|
|
2115
|
+
const manualMode = phase === "empty" || phase === "failed";
|
|
2116
|
+
const skills = phase === "picked" ? [...selected] : names;
|
|
2117
|
+
const canSubmit = phase === "picked" ? selected.size > 0 : manualMode ? names.length > 0 : false;
|
|
2118
|
+
const submit = () => {
|
|
2119
|
+
if (!canSubmit) return;
|
|
2120
|
+
onInstall({ source: source.trim(), skills });
|
|
2121
|
+
};
|
|
2122
|
+
return h(
|
|
2123
|
+
Overlay,
|
|
2124
|
+
{
|
|
2125
|
+
t,
|
|
2126
|
+
title: t("action.addSkill"),
|
|
2127
|
+
onCancel,
|
|
2128
|
+
footer: [
|
|
2129
|
+
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2130
|
+
h("button", {
|
|
2131
|
+
key: "s",
|
|
2132
|
+
className: "dshsc-btn primary",
|
|
2133
|
+
disabled: !canSubmit,
|
|
2134
|
+
onClick: submit
|
|
2135
|
+
}, t("action.save"))
|
|
2136
|
+
]
|
|
2137
|
+
},
|
|
2138
|
+
h("p", { className: "dshsc-sub" }, t("form.pickHint")),
|
|
2139
|
+
h(
|
|
2140
|
+
"div",
|
|
2141
|
+
{ className: "dshsc-field" },
|
|
2142
|
+
h("span", null, t("form.source")),
|
|
2143
|
+
h(
|
|
2144
|
+
"div",
|
|
2145
|
+
{ className: "dshsc-inline" },
|
|
2146
|
+
h("input", {
|
|
2147
|
+
value: source,
|
|
2148
|
+
placeholder: t("form.sourceHint"),
|
|
2149
|
+
onChange: (event) => reset(event.target.value),
|
|
2150
|
+
onKeyDown: (event) => {
|
|
2151
|
+
if (event.key === "Enter") void probe();
|
|
2152
|
+
}
|
|
2153
|
+
}),
|
|
2154
|
+
h("button", {
|
|
2155
|
+
className: "dshsc-btn",
|
|
2156
|
+
disabled: !source.trim() || phase === "probing",
|
|
2157
|
+
onClick: () => void probe()
|
|
2158
|
+
}, phase === "probing" ? t("form.probing") : t("form.probe"))
|
|
2159
|
+
)
|
|
2160
|
+
),
|
|
2161
|
+
phase === "picked" ? h(
|
|
2162
|
+
"div",
|
|
2163
|
+
{ key: "picked" },
|
|
2164
|
+
h("div", { className: "dshsc-group" }, t("form.detected").replace("{n}", String(options.length))),
|
|
2165
|
+
h(
|
|
2166
|
+
"div",
|
|
2167
|
+
{ style: { display: "flex", gap: 6, marginBottom: 6 } },
|
|
2168
|
+
h("button", {
|
|
2169
|
+
className: "dshsc-btn ghost",
|
|
2170
|
+
onClick: () => setSelected(new Set(options.map((item) => item.name)))
|
|
2171
|
+
}, t("form.selectAll")),
|
|
2172
|
+
h("button", {
|
|
2173
|
+
className: "dshsc-btn ghost",
|
|
2174
|
+
onClick: () => setSelected(/* @__PURE__ */ new Set())
|
|
2175
|
+
}, t("form.selectNone"))
|
|
2176
|
+
),
|
|
2177
|
+
h("div", { className: "dshsc-panel" }, options.map((option) => h(
|
|
2178
|
+
"label",
|
|
2179
|
+
{ className: "dshsc-check dshsc-skill", key: option.name },
|
|
2180
|
+
h("input", {
|
|
2181
|
+
type: "checkbox",
|
|
2182
|
+
checked: selected.has(option.name),
|
|
2183
|
+
onChange: () => toggle(option.name)
|
|
2184
|
+
}),
|
|
2185
|
+
h(
|
|
2186
|
+
"span",
|
|
2187
|
+
{ className: "dshsc-skill-main" },
|
|
2188
|
+
h("span", { className: "dshsc-skill-name" }, option.name),
|
|
2189
|
+
option.description ? h("small", { className: "dshsc-skill-desc" }, option.description) : null,
|
|
2190
|
+
h("small", { className: "dshsc-skill-path" }, option.skillPath)
|
|
2191
|
+
)
|
|
2192
|
+
))),
|
|
2193
|
+
skippedText ? h("p", { className: "dshsc-sub" }, skippedText) : null
|
|
2194
|
+
) : null,
|
|
2195
|
+
phase === "probing" ? h("p", { className: "dshsc-sub" }, t("form.probingHint")) : null,
|
|
2196
|
+
manualMode ? h(
|
|
2197
|
+
"div",
|
|
2198
|
+
{ key: "manual" },
|
|
2199
|
+
h("p", { className: "dshsc-sub" }, phase === "empty" ? skippedText || t("form.noSkills") : `${t("form.probeFailed")}${error ? ` · ${error}` : ""}`),
|
|
2200
|
+
h(
|
|
2201
|
+
"label",
|
|
2202
|
+
{ className: "dshsc-field" },
|
|
2203
|
+
h("span", null, t("form.skills")),
|
|
2204
|
+
h("input", {
|
|
2205
|
+
value: manual,
|
|
2206
|
+
placeholder: "git-kit, handoff",
|
|
2207
|
+
onChange: (event) => setManual(event.target.value)
|
|
2208
|
+
})
|
|
2209
|
+
)
|
|
2210
|
+
) : null,
|
|
2211
|
+
h("p", { className: "dshsc-sub" }, t("form.hint"))
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2032
2214
|
function Modals({ t, modal, data, autoLinkAgents, pickDirectory, actions, onClose, onSaveAutoLink }) {
|
|
2033
2215
|
if (modal.type === "add-skill") {
|
|
2034
|
-
return h(
|
|
2216
|
+
return h(AddSkillModal, {
|
|
2035
2217
|
t,
|
|
2036
|
-
title: t("action.addSkill"),
|
|
2037
|
-
hint: t("form.hint"),
|
|
2038
|
-
fields: [
|
|
2039
|
-
{ key: "source", label: t("form.source"), placeholder: t("form.sourceHint") },
|
|
2040
|
-
{ key: "skills", label: t("form.skills"), placeholder: "git-kit, handoff" }
|
|
2041
|
-
],
|
|
2042
2218
|
onCancel: onClose,
|
|
2043
|
-
|
|
2219
|
+
onProbe: actions.probeSkills,
|
|
2220
|
+
onInstall: actions.installSkills
|
|
2044
2221
|
});
|
|
2045
2222
|
}
|
|
2046
2223
|
if (modal.type === "add-subscription") {
|
|
@@ -2050,8 +2227,8 @@ function Modals({ t, modal, data, autoLinkAgents, pickDirectory, actions, onClos
|
|
|
2050
2227
|
hint: t("form.subHint"),
|
|
2051
2228
|
fields: [
|
|
2052
2229
|
{ key: "source", label: t("form.source"), placeholder: t("form.sourceHint") },
|
|
2053
|
-
{ key: "ref", label: t("form.ref"), placeholder: "
|
|
2054
|
-
{ key: "subpath", label: t("form.subpath"), placeholder: "skills" }
|
|
2230
|
+
{ key: "ref", label: t("form.ref"), placeholder: t("form.refHint"), required: false },
|
|
2231
|
+
{ key: "subpath", label: t("form.subpath"), placeholder: "skills", required: false }
|
|
2055
2232
|
],
|
|
2056
2233
|
onCancel: onClose,
|
|
2057
2234
|
onSubmit: (form) => void actions.addSubscription(form)
|
|
@@ -2267,7 +2444,8 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
2267
2444
|
autoLinkAgents: autolinkTargets,
|
|
2268
2445
|
pickDirectory,
|
|
2269
2446
|
actions: {
|
|
2270
|
-
|
|
2447
|
+
probeSkills: actions.probeSkills,
|
|
2448
|
+
installSkills: actions.installSkills,
|
|
2271
2449
|
addSubscription: actions.addSubscription,
|
|
2272
2450
|
saveLinks: actions.saveLinks
|
|
2273
2451
|
},
|
package/lib/core/cli.js
CHANGED
|
@@ -213,10 +213,12 @@ export function createCli({ env = process.env, logger = () => {} } = {}) {
|
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
215
|
* 安装:`-a universal` 必带(F17),`--json` 配 `-y`(F11),`--full-depth` 必带(F24)。
|
|
216
|
+
* 技能名留空 = `--skill '*'`(整仓全部);不能留空值的 `--skill`,那会把后面的 `-a` 当成它的参数。
|
|
216
217
|
* 永不带 `--all` / `-a '*'`。
|
|
217
218
|
*/
|
|
218
219
|
async add({ source, skills = [], timeoutMs = env.DSH_SKILLS_CONSOLE_TIMEOUT_MS ? Number(env.DSH_SKILLS_CONSOLE_TIMEOUT_MS) : 120000 }) {
|
|
219
|
-
const
|
|
220
|
+
const names = skills.length > 0 ? skills : ['*'];
|
|
221
|
+
const args = ['add', source, '--skill', ...names, '-a', 'universal', '-g', '-y', '--json', '--full-depth'];
|
|
220
222
|
const result = await call(args, { timeoutMs });
|
|
221
223
|
return result;
|
|
222
224
|
},
|