@morehao/dsh-skills-console 0.4.0 → 0.5.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 +44 -20
- package/client.js +1299 -248
- package/lib/core/hash.js +13 -1
- package/lib/core/intro-store.js +156 -0
- package/lib/core/intro.js +430 -0
- package/lib/core/inventory.js +45 -17
- package/lib/core/invocation.js +578 -0
- package/lib/core/paths.js +2 -0
- package/lib/core/remote.js +103 -1
- package/lib/core/service.js +1103 -9
- package/lib/core/state.js +106 -6
- package/lib/index.js +16 -0
- package/lib/invocation-support.json +46 -0
- package/lib/routes.js +60 -2
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -46,12 +46,13 @@ var API = "/api/morehao/skills-console";
|
|
|
46
46
|
function short(sha) {
|
|
47
47
|
return sha ? String(sha).slice(0, 7) : "";
|
|
48
48
|
}
|
|
49
|
-
async function apiCall(method, path, body) {
|
|
49
|
+
async function apiCall(method, path, body, options = {}) {
|
|
50
50
|
const response = await fetch(`${API}${path}`, {
|
|
51
51
|
method,
|
|
52
52
|
headers: { "content-type": "application/json", "x-dsh-skills-console": "1" },
|
|
53
53
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
54
|
-
cache: "no-store"
|
|
54
|
+
cache: "no-store",
|
|
55
|
+
...options.signal ? { signal: options.signal } : {}
|
|
55
56
|
});
|
|
56
57
|
let json = {};
|
|
57
58
|
try {
|
|
@@ -74,9 +75,19 @@ var DICT = {
|
|
|
74
75
|
zh: {
|
|
75
76
|
"section.title": "技能控制台",
|
|
76
77
|
"page.title": "技能控制台",
|
|
77
|
-
"section.subtitle": "
|
|
78
|
+
"section.subtitle": "统一管理本地技能的安装、更新、删除与共享,让同一份技能在多个 Agent 间复用。",
|
|
78
79
|
"tab.installed": "已安装",
|
|
79
80
|
"tab.subscriptions": "订阅",
|
|
81
|
+
"column.skill": "技能",
|
|
82
|
+
"column.agents": "共享 Agent",
|
|
83
|
+
"column.policy": "调用策略",
|
|
84
|
+
"column.actions": "操作",
|
|
85
|
+
"column.skillAgents": "技能(含 Agent)",
|
|
86
|
+
"column.policyActions": "策略与操作",
|
|
87
|
+
"column.repo": "仓库",
|
|
88
|
+
"column.ref": "Ref",
|
|
89
|
+
"column.entries": "条目",
|
|
90
|
+
"column.status": "状态",
|
|
80
91
|
"action.addSkill": "添加技能",
|
|
81
92
|
"action.addSubscription": "添加订阅",
|
|
82
93
|
"action.checkUpdates": "检查更新",
|
|
@@ -86,7 +97,7 @@ var DICT = {
|
|
|
86
97
|
"action.unsubscribe": "取消订阅",
|
|
87
98
|
"action.view": "查看",
|
|
88
99
|
"action.hide": "收起",
|
|
89
|
-
"action.exports": "
|
|
100
|
+
"action.exports": "共享设置",
|
|
90
101
|
"action.install": "安装",
|
|
91
102
|
"action.expand": "展开",
|
|
92
103
|
"action.collapse": "收起",
|
|
@@ -98,7 +109,7 @@ var DICT = {
|
|
|
98
109
|
"action.retry": "重新加载",
|
|
99
110
|
"view.flat": "平铺",
|
|
100
111
|
"view.agent": "按 Agent",
|
|
101
|
-
exportDefault: "
|
|
112
|
+
exportDefault: "新安装时自动共享",
|
|
102
113
|
"settings.title": "插件设置",
|
|
103
114
|
"placement.label": "侧边栏入口",
|
|
104
115
|
"placement.effect": "切换后立即生效",
|
|
@@ -117,27 +128,65 @@ var DICT = {
|
|
|
117
128
|
"status.noUpstream": "无上游",
|
|
118
129
|
"status.noUpstreamHint": "没有可用的订阅来源,无法检查更新;重新接管或添加订阅后再试",
|
|
119
130
|
"status.off": "已停用",
|
|
120
|
-
"status.brokenHint": "
|
|
131
|
+
"status.brokenHint": "共享链接失效:重新共享,或删除后重装",
|
|
121
132
|
"adopt.switchHint": "还未纳入追踪,不能启停",
|
|
122
133
|
"adopt.noUpstream": "没有远端来源,暂时无法检测更新",
|
|
123
134
|
"adopt.done": "已纳入追踪",
|
|
124
135
|
"toast.checked": "检查完成",
|
|
125
136
|
"mode.on": "启用",
|
|
126
137
|
"mode.off": "停用",
|
|
127
|
-
"mode.offHint": "已停用:DSH 的模型调用与 `/`
|
|
138
|
+
"mode.offHint": "已停用:DSH 的模型调用与 `/` 手动调用都看不到它;技能文件、共享关系与其他 Agent 都不受影响",
|
|
139
|
+
"policy.auto": "自动",
|
|
140
|
+
"policy.manual": "仅手动",
|
|
141
|
+
"policy.hidden": "完全停用",
|
|
142
|
+
"policy.legacy": "仅 DSH 隐藏",
|
|
143
|
+
"policy.author": "仅手动 · 只读",
|
|
144
|
+
"policy.drift": "需修复",
|
|
145
|
+
"policy.unmanaged": "未纳入追踪",
|
|
146
|
+
"policy.flat": "平铺 .md 不支持",
|
|
147
|
+
"policy.open": "设置调用策略",
|
|
148
|
+
"policy.title": "调用策略",
|
|
149
|
+
"policy.subtitle": "只改变 DSH 对这份技能的调用方式;项目里的同名副本不受影响。",
|
|
150
|
+
"policy.autoDesc": "模型与 / 都能调用。",
|
|
151
|
+
"policy.manualDesc": "模型不自动调用,手动 / 仍可用。",
|
|
152
|
+
"policy.hiddenDesc": "所有 Agent 都看不到,可随时恢复。",
|
|
153
|
+
"policy.authorHint": "作者已把这份技能固定为「仅手动」;插件不会覆盖,改回自动需要编辑技能文件。",
|
|
154
|
+
"policy.legacyHint": "旧版 DSH-only 停用:只隐藏 DSH 的模型调用;选择任一模式完成迁移。",
|
|
155
|
+
"policy.driftHint": "记录与技能文件不一致,可能被外部改过;选择「仅手动」可重试。",
|
|
156
|
+
"policy.hiddenConfirmTitle": "完全停用这个技能?",
|
|
157
|
+
"policy.hiddenConfirmBody": "所有 Agent 都看不到;不会删除内容,随时可恢复。",
|
|
158
|
+
"policy.hiddenConfirm": "完全停用",
|
|
159
|
+
"policy.saving": "保存中…",
|
|
160
|
+
"policy.saved": "调用策略已更新",
|
|
161
|
+
"policy.savedManual": "已设为仅手动;更新后会保持",
|
|
162
|
+
"policy.savedAuto": "已恢复自动",
|
|
163
|
+
"policy.savedHidden": "已完全停用;可随时恢复",
|
|
164
|
+
"policy.unpark": "恢复",
|
|
165
|
+
"policy.unparkTitle": "恢复技能",
|
|
166
|
+
"policy.unparkBody": "将回到停用前的模式({mode});不需要重新下载,其他设置也不受影响。",
|
|
167
|
+
"policy.unparkDone": "已恢复;模型与 / 按停用前的模式生效",
|
|
168
|
+
"policy.manualOnlyHint": "仅手动:模型不会自动调用,手动 / 仍可用",
|
|
169
|
+
"policy.hiddenHint": "完全停用:所有 Agent 都看不到;随时可恢复",
|
|
170
|
+
"policy.legacyNote": "当前仍是旧版 DSH-only 停用",
|
|
171
|
+
"policy.driftNote": "仅手动标记需修复",
|
|
172
|
+
"policy.authorNote": "作者已设为仅手动,插件不覆盖",
|
|
173
|
+
"policy.hiddenNote": "恢复后回到停用前的模式;停用期间不能更新或重装",
|
|
174
|
+
"policy.unparkKept": "已恢复;冲突文件保留在 {path}",
|
|
175
|
+
"policy.unparkDrift": "已恢复,但文件内容与停用时不一致;请检查或重新检查更新",
|
|
176
|
+
"filter.manual": "仅手动",
|
|
128
177
|
"visibility.hiddenHint": "已启用,但扫描目录里没有它:确认 ~/.agents/skills 下真有这个技能,或检查 DSH_AGENTS_HOME。",
|
|
129
|
-
"visibility.manualOnlyHint": "
|
|
130
|
-
"visibility.scopeMismatch": "
|
|
178
|
+
"visibility.manualOnlyHint": "技能作者自己设了「仅手动」:模型调用看不到它,手动 `/` 调用仍可用。",
|
|
179
|
+
"visibility.scopeMismatch": "当前有另一份同名技能排在前面(项目或用户目录);停用只影响这一份,不会隐藏它。",
|
|
131
180
|
"visibility.foreignWinner": "模型看到的是另一份同名技能:{path}(它的扫描根 rank 更小,会遮住这一份)",
|
|
132
|
-
"exports.targets": "
|
|
133
|
-
"exports.detectedHint": "只列出本机已安装(目录存在)的 Agent
|
|
134
|
-
"exports.canonical": "技能本体:{path}
|
|
135
|
-
"exports.none": "
|
|
136
|
-
"exports.hint": "
|
|
137
|
-
"exports.sharedUnknown": "
|
|
138
|
-
"exportDefault.detected": "
|
|
139
|
-
"exportDefault.none": "
|
|
140
|
-
"exportDefault.hint": "默认一个都不勾:技能装进 ~/.agents/skills 后 DSH
|
|
181
|
+
"exports.targets": "共享 Agent",
|
|
182
|
+
"exports.detectedHint": "只列出本机已安装(目录存在)的 Agent。共享只影响它们;DSH 自己看不看得见由 DSH 决定,和共享无关。",
|
|
183
|
+
"exports.canonical": "技能本体:{path};勾选后只是让不直接读该目录的 Agent 也能用,不会复制文件。",
|
|
184
|
+
"exports.none": "本机没有可共享的 Agent 目录",
|
|
185
|
+
"exports.hint": "保存后按勾选建立或移除链接;技能本体留在原位置,不会移动。",
|
|
186
|
+
"exports.sharedUnknown": "共享目录未验证",
|
|
187
|
+
"exportDefault.detected": "装完后默认共享至以下 Agent",
|
|
188
|
+
"exportDefault.none": "本机没有需要共享的 Agent",
|
|
189
|
+
"exportDefault.hint": "默认一个都不勾:技能装进 ~/.agents/skills 后 DSH 本来就能看见。共享只用于仍只认私有目录的 Agent,且只影响之后的安装。",
|
|
141
190
|
"empty.skills": "还没有安装任何技能",
|
|
142
191
|
"empty.skillsHint": "在终端跑 `npx skills add owner/repo`,或点右上角「添加技能」",
|
|
143
192
|
"empty.subs": "还没有添加订阅",
|
|
@@ -151,7 +200,7 @@ var DICT = {
|
|
|
151
200
|
"form.probing": "读取中…",
|
|
152
201
|
"form.probingHint": "正在读仓库的文件树,稍等一下…",
|
|
153
202
|
"form.detected": "检测到 {n} 个技能,勾选要安装的",
|
|
154
|
-
"form.skipped": "另有 {n}
|
|
203
|
+
"form.skipped": "另有 {n} 个技能缺少名称或描述,已跳过",
|
|
155
204
|
"form.selectAll": "全选",
|
|
156
205
|
"form.selectNone": "全不选",
|
|
157
206
|
"form.pickHint": "先填来源并检测;仓库里有多个技能时可以多选,只有一个会默认勾上。",
|
|
@@ -161,23 +210,23 @@ var DICT = {
|
|
|
161
210
|
"form.ref": "ref(分支 / tag,可空)",
|
|
162
211
|
"form.refHint": "留空则自动跟随仓库默认分支",
|
|
163
212
|
"form.subpath": "子目录(可空)",
|
|
164
|
-
"form.hint": "会立即下载并写入 ~/.agents/skills
|
|
213
|
+
"form.hint": "会立即下载并写入 ~/.agents/skills,并按「新安装时自动共享」的选择共享至对应 Agent。",
|
|
165
214
|
"form.subHint": "只记录你关注这个仓库,不会安装任何技能。",
|
|
166
215
|
"subscription.defaultRef": "默认分支",
|
|
167
216
|
"confirm.deleteTitle": "删除技能",
|
|
168
|
-
"confirm.deleteBody": "
|
|
217
|
+
"confirm.deleteBody": "会从本机移除,断开本插件建立的其他 Agent 链接并清理记录;目录先备份到回收站(保留 7 天)。不会删除你的源仓库。",
|
|
169
218
|
"confirm.updateTitle": "本地有改动",
|
|
170
219
|
"confirm.updateBody": "更新会覆盖这些本地改动;旧内容已备份,可回滚。",
|
|
171
220
|
"error.conflictTitle": "删除被拦截",
|
|
172
221
|
"busy.working": "处理中…",
|
|
173
222
|
"meta.from": "来自",
|
|
174
223
|
"meta.noUpstream": "无远端来源 · 不检测更新",
|
|
175
|
-
"meta.noExport": "
|
|
176
|
-
"meta.noExportHint": "没有给不支持共享根的 Agent
|
|
224
|
+
"meta.noExport": "未共享",
|
|
225
|
+
"meta.noExportHint": "没有给不支持共享根的 Agent 建链接;直读 ~/.agents/skills 的 Agent 装好即见,无需共享。",
|
|
177
226
|
"meta.modelVisible": "模型可见",
|
|
178
227
|
"meta.modelHidden": "模型不可见",
|
|
179
228
|
"meta.manualOnly": "仅手动",
|
|
180
|
-
"meta.agentsAll": "
|
|
229
|
+
"meta.agentsAll": "已共享的 Agent",
|
|
181
230
|
"meta.none": "无",
|
|
182
231
|
"filter.all": "全部",
|
|
183
232
|
"filter.update": "可更新",
|
|
@@ -190,6 +239,7 @@ var DICT = {
|
|
|
190
239
|
"empty.filteredHint": "换个关键词,或清除筛选条件",
|
|
191
240
|
"hint.unknown": "还有 {n} 个技能未检查更新。",
|
|
192
241
|
"group.visible": "模型可见",
|
|
242
|
+
"group.manual": "仅手动",
|
|
193
243
|
"group.off": "已停用",
|
|
194
244
|
"group.hidden": "启用但不可见",
|
|
195
245
|
installed: "已安装",
|
|
@@ -248,19 +298,55 @@ var DICT = {
|
|
|
248
298
|
"viewer.info.localChange": "本地改动",
|
|
249
299
|
"viewer.info.localChangeNone": "无(与安装时一致)",
|
|
250
300
|
"viewer.info.localChangeYes": "有本地改动",
|
|
251
|
-
"viewer.info.links": "
|
|
252
|
-
"viewer.info.exported": "
|
|
301
|
+
"viewer.info.links": "共享",
|
|
302
|
+
"viewer.info.exported": "已共享",
|
|
253
303
|
"viewer.info.visibility": "可见性",
|
|
254
304
|
"viewer.info.mode": "状态",
|
|
255
305
|
"viewer.info.status": "更新",
|
|
256
|
-
"viewer.info.none": "无"
|
|
306
|
+
"viewer.info.none": "无",
|
|
307
|
+
"intro.button": "介绍",
|
|
308
|
+
"intro.open": "AI 介绍:这个技能能做什么",
|
|
309
|
+
"intro.title": "AI 介绍",
|
|
310
|
+
"intro.loading": "读取中…",
|
|
311
|
+
"intro.empty": "还没有生成介绍",
|
|
312
|
+
"intro.generate": "生成介绍",
|
|
313
|
+
"intro.regenerate": "重新生成",
|
|
314
|
+
"intro.generating": "生成中…",
|
|
315
|
+
"intro.hint": "会把 SKILL.md 正文发送给当前配置的模型提供方",
|
|
316
|
+
"intro.hintRemote": "会把该技能在 {source}@{commit} 的 SKILL.md 发送给当前配置的模型提供方",
|
|
317
|
+
"intro.meta": "{model} · {time} · AI 生成,可能不准确",
|
|
318
|
+
"intro.stale": "技能内容已更新,介绍可能过期",
|
|
319
|
+
"intro.view": "查看技能",
|
|
320
|
+
"intro.unavailable": "当前宿主没有可用的模型服务",
|
|
321
|
+
"intro.error.noModel": "还没有配置可用的模型",
|
|
322
|
+
"intro.error.rateLimit": "模型限流或额度不足,请稍后再试",
|
|
323
|
+
"intro.error.tooLarge": "技能正文超出模型上下文",
|
|
324
|
+
"intro.error.timeout": "生成超时,请重试",
|
|
325
|
+
"intro.error.auth": "模型提供方认证失败",
|
|
326
|
+
"intro.error.empty": "模型没有返回内容",
|
|
327
|
+
"intro.error.busy": "正在生成,请稍候",
|
|
328
|
+
"intro.error.source": "读不到远端的 SKILL.md(仓库私有、已删除或网络问题)",
|
|
329
|
+
"intro.error.noSource": "订阅快照里没有这个技能,请先点「检查更新」",
|
|
330
|
+
"intro.error.subscription": "订阅不存在,可能已被取消",
|
|
331
|
+
"intro.error.sourceTimeout": "读取远端 SKILL.md 超时,请重试",
|
|
332
|
+
"intro.error.generic": "生成失败,请重试"
|
|
257
333
|
},
|
|
258
334
|
en: {
|
|
259
335
|
"section.title": "Skills Console",
|
|
260
336
|
"page.title": "Skills Console",
|
|
261
|
-
"section.subtitle": "Manage local skills in one place: install, update, remove and
|
|
337
|
+
"section.subtitle": "Manage local skills in one place: install, update, remove and share — so the same skill can be reused across agents.",
|
|
262
338
|
"tab.installed": "Installed",
|
|
263
339
|
"tab.subscriptions": "Subscriptions",
|
|
340
|
+
"column.skill": "Skill",
|
|
341
|
+
"column.agents": "Shared with",
|
|
342
|
+
"column.policy": "Invocation",
|
|
343
|
+
"column.actions": "Actions",
|
|
344
|
+
"column.skillAgents": "Skill & agents",
|
|
345
|
+
"column.policyActions": "Policy & actions",
|
|
346
|
+
"column.repo": "Repository",
|
|
347
|
+
"column.ref": "Ref",
|
|
348
|
+
"column.entries": "Skills",
|
|
349
|
+
"column.status": "Status",
|
|
264
350
|
"action.addSkill": "Add skill",
|
|
265
351
|
"action.addSubscription": "Add subscription",
|
|
266
352
|
"action.checkUpdates": "Check for updates",
|
|
@@ -270,7 +356,7 @@ var DICT = {
|
|
|
270
356
|
"action.unsubscribe": "Unsubscribe",
|
|
271
357
|
"action.view": "View",
|
|
272
358
|
"action.hide": "Hide",
|
|
273
|
-
"action.exports": "
|
|
359
|
+
"action.exports": "Share settings",
|
|
274
360
|
"action.install": "Install",
|
|
275
361
|
"action.expand": "Expand",
|
|
276
362
|
"action.collapse": "Collapse",
|
|
@@ -282,7 +368,7 @@ var DICT = {
|
|
|
282
368
|
"action.retry": "Reload",
|
|
283
369
|
"view.flat": "Flat",
|
|
284
370
|
"view.agent": "By agent",
|
|
285
|
-
"exportDefault": "Auto-
|
|
371
|
+
"exportDefault": "Auto-share on install",
|
|
286
372
|
"settings.title": "Plugin settings",
|
|
287
373
|
"placement.label": "Sidebar entry",
|
|
288
374
|
"placement.effect": "Applies immediately",
|
|
@@ -301,26 +387,64 @@ var DICT = {
|
|
|
301
387
|
"status.noUpstream": "No upstream",
|
|
302
388
|
"status.noUpstreamHint": "No usable subscription source, so update checks are impossible; re-adopt or add a subscription",
|
|
303
389
|
"status.off": "Disabled",
|
|
304
|
-
"status.brokenHint": "The link
|
|
390
|
+
"status.brokenHint": "The shared link is broken: share it again, or delete and reinstall",
|
|
305
391
|
"adopt.switchHint": "Not tracked yet, cannot be enabled or disabled",
|
|
306
392
|
"adopt.noUpstream": "No upstream source; update checks are unavailable",
|
|
307
393
|
"toast.checked": "Checked",
|
|
308
394
|
"mode.on": "Enabled",
|
|
309
395
|
"mode.off": "Disabled",
|
|
310
|
-
"mode.offHint": "Disabled for DSH’s model and `/` calls; the skill files,
|
|
396
|
+
"mode.offHint": "Disabled for DSH’s model and `/` calls; the skill files, shares, and other agents are untouched",
|
|
397
|
+
"policy.auto": "Auto",
|
|
398
|
+
"policy.manual": "Manual only",
|
|
399
|
+
"policy.hidden": "Fully disabled",
|
|
400
|
+
"policy.legacy": "DSH-only hidden",
|
|
401
|
+
"policy.author": "Manual · read-only",
|
|
402
|
+
"policy.drift": "Needs repair",
|
|
403
|
+
"policy.unmanaged": "Untracked",
|
|
404
|
+
"policy.flat": "Flat .md unsupported",
|
|
405
|
+
"policy.open": "Set invocation policy",
|
|
406
|
+
"policy.title": "Invocation policy",
|
|
407
|
+
"policy.subtitle": "Applies only to how DSH invokes this skill; project copies are untouched.",
|
|
408
|
+
"policy.autoDesc": "The model and `/` can invoke it.",
|
|
409
|
+
"policy.manualDesc": "The model will not invoke it; `/` still works.",
|
|
410
|
+
"policy.hiddenDesc": "Invisible to every agent; restorable at any time.",
|
|
411
|
+
"policy.authorHint": "The author fixed this skill to “Manual only”; the plugin will not override it — edit the skill file to restore auto.",
|
|
412
|
+
"policy.legacyHint": "Legacy DSH-only disable: only hides DSH model calls; pick a mode to migrate.",
|
|
413
|
+
"policy.driftHint": "The record and the skill file disagree; it may have been edited externally. Pick “Manual only” to retry.",
|
|
414
|
+
"policy.hiddenConfirmTitle": "Fully disable this skill?",
|
|
415
|
+
"policy.hiddenConfirmBody": "Every agent stops seeing it. Nothing is deleted, and you can restore at any time.",
|
|
416
|
+
"policy.hiddenConfirm": "Disable fully",
|
|
417
|
+
"policy.saving": "Saving…",
|
|
418
|
+
"policy.saved": "Invocation policy updated",
|
|
419
|
+
"policy.savedManual": "Set to manual only; kept after updates",
|
|
420
|
+
"policy.savedAuto": "Auto restored",
|
|
421
|
+
"policy.savedHidden": "Fully disabled; restorable at any time",
|
|
422
|
+
"policy.unpark": "Restore",
|
|
423
|
+
"policy.unparkTitle": "Restore skill",
|
|
424
|
+
"policy.unparkBody": "Returns to the mode before disabling ({mode}); no re-download, and other settings stay as they are.",
|
|
425
|
+
"policy.unparkDone": "Restored; the model and `/` follow the previous mode",
|
|
426
|
+
"policy.manualOnlyHint": "Manual only: the model will not invoke it; `/` still works",
|
|
427
|
+
"policy.hiddenHint": "Fully disabled: invisible to every agent; restorable",
|
|
428
|
+
"policy.legacyNote": "Still on the legacy DSH-only disable",
|
|
429
|
+
"policy.driftNote": "The manual-only marker needs repair",
|
|
430
|
+
"policy.authorNote": "Set to manual only by the author; the plugin will not override it",
|
|
431
|
+
"policy.hiddenNote": "Restores to the previous mode; updates and reinstalls are blocked while parked",
|
|
432
|
+
"policy.unparkKept": "Restored; conflict copy kept at {path}",
|
|
433
|
+
"policy.unparkDrift": "Restored, but the file differs from when it was parked; review or check for updates",
|
|
434
|
+
"filter.manual": "Manual only",
|
|
311
435
|
"visibility.hiddenHint": "Enabled, but no copy sits in a scan directory: check that the skill really exists under ~/.agents/skills, or inspect DSH_AGENTS_HOME.",
|
|
312
|
-
"visibility.manualOnlyHint": "
|
|
313
|
-
"visibility.scopeMismatch": "Another copy of this skill ranks first (project or user directory); disabling only affects
|
|
436
|
+
"visibility.manualOnlyHint": "The skill author set it to “Manual only”: the model cannot invoke it, but manual `/` invocation still works.",
|
|
437
|
+
"visibility.scopeMismatch": "Another copy of this skill ranks first (project or user directory); disabling only affects this copy.",
|
|
314
438
|
"visibility.foreignWinner": "The model sees a different copy of this skill: {path} (its scan root ranks higher, so it shadows this one)",
|
|
315
|
-
"exports.targets": "
|
|
316
|
-
"exports.detectedHint": "Only installed agents (existing directories) are listed.
|
|
317
|
-
"exports.canonical": "Skill source: {path};
|
|
318
|
-
"exports.none": "No agent directories available
|
|
319
|
-
"exports.hint": "Saving
|
|
320
|
-
"exports.sharedUnknown": "shared
|
|
321
|
-
"exportDefault.detected": "
|
|
322
|
-
"exportDefault.none": "No agents need
|
|
323
|
-
"exportDefault.hint": "Nothing is checked by default: once a skill lands in ~/.agents/skills, DSH already sees it.
|
|
439
|
+
"exports.targets": "Share with which agents",
|
|
440
|
+
"exports.detectedHint": "Only installed agents (existing directories) are listed. Sharing only affects them; whether DSH sees the skill is up to DSH, not sharing.",
|
|
441
|
+
"exports.canonical": "Skill source: {path}; ticking an agent only makes it available there — nothing is copied.",
|
|
442
|
+
"exports.none": "No agent directories available to share to on this machine",
|
|
443
|
+
"exports.hint": "Saving adds or removes links; the skill itself stays where it is.",
|
|
444
|
+
"exports.sharedUnknown": "shared directory unverified",
|
|
445
|
+
"exportDefault.detected": "Share new installs with",
|
|
446
|
+
"exportDefault.none": "No agents need sharing on this machine",
|
|
447
|
+
"exportDefault.hint": "Nothing is checked by default: once a skill lands in ~/.agents/skills, DSH already sees it. Sharing only matters for agents that still read their own directory, and only affects future installs.",
|
|
324
448
|
"empty.skills": "No skills installed yet",
|
|
325
449
|
"empty.skillsHint": 'Run `npx skills add owner/repo` or use "Add skill".',
|
|
326
450
|
"empty.subs": "No subscriptions yet",
|
|
@@ -334,7 +458,7 @@ var DICT = {
|
|
|
334
458
|
"form.probing": "Reading…",
|
|
335
459
|
"form.probingHint": "Reading the repository tree…",
|
|
336
460
|
"form.detected": "Found {n} skills; pick the ones to install",
|
|
337
|
-
"form.skipped": "{n}
|
|
461
|
+
"form.skipped": "{n} skill(s) skipped: missing a name or description",
|
|
338
462
|
"form.selectAll": "Select all",
|
|
339
463
|
"form.selectNone": "Clear",
|
|
340
464
|
"form.pickHint": "Enter a source and detect first. Pick one or more when the repo has several.",
|
|
@@ -344,23 +468,23 @@ var DICT = {
|
|
|
344
468
|
"form.ref": "ref (branch / tag, optional)",
|
|
345
469
|
"form.refHint": "empty follows the repository default branch",
|
|
346
470
|
"form.subpath": "Subdirectory (optional)",
|
|
347
|
-
"form.hint": "Downloads into ~/.agents/skills and
|
|
471
|
+
"form.hint": "Downloads into ~/.agents/skills and shares it with agents per the auto-share setting.",
|
|
348
472
|
"form.subHint": "Only records the repository; installs nothing.",
|
|
349
473
|
"subscription.defaultRef": "default branch",
|
|
350
474
|
"confirm.deleteTitle": "Delete skill",
|
|
351
|
-
"confirm.deleteBody": "Removes it locally, disconnects this plugin
|
|
475
|
+
"confirm.deleteBody": "Removes it locally, disconnects the agent links this plugin created, and clears the record. A snapshot stays in trash for 7 days. Your source repo is untouched.",
|
|
352
476
|
"confirm.updateTitle": "Local changes",
|
|
353
477
|
"confirm.updateBody": "Updating overwrites local changes; the old content is backed up and can be rolled back.",
|
|
354
478
|
"error.conflictTitle": "Delete blocked",
|
|
355
479
|
"busy.working": "Working…",
|
|
356
480
|
"meta.from": "from",
|
|
357
481
|
"meta.noUpstream": "no upstream · updates not checked",
|
|
358
|
-
"meta.noExport": "Not
|
|
359
|
-
"meta.noExportHint": "No
|
|
482
|
+
"meta.noExport": "Not shared",
|
|
483
|
+
"meta.noExportHint": "No links to agents that need them; agents that read ~/.agents/skills can use it as-is.",
|
|
360
484
|
"meta.modelVisible": "visible to the model",
|
|
361
485
|
"meta.modelHidden": "hidden from the model",
|
|
362
486
|
"meta.manualOnly": "Manual only",
|
|
363
|
-
"meta.agentsAll": "
|
|
487
|
+
"meta.agentsAll": "Shared with",
|
|
364
488
|
"meta.none": "none",
|
|
365
489
|
"filter.all": "All",
|
|
366
490
|
"filter.update": "Updates",
|
|
@@ -373,6 +497,7 @@ var DICT = {
|
|
|
373
497
|
"empty.filteredHint": "Try another keyword, or clear the filters",
|
|
374
498
|
"hint.unknown": "{n} skills have not been checked for updates.",
|
|
375
499
|
"group.visible": "Model visible",
|
|
500
|
+
"group.manual": "Manual only",
|
|
376
501
|
"group.off": "Disabled",
|
|
377
502
|
"group.hidden": "Enabled but hidden",
|
|
378
503
|
installed: "installed",
|
|
@@ -432,12 +557,38 @@ var DICT = {
|
|
|
432
557
|
"viewer.info.localChange": "Local changes",
|
|
433
558
|
"viewer.info.localChangeNone": "None (matches install)",
|
|
434
559
|
"viewer.info.localChangeYes": "Modified locally",
|
|
435
|
-
"viewer.info.links": "
|
|
436
|
-
"viewer.info.exported": "
|
|
560
|
+
"viewer.info.links": "Shared",
|
|
561
|
+
"viewer.info.exported": "Shared",
|
|
437
562
|
"viewer.info.visibility": "Visibility",
|
|
438
563
|
"viewer.info.mode": "State",
|
|
439
564
|
"viewer.info.status": "Updates",
|
|
440
|
-
"viewer.info.none": "None"
|
|
565
|
+
"viewer.info.none": "None",
|
|
566
|
+
"intro.button": "Intro",
|
|
567
|
+
"intro.open": "AI intro: what this skill does",
|
|
568
|
+
"intro.title": "AI intro",
|
|
569
|
+
"intro.loading": "Loading…",
|
|
570
|
+
"intro.empty": "No intro yet",
|
|
571
|
+
"intro.generate": "Generate intro",
|
|
572
|
+
"intro.regenerate": "Regenerate",
|
|
573
|
+
"intro.generating": "Generating…",
|
|
574
|
+
"intro.hint": "Sends the SKILL.md body to the model provider configured in DSH",
|
|
575
|
+
"intro.hintRemote": "Sends the SKILL.md at {source}@{commit} to the model provider configured in DSH",
|
|
576
|
+
"intro.meta": "{model} · {time} · AI-generated, may be inaccurate",
|
|
577
|
+
"intro.stale": "The skill changed; this intro may be out of date",
|
|
578
|
+
"intro.view": "Open skill",
|
|
579
|
+
"intro.unavailable": "No model service is available in this host",
|
|
580
|
+
"intro.error.noModel": "No usable model is configured",
|
|
581
|
+
"intro.error.rateLimit": "The model is rate-limited or out of quota; try again later",
|
|
582
|
+
"intro.error.tooLarge": "The skill body exceeds the model context window",
|
|
583
|
+
"intro.error.timeout": "Generation timed out; try again",
|
|
584
|
+
"intro.error.auth": "The model provider rejected the credentials",
|
|
585
|
+
"intro.error.empty": "The model returned no content",
|
|
586
|
+
"intro.error.busy": "Already generating; please wait",
|
|
587
|
+
"intro.error.source": "Could not read the remote SKILL.md (private repo, deleted, or network)",
|
|
588
|
+
"intro.error.noSource": "This skill is not in the subscription snapshot; run “Check for updates” first",
|
|
589
|
+
"intro.error.subscription": "The subscription no longer exists",
|
|
590
|
+
"intro.error.sourceTimeout": "Timed out reading the remote SKILL.md; try again",
|
|
591
|
+
"intro.error.generic": "Generation failed; try again"
|
|
441
592
|
}
|
|
442
593
|
};
|
|
443
594
|
function buildTranslate(locale) {
|
|
@@ -448,11 +599,16 @@ function buildTranslate(locale) {
|
|
|
448
599
|
return "zh";
|
|
449
600
|
}
|
|
450
601
|
};
|
|
451
|
-
|
|
602
|
+
const translate = (key) => {
|
|
452
603
|
const id = active();
|
|
453
604
|
const pack = DICT[id] || DICT.zh;
|
|
454
605
|
return pack[key] ?? DICT.zh[key] ?? key;
|
|
455
606
|
};
|
|
607
|
+
Object.defineProperty(translate, "lang", {
|
|
608
|
+
get: () => active() === "en" ? "en" : "zh",
|
|
609
|
+
enumerable: false
|
|
610
|
+
});
|
|
611
|
+
return translate;
|
|
456
612
|
}
|
|
457
613
|
|
|
458
614
|
// client/host.js
|
|
@@ -694,6 +850,48 @@ function TrashIcon({ size = 14 }) {
|
|
|
694
850
|
h("path", { d: "M10 11v5.5M14 11v5.5" })
|
|
695
851
|
);
|
|
696
852
|
}
|
|
853
|
+
function DownloadIcon({ size = 14 }) {
|
|
854
|
+
return h(
|
|
855
|
+
"svg",
|
|
856
|
+
{
|
|
857
|
+
width: size,
|
|
858
|
+
height: size,
|
|
859
|
+
viewBox: "0 0 24 24",
|
|
860
|
+
fill: "none",
|
|
861
|
+
stroke: "currentColor",
|
|
862
|
+
strokeWidth: 1.8,
|
|
863
|
+
strokeLinecap: "round",
|
|
864
|
+
strokeLinejoin: "round",
|
|
865
|
+
"aria-hidden": true,
|
|
866
|
+
style: { display: "block" }
|
|
867
|
+
},
|
|
868
|
+
h("path", { d: "M12 4v12" }),
|
|
869
|
+
h("path", { d: "M7 11l5 5 5-5" }),
|
|
870
|
+
h("path", { d: "M4 20h16" })
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function ShareIcon({ size = 14 }) {
|
|
874
|
+
return h(
|
|
875
|
+
"svg",
|
|
876
|
+
{
|
|
877
|
+
width: size,
|
|
878
|
+
height: size,
|
|
879
|
+
viewBox: "0 0 24 24",
|
|
880
|
+
fill: "none",
|
|
881
|
+
stroke: "currentColor",
|
|
882
|
+
strokeWidth: 1.8,
|
|
883
|
+
strokeLinecap: "round",
|
|
884
|
+
strokeLinejoin: "round",
|
|
885
|
+
"aria-hidden": true,
|
|
886
|
+
style: { display: "block" }
|
|
887
|
+
},
|
|
888
|
+
h("rect", { x: 9.2, y: 2.8, width: 5.6, height: 5.6, rx: 1.2 }),
|
|
889
|
+
h("rect", { x: 2.4, y: 15.4, width: 5.6, height: 5.6, rx: 1.2 }),
|
|
890
|
+
h("rect", { x: 16, y: 15.4, width: 5.6, height: 5.6, rx: 1.2 }),
|
|
891
|
+
h("path", { d: "M10.4 8.8 6.2 15.4" }),
|
|
892
|
+
h("path", { d: "M13.6 8.8 17.8 15.4" })
|
|
893
|
+
);
|
|
894
|
+
}
|
|
697
895
|
function InfoIcon({ size = 13 }) {
|
|
698
896
|
return h(
|
|
699
897
|
"svg",
|
|
@@ -842,7 +1040,7 @@ function FolderIcon({ size = 12 }) {
|
|
|
842
1040
|
}
|
|
843
1041
|
|
|
844
1042
|
// client/section.js
|
|
845
|
-
var
|
|
1043
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
846
1044
|
|
|
847
1045
|
// client/scroll.js
|
|
848
1046
|
function isScroller(node, tolerance = 4) {
|
|
@@ -1009,10 +1207,24 @@ var CSS = `
|
|
|
1009
1207
|
|
|
1010
1208
|
.dshsc-list { display: flex; flex-direction: column; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
|
|
1011
1209
|
background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
1012
|
-
.dshsc-row { display: grid; grid-template-columns:
|
|
1210
|
+
.dshsc-row { display: grid; grid-template-columns: 280px minmax(0, 1fr) 150px 104px;
|
|
1211
|
+
gap: 14px; align-items: center;
|
|
1013
1212
|
padding: 8px 12px 8px 14px; min-height: 54px; border-top: 1px solid var(--dsw-alias-border-l1);
|
|
1014
1213
|
transition: background .12s ease; }
|
|
1015
|
-
.dshsc-list > .dshsc-row:first-child { border-top: 0; }
|
|
1214
|
+
.dshsc-list > .dshsc-row:first-child, .dshsc-listhead + .dshsc-row { border-top: 0; }
|
|
1215
|
+
/* 表头与行共用同一套列宽/间距,只有列语义;不参与排序或筛选。
|
|
1216
|
+
操作列暂按 4 个图标按钮(查看/更新/共享设置/删除)取 104px,三档 chip 落地、行内
|
|
1217
|
+
去掉重复的「查看」后收窄到设计稿的 92px。 */
|
|
1218
|
+
.dshsc-listhead { display: grid; grid-template-columns: 280px minmax(0, 1fr) 150px 104px;
|
|
1219
|
+
gap: 14px; align-items: center; padding: 7px 12px 7px 14px;
|
|
1220
|
+
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1221
|
+
background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 40%, var(--dsw-alias-bg-layer-1));
|
|
1222
|
+
color: var(--dsw-alias-label-tertiary); font-size: 10.5px; font-weight: 500; letter-spacing: .02em; }
|
|
1223
|
+
/* 行首有 6px 可见性圆点 + 7px 间距,表头跟名字对齐而不是跟圆点对齐 */
|
|
1224
|
+
.dshsc-listhead .dshsc-hskill { padding-left: 13px; }
|
|
1225
|
+
.dshsc-listhead .dshsc-hactions { text-align: right; padding-right: 2px; }
|
|
1226
|
+
.dshsc-col-narrow { display: none; }
|
|
1227
|
+
.dshsc-policy { min-width: 0; display: flex; align-items: center; }
|
|
1016
1228
|
.dshsc-row:hover { background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 48%, transparent); }
|
|
1017
1229
|
.dshsc-row.off { opacity: .7; }
|
|
1018
1230
|
.dshsc-cell { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
@@ -1113,51 +1325,148 @@ var CSS = `
|
|
|
1113
1325
|
.dshsc-banner.err { color: var(--dsw-alias-state-error-primary); border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, var(--dsw-alias-border-l1)); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent); }
|
|
1114
1326
|
.dshsc-banner.warn { color: var(--dsw-alias-state-warn-primary); border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 45%, var(--dsw-alias-border-l1)); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent); }
|
|
1115
1327
|
|
|
1116
|
-
|
|
1117
|
-
.dshsc-
|
|
1328
|
+
/* 订阅:滚动容器本身就是卡片,表头 sticky 在同一滚动上下文里,列宽与行共用 */
|
|
1329
|
+
.dshsc-subscroll { border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px;
|
|
1330
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1331
|
+
.dshsc-subhead { position: sticky; top: 0; z-index: 3; display: grid;
|
|
1332
|
+
grid-template-columns: minmax(0, 1fr) 86px 178px 118px; gap: 10px; align-items: center;
|
|
1333
|
+
height: 30px; padding: 0 12px 0 14px; background: var(--dsw-alias-bg-layer-2);
|
|
1334
|
+
border-bottom: 1px solid var(--dsw-alias-border-l1); border-radius: 11px 11px 0 0;
|
|
1335
|
+
color: var(--dsw-alias-label-tertiary); font-size: 10.5px; font-weight: 500; letter-spacing: .02em; }
|
|
1336
|
+
.dshsc-subhead .dshsc-hrepo { padding-left: 32px; }
|
|
1337
|
+
.dshsc-subhead .dshsc-hact { text-align: right; padding-right: 2px; }
|
|
1338
|
+
.dshsc-sub-block { border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1339
|
+
.dshsc-subhead + .dshsc-sub-block { border-top: 0; }
|
|
1118
1340
|
.dshsc-sub-block + .dshsc-sub-block { border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1341
|
+
.dshsc-sub-row { display: grid; grid-template-columns: minmax(0, 1fr) 86px 178px 118px;
|
|
1342
|
+
gap: 10px; align-items: center; padding: 8px 12px 8px 14px; min-height: 52px; }
|
|
1343
|
+
.dshsc-sub-repo { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1344
|
+
/* 宽屏把 Ref / 条目铺成两列,窄屏回到仓库名下方一行 */
|
|
1345
|
+
.dshsc-sub-meta { display: contents; }
|
|
1346
|
+
.dshsc-subref { justify-self: start; }
|
|
1119
1347
|
.dshsc-caret { flex: none; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 6px; background: transparent;
|
|
1120
1348
|
color: var(--dsw-alias-label-secondary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; }
|
|
1121
1349
|
.dshsc-caret:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1122
1350
|
.dshsc-caret:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1123
1351
|
.dshsc-sub-row .dshsc-name { font-size: 12.5px; }
|
|
1124
|
-
.dshsc-substat { font-size: 11px; color: var(--dsw-alias-label-tertiary); white-space: nowrap;
|
|
1125
|
-
|
|
1352
|
+
.dshsc-substat { font-size: 11px; color: var(--dsw-alias-label-tertiary); white-space: nowrap;
|
|
1353
|
+
overflow: hidden; text-overflow: ellipsis; }
|
|
1354
|
+
.dshsc-sub-row .dshsc-actions { justify-self: end; display: flex; align-items: center; gap: 2px; }
|
|
1126
1355
|
.dshsc-sub-row .dshsc-btn { height: 24px; padding: 0 7px; font-size: 11.5px; display: inline-flex; align-items: center; }
|
|
1127
|
-
|
|
1128
|
-
.dshsc-sub-
|
|
1129
|
-
|
|
1130
|
-
|
|
1356
|
+
/* 条目缩进对齐仓库名:行左内边距 14 + caret 24 + 间距 8 = 46 */
|
|
1357
|
+
.dshsc-sub-list { display: flex; flex-direction: column; padding: 0 12px 7px 46px;
|
|
1358
|
+
background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 22%, transparent);
|
|
1359
|
+
border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1360
|
+
.dshsc-entryhead { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
|
|
1361
|
+
align-items: center; height: 26px; border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1362
|
+
color: var(--dsw-alias-label-tertiary); font-size: 10px; font-weight: 500; letter-spacing: .02em; }
|
|
1363
|
+
.dshsc-entryhead .right { text-align: right; padding-right: 2px; }
|
|
1364
|
+
.dshsc-sub-entry { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
|
|
1365
|
+
align-items: center; font-size: 12px; padding: 6px 0; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1366
|
+
.dshsc-entryhead + .dshsc-sub-entry { border-top: 0; }
|
|
1131
1367
|
.dshsc-sub-entry .dshsc-name { font-size: 12px; font-weight: 500; }
|
|
1368
|
+
/* 条目第一列:名字 + ✨ 同一行,作者原描述单独一行(超长省略,完整内容在 title) */
|
|
1369
|
+
.dshsc-sub-skill { min-width: 0; }
|
|
1370
|
+
.dshsc-sub-namerow { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
1371
|
+
.dshsc-sub-desc { margin-top: 1px; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary);
|
|
1372
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1132
1373
|
.dshsc-state { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
1133
1374
|
.dshsc-state.ok svg { color: var(--dsw-alias-state-success-primary); }
|
|
1134
|
-
.dshsc-sub-entry .dshsc-actions {
|
|
1375
|
+
.dshsc-sub-entry .dshsc-actions { justify-self: end; display: flex; justify-content: flex-end; }
|
|
1135
1376
|
.dshsc-sub-entry .dshsc-btn { height: 24px; padding: 0 9px; font-size: 11.5px; }
|
|
1377
|
+
/* 已装条目的操作列没有动作:破折号占位,保持右对齐与按钮基线一致 */
|
|
1378
|
+
.dshsc-sub-entry .dshsc-noop { color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 24px; padding-right: 9px; }
|
|
1136
1379
|
/* 设置页的真实宽度常常远小于窗口(侧栏 + 页面缩放),视口 media query 不可靠;
|
|
1137
1380
|
以 .dshsc-root 自身宽度为准,窄面板时切两列,避免固定列把 Agent 标签挤成竖排。
|
|
1138
|
-
断点取 860 而不是贴着面板常见的 ~900
|
|
1381
|
+
断点取 860 而不是贴着面板常见的 ~900:四列网格需要 280 + 150 + 操作列 + 3×14 gap + 中间列,
|
|
1139
1382
|
860 已经够用;留出 ~40px 余量后,滚动条(5~15px)、窗口微调、页面缩放都不会把整页翻面。 */
|
|
1140
1383
|
@container dshsc (max-width: 860px) {
|
|
1141
1384
|
.dshsc-search input { width: 180px; }
|
|
1142
|
-
.dshsc-row { grid-template-columns: minmax(0, 1fr)
|
|
1143
|
-
.dshsc-cell { grid-column: 1; }
|
|
1144
|
-
.dshsc-agents { grid-column: 1; }
|
|
1145
|
-
.dshsc-
|
|
1385
|
+
.dshsc-row { grid-template-columns: minmax(0, 1fr) 102px; row-gap: 3px; padding: 9px 12px; }
|
|
1386
|
+
.dshsc-cell { grid-column: 1; grid-row: 1; }
|
|
1387
|
+
.dshsc-agents { grid-column: 1; grid-row: 2; }
|
|
1388
|
+
.dshsc-policy { grid-column: 2; grid-row: 1; justify-content: flex-end; }
|
|
1389
|
+
.dshsc-row .dshsc-actions { grid-column: 2; grid-row: 2; }
|
|
1146
1390
|
.dshsc-row .dshsc-btn { padding: 0 5px; }
|
|
1391
|
+
.dshsc-listhead { grid-template-columns: minmax(0, 1fr) 102px; padding: 7px 12px; }
|
|
1392
|
+
.dshsc-listhead .dshsc-hagents, .dshsc-listhead .dshsc-hpolicy { display: none; }
|
|
1393
|
+
.dshsc-listhead .dshsc-col-wide { display: none; }
|
|
1394
|
+
.dshsc-listhead .dshsc-col-narrow { display: inline; }
|
|
1395
|
+
/* 订阅表头收成「仓库 / 操作」,Ref 与条目数移到仓库名下 */
|
|
1396
|
+
.dshsc-subhead { grid-template-columns: minmax(0, 1fr) auto; }
|
|
1397
|
+
.dshsc-subhead .dshsc-href, .dshsc-subhead .dshsc-hcount { display: none; }
|
|
1398
|
+
.dshsc-sub-row { grid-template-columns: minmax(0, 1fr) auto; row-gap: 2px; padding: 8px 12px; }
|
|
1399
|
+
.dshsc-sub-repo { grid-column: 1; grid-row: 1; }
|
|
1400
|
+
.dshsc-sub-meta { display: flex; align-items: center; gap: 8px; min-width: 0; grid-column: 1; grid-row: 2; }
|
|
1401
|
+
.dshsc-sub-row .dshsc-actions { grid-column: 2; grid-row: 1 / span 2; }
|
|
1402
|
+
/* 展开条目:状态 / 操作两列收窄,技能名仍吃剩余宽度 */
|
|
1403
|
+
.dshsc-entryhead, .dshsc-sub-entry { grid-template-columns: minmax(0, 1fr) 96px 78px; }
|
|
1147
1404
|
}
|
|
1148
1405
|
.dshsc-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1149
1406
|
|
|
1150
|
-
/*
|
|
1151
|
-
.dshsc-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
.dshsc-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
.dshsc-
|
|
1407
|
+
/* 调用策略 chip:一个入口显示当前模式;点开三档弹层。窄列里允许省略,不撑破 102px */
|
|
1408
|
+
.dshsc-chip { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
|
|
1409
|
+
height: 24px; padding: 0 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
|
|
1410
|
+
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); font-family: inherit;
|
|
1411
|
+
font-size: 11.5px; font-weight: 500; line-height: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1412
|
+
cursor: pointer; transition: background .12s ease, border-color .12s ease; }
|
|
1413
|
+
.dshsc-chip::before { content: ''; flex: none; width: 6px; height: 6px; border-radius: 50%;
|
|
1414
|
+
background: currentColor; opacity: .9; }
|
|
1415
|
+
.dshsc-chip:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-border-l3); }
|
|
1416
|
+
.dshsc-chip:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1417
|
+
.dshsc-chip:disabled { cursor: not-allowed; opacity: .55; }
|
|
1418
|
+
.dshsc-chip.auto { color: var(--dsw-alias-state-success-primary);
|
|
1419
|
+
border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 34%, var(--dsw-alias-border-l2));
|
|
1420
|
+
background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 9%, transparent); }
|
|
1421
|
+
.dshsc-chip.manual { color: var(--dsw-alias-brand-primary);
|
|
1422
|
+
border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 40%, var(--dsw-alias-border-l2));
|
|
1423
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary) 9%, transparent); }
|
|
1424
|
+
.dshsc-chip.hidden { color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-2); }
|
|
1425
|
+
.dshsc-chip.author, .dshsc-chip.drift { color: var(--dsw-alias-state-warn-primary);
|
|
1426
|
+
border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 34%, var(--dsw-alias-border-l2));
|
|
1427
|
+
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent); }
|
|
1428
|
+
.dshsc-chip.legacy { color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-bg-layer-2); }
|
|
1429
|
+
.dshsc-chip.muted { color: var(--dsw-alias-label-tertiary); cursor: default; opacity: .8; }
|
|
1430
|
+
|
|
1431
|
+
.dshsc-policy-pop { position: fixed; z-index: 70; box-sizing: border-box; width: min(380px, calc(100vw - 16px));
|
|
1432
|
+
max-height: min(600px, calc(100vh - 16px)); overflow: auto; padding: 14px;
|
|
1433
|
+
background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 14px;
|
|
1434
|
+
box-shadow: 0 18px 48px rgba(0,0,0,.32), 0 2px 8px rgba(0,0,0,.16);
|
|
1435
|
+
display: flex; flex-direction: column; gap: 10px; }
|
|
1436
|
+
.dshsc-policy-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
|
|
1437
|
+
.dshsc-policy-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
1438
|
+
.dshsc-policy-sub { margin: 2px 0 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); }
|
|
1439
|
+
.dshsc-policy-close { flex: none; width: 24px; height: 24px; margin: -4px -4px 0 0; border: 0; border-radius: 6px;
|
|
1440
|
+
background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; cursor: pointer; }
|
|
1441
|
+
.dshsc-policy-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1442
|
+
.dshsc-policy-options { display: flex; flex-direction: column; gap: 6px; }
|
|
1443
|
+
.dshsc-policyopt { box-sizing: border-box; display: flex; align-items: flex-start; gap: 10px; width: 100%;
|
|
1444
|
+
padding: 9px 10px; text-align: left; border: 1px solid var(--dsw-alias-border-l1); border-radius: 9px;
|
|
1445
|
+
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-family: inherit; cursor: pointer; }
|
|
1446
|
+
.dshsc-policyopt:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-border-l2); }
|
|
1447
|
+
.dshsc-policyopt.active { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, var(--dsw-alias-border-l1));
|
|
1448
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary) 7%, transparent); }
|
|
1449
|
+
.dshsc-policyopt:disabled { cursor: not-allowed; opacity: .55; }
|
|
1450
|
+
.dshsc-policyopt.static { cursor: default; }
|
|
1451
|
+
.dshsc-policyopt-dot { flex: none; width: 14px; height: 14px; margin-top: 1px; border-radius: 50%;
|
|
1452
|
+
border: 1.5px solid var(--dsw-alias-border-l3); }
|
|
1453
|
+
.dshsc-policyopt.active .dshsc-policyopt-dot { border-color: var(--dsw-alias-brand-primary);
|
|
1454
|
+
background: radial-gradient(circle, var(--dsw-alias-brand-primary) 0 40%, transparent 47%); }
|
|
1455
|
+
.dshsc-policyopt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1456
|
+
.dshsc-policyopt-body b { font-size: 12px; font-weight: 600; }
|
|
1457
|
+
.dshsc-policyopt-body small { font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
1458
|
+
.dshsc-policy-note { padding: 7px 9px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2);
|
|
1459
|
+
color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 15px; }
|
|
1460
|
+
.dshsc-policy-note.warn, .dshsc-policy-note.author { color: var(--dsw-alias-state-warn-primary);
|
|
1461
|
+
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 9%, transparent); }
|
|
1462
|
+
.dshsc-policy-hidden { display: flex; flex-direction: column; gap: 8px; }
|
|
1463
|
+
.dshsc-policy-restore { align-self: flex-start; }
|
|
1464
|
+
.dshsc-policy-foot { display: flex; justify-content: flex-end; gap: 6px; }
|
|
1465
|
+
.dshsc-policy-confirm { display: flex; flex-direction: column; gap: 6px; padding: 10px; border-radius: 9px;
|
|
1466
|
+
border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, var(--dsw-alias-border-l1));
|
|
1467
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 7%, transparent); }
|
|
1468
|
+
.dshsc-policy-confirm b { font-size: 12px; }
|
|
1469
|
+
.dshsc-policy-confirm p { margin: 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-secondary); }
|
|
1161
1470
|
|
|
1162
1471
|
.dshsc-overlay { position: fixed; inset: 0; background: rgba(4,6,9,.55); display: flex; align-items: center; justify-content: center;
|
|
1163
1472
|
z-index: 60; padding: 20px; backdrop-filter: blur(2px); }
|
|
@@ -1338,6 +1647,26 @@ var CSS = `
|
|
|
1338
1647
|
.dshsc-info-sec dd { margin: 0; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
1339
1648
|
.dshsc-info-sec dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1340
1649
|
|
|
1650
|
+
/* 列表行的 ✨ 介绍入口:只占一个小胶囊,不改变行高与列宽 */
|
|
1651
|
+
.dshsc-intro-btn { flex: none; display: inline-flex; align-items: center; gap: 3px;
|
|
1652
|
+
height: 19px; padding: 0 7px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
|
|
1653
|
+
background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; font-size: 11px;
|
|
1654
|
+
line-height: 1; cursor: pointer; white-space: nowrap; }
|
|
1655
|
+
.dshsc-intro-btn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3);
|
|
1656
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1657
|
+
.dshsc-intro-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1658
|
+
.dshsc-intro-btn .dshsc-spark { font-size: 10px; color: var(--dsw-alias-brand-primary); }
|
|
1659
|
+
|
|
1660
|
+
/* 介绍浮层:复用 policy 浮层的外壳(fixed + portal),只调内容排版 */
|
|
1661
|
+
.dshsc-intro-pop { width: min(360px, calc(100vw - 16px)); gap: 7px; }
|
|
1662
|
+
.dshsc-intro-text { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--dsw-alias-label-primary);
|
|
1663
|
+
white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1664
|
+
.dshsc-intro-note { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary); }
|
|
1665
|
+
.dshsc-intro-meta { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
1666
|
+
.dshsc-intro-stale { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-state-warn-primary); }
|
|
1667
|
+
.dshsc-intro-error { margin: 0; font-size: 11.5px; line-height: 16px; color: var(--dsw-alias-state-error-primary); }
|
|
1668
|
+
.dshsc-intro-foot { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 3px; }
|
|
1669
|
+
|
|
1341
1670
|
/* 正文排版:只用 token,不写死深浅色 */
|
|
1342
1671
|
.dshsc-md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-primary); }
|
|
1343
1672
|
.dshsc-md-h { margin: 20px 0 8px; line-height: 1.35; font-weight: 650; letter-spacing: -.008em; }
|
|
@@ -1465,6 +1794,34 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1465
1794
|
const mode = row.enabled === false ? "on" : "off";
|
|
1466
1795
|
await run(`mode:${row.name}`, () => apiCall("POST", `/skills/${encodeURIComponent(row.name)}/mode`, { mode }));
|
|
1467
1796
|
}
|
|
1797
|
+
async function setInvocation(row, mode) {
|
|
1798
|
+
await run(`invocation:${row.name}`, async () => {
|
|
1799
|
+
const result = await apiCall("POST", `/skills/${encodeURIComponent(row.name)}/invocation`, { mode });
|
|
1800
|
+
const saved = mode === "hidden" ? t("policy.savedHidden") : mode === "manual" ? t("policy.savedManual") : t("policy.savedAuto");
|
|
1801
|
+
setToast({ kind: "ok", text: `${row.name} · ${saved}` });
|
|
1802
|
+
return result;
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
async function unpark(row) {
|
|
1806
|
+
await run(`unpark:${row.name}`, async () => {
|
|
1807
|
+
const result = await apiCall("POST", `/skills/${encodeURIComponent(row.name)}/unpark`, {});
|
|
1808
|
+
setToast({
|
|
1809
|
+
kind: result.hashMismatch ? "err" : "ok",
|
|
1810
|
+
text: result.kept ? `${row.name} · ${t("policy.unparkKept").replace("{path}", result.kept)}` : result.hashMismatch ? `${row.name} · ${t("policy.unparkDrift")}` : `${row.name} · ${t("policy.unparkDone")}`
|
|
1811
|
+
});
|
|
1812
|
+
return result;
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
function requestUnpark(row) {
|
|
1816
|
+
setModal({
|
|
1817
|
+
type: "confirm-unpark",
|
|
1818
|
+
row,
|
|
1819
|
+
onConfirm: () => {
|
|
1820
|
+
setModal(null);
|
|
1821
|
+
void unpark(row);
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1468
1825
|
async function updateOne(row, overwrite) {
|
|
1469
1826
|
await run(`update:${row.name}`, async () => {
|
|
1470
1827
|
const result = await apiCall("POST", "/updates/apply", { name: row.name, overwrite });
|
|
@@ -1592,6 +1949,8 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1592
1949
|
return {
|
|
1593
1950
|
adopt,
|
|
1594
1951
|
toggleMode,
|
|
1952
|
+
setInvocation,
|
|
1953
|
+
requestUnpark,
|
|
1595
1954
|
requestUpdate,
|
|
1596
1955
|
updateAll,
|
|
1597
1956
|
checkUpdates,
|
|
@@ -1825,6 +2184,70 @@ function useSkillReader(row) {
|
|
|
1825
2184
|
}, []);
|
|
1826
2185
|
return { state, file, loadFile, retry };
|
|
1827
2186
|
}
|
|
2187
|
+
function useSkillIntro({ name, lang, stamp, enabled = true, endpoint = null }) {
|
|
2188
|
+
const base = endpoint || `/skills/${encodeURIComponent(name)}/intro`;
|
|
2189
|
+
const [state, setState] = import_react2.default.useState({ loading: true, intro: null, capability: null, error: null });
|
|
2190
|
+
const [generating, setGenerating] = import_react2.default.useState(false);
|
|
2191
|
+
const [genError, setGenError] = import_react2.default.useState(null);
|
|
2192
|
+
const seq = import_react2.default.useRef(0);
|
|
2193
|
+
const controller = import_react2.default.useRef(null);
|
|
2194
|
+
import_react2.default.useEffect(() => {
|
|
2195
|
+
if (!name || !enabled) {
|
|
2196
|
+
setState({ loading: false, intro: null, capability: null, error: null });
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const ticket = seq.current + 1;
|
|
2200
|
+
seq.current = ticket;
|
|
2201
|
+
setState({ loading: true, intro: null, capability: null, error: null });
|
|
2202
|
+
void (async () => {
|
|
2203
|
+
try {
|
|
2204
|
+
const result = await apiCall("GET", `${base}?lang=${encodeURIComponent(lang || "zh")}`);
|
|
2205
|
+
if (ticket !== seq.current) return;
|
|
2206
|
+
setState({ loading: false, intro: result.intro || null, capability: result.capability || null, error: null });
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
if (ticket !== seq.current) return;
|
|
2209
|
+
setState({
|
|
2210
|
+
loading: false,
|
|
2211
|
+
intro: null,
|
|
2212
|
+
capability: null,
|
|
2213
|
+
error: { code: error?.code || null, message: error?.message || "读取失败" }
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
})();
|
|
2217
|
+
}, [name, lang, stamp, enabled, base]);
|
|
2218
|
+
import_react2.default.useEffect(() => () => {
|
|
2219
|
+
if (controller.current) {
|
|
2220
|
+
controller.current.abort();
|
|
2221
|
+
controller.current = null;
|
|
2222
|
+
}
|
|
2223
|
+
}, [name, lang, base]);
|
|
2224
|
+
const generate = import_react2.default.useCallback(async (force = false) => {
|
|
2225
|
+
if (!name) return null;
|
|
2226
|
+
if (controller.current) return null;
|
|
2227
|
+
const ctrl = new AbortController();
|
|
2228
|
+
controller.current = ctrl;
|
|
2229
|
+
setGenerating(true);
|
|
2230
|
+
setGenError(null);
|
|
2231
|
+
try {
|
|
2232
|
+
const result = await apiCall(
|
|
2233
|
+
"POST",
|
|
2234
|
+
base,
|
|
2235
|
+
{ lang: lang || "zh", force: Boolean(force) },
|
|
2236
|
+
{ signal: ctrl.signal }
|
|
2237
|
+
);
|
|
2238
|
+
setState({ loading: false, intro: result.intro || null, capability: result.capability || null, error: null });
|
|
2239
|
+
return result;
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
if (error?.name === "AbortError") return null;
|
|
2242
|
+
setGenError({ code: error?.code || null, message: error?.message || "生成失败" });
|
|
2243
|
+
return null;
|
|
2244
|
+
} finally {
|
|
2245
|
+
if (controller.current === ctrl) controller.current = null;
|
|
2246
|
+
setGenerating(false);
|
|
2247
|
+
}
|
|
2248
|
+
}, [name, lang, base]);
|
|
2249
|
+
return { ...state, generating, genError, generate };
|
|
2250
|
+
}
|
|
1828
2251
|
|
|
1829
2252
|
// client/views/header.js
|
|
1830
2253
|
function installSpec(plugin) {
|
|
@@ -2017,6 +2440,313 @@ function Toolbar({
|
|
|
2017
2440
|
);
|
|
2018
2441
|
}
|
|
2019
2442
|
|
|
2443
|
+
// client/views/installed.js
|
|
2444
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
2445
|
+
|
|
2446
|
+
// client/views/intro-popover.js
|
|
2447
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
2448
|
+
var ERROR_KEY = {
|
|
2449
|
+
"error.skills.intro-no-model": "intro.error.noModel",
|
|
2450
|
+
"error.skills.intro-rate-limit": "intro.error.rateLimit",
|
|
2451
|
+
"error.skills.intro-quota": "intro.error.rateLimit",
|
|
2452
|
+
"error.skills.intro-too-large": "intro.error.tooLarge",
|
|
2453
|
+
"error.skills.intro-timeout": "intro.error.timeout",
|
|
2454
|
+
"error.skills.intro-auth": "intro.error.auth",
|
|
2455
|
+
"error.skills.intro-empty": "intro.error.empty",
|
|
2456
|
+
"error.skills.intro-busy": "intro.error.busy",
|
|
2457
|
+
"error.skills.intro-source": "intro.error.source",
|
|
2458
|
+
"error.skills.subscription-not-found": "intro.error.subscription",
|
|
2459
|
+
"error.skills.intro-source-timeout": "intro.error.sourceTimeout",
|
|
2460
|
+
"error.skills.intro-no-source": "intro.error.noSource"
|
|
2461
|
+
};
|
|
2462
|
+
function formatWhen(iso) {
|
|
2463
|
+
if (!iso) return null;
|
|
2464
|
+
const date = new Date(iso);
|
|
2465
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
2466
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
2467
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
2468
|
+
}
|
|
2469
|
+
function modelLabel(intro) {
|
|
2470
|
+
if (!intro?.model) return "—";
|
|
2471
|
+
return intro.provider ? `${intro.provider}/${intro.model}` : intro.model;
|
|
2472
|
+
}
|
|
2473
|
+
function IntroPopover({
|
|
2474
|
+
t,
|
|
2475
|
+
name,
|
|
2476
|
+
stamp = "",
|
|
2477
|
+
subtitle = null,
|
|
2478
|
+
endpoint = null,
|
|
2479
|
+
capability,
|
|
2480
|
+
anchor,
|
|
2481
|
+
anchorEl,
|
|
2482
|
+
hint = null,
|
|
2483
|
+
viewLabel = null,
|
|
2484
|
+
onClose,
|
|
2485
|
+
onView = null
|
|
2486
|
+
}) {
|
|
2487
|
+
const ref = import_react3.default.useRef(null);
|
|
2488
|
+
const [pos, setPos] = import_react3.default.useState({ left: 0, top: 0, visibility: "hidden" });
|
|
2489
|
+
const enabled = capability?.enabled !== false;
|
|
2490
|
+
const available = capability?.available !== false;
|
|
2491
|
+
const intro = useSkillIntro({
|
|
2492
|
+
name,
|
|
2493
|
+
lang: t.lang || "zh",
|
|
2494
|
+
stamp,
|
|
2495
|
+
enabled,
|
|
2496
|
+
endpoint
|
|
2497
|
+
});
|
|
2498
|
+
useOverlayLayer(true, onClose);
|
|
2499
|
+
import_react3.default.useLayoutEffect(() => {
|
|
2500
|
+
const node = ref.current;
|
|
2501
|
+
if (!node || !anchor || typeof window === "undefined") return;
|
|
2502
|
+
const rect = node.getBoundingClientRect();
|
|
2503
|
+
const width = rect.width || 360;
|
|
2504
|
+
const height = rect.height || 220;
|
|
2505
|
+
let left = anchor.left;
|
|
2506
|
+
left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
|
|
2507
|
+
let top = anchor.bottom + 8;
|
|
2508
|
+
if (top + height > window.innerHeight - 8) {
|
|
2509
|
+
top = Math.max(8, anchor.top - height - 8);
|
|
2510
|
+
}
|
|
2511
|
+
setPos({ left, top, visibility: "visible" });
|
|
2512
|
+
}, [anchor, intro.loading, intro.intro?.text, intro.generating, intro.genError]);
|
|
2513
|
+
import_react3.default.useEffect(() => {
|
|
2514
|
+
if (typeof document === "undefined") return void 0;
|
|
2515
|
+
const onPointerDown = (event) => {
|
|
2516
|
+
const target = event?.target;
|
|
2517
|
+
if (ref.current?.contains?.(target)) return;
|
|
2518
|
+
if (anchorEl?.contains?.(target)) return;
|
|
2519
|
+
onClose();
|
|
2520
|
+
};
|
|
2521
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2522
|
+
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2523
|
+
}, [anchorEl, onClose]);
|
|
2524
|
+
const failure = intro.genError || intro.error;
|
|
2525
|
+
const failureKey = failure?.code ? ERROR_KEY[failure.code] || "intro.error.generic" : null;
|
|
2526
|
+
const meta = intro.intro ? t("intro.meta").replace("{model}", modelLabel(intro.intro)).replace("{time}", formatWhen(intro.intro.generatedAt) || "—") : "";
|
|
2527
|
+
const actions = [];
|
|
2528
|
+
if (intro.intro) {
|
|
2529
|
+
actions.push(h("button", {
|
|
2530
|
+
type: "button",
|
|
2531
|
+
key: "regen",
|
|
2532
|
+
className: "dshsc-btn ghost",
|
|
2533
|
+
disabled: intro.generating || !available,
|
|
2534
|
+
onClick: () => {
|
|
2535
|
+
void intro.generate(true);
|
|
2536
|
+
}
|
|
2537
|
+
}, intro.generating ? t("intro.generating") : t("intro.regenerate")));
|
|
2538
|
+
if (onView) {
|
|
2539
|
+
actions.push(h("button", {
|
|
2540
|
+
type: "button",
|
|
2541
|
+
key: "view",
|
|
2542
|
+
className: "dshsc-btn primary",
|
|
2543
|
+
onClick: onView
|
|
2544
|
+
}, viewLabel || t("intro.view")));
|
|
2545
|
+
}
|
|
2546
|
+
} else {
|
|
2547
|
+
actions.push(h("button", {
|
|
2548
|
+
type: "button",
|
|
2549
|
+
key: "gen",
|
|
2550
|
+
className: "dshsc-btn primary",
|
|
2551
|
+
disabled: intro.generating || !available || !enabled,
|
|
2552
|
+
onClick: () => {
|
|
2553
|
+
void intro.generate(false);
|
|
2554
|
+
}
|
|
2555
|
+
}, intro.generating ? t("intro.generating") : t("intro.generate")));
|
|
2556
|
+
}
|
|
2557
|
+
return portalize(h(
|
|
2558
|
+
"div",
|
|
2559
|
+
{
|
|
2560
|
+
ref,
|
|
2561
|
+
className: "dshsc-policy-pop dshsc-intro-pop",
|
|
2562
|
+
role: "dialog",
|
|
2563
|
+
"aria-label": `${t("intro.title")} · ${name}`,
|
|
2564
|
+
style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
|
|
2565
|
+
},
|
|
2566
|
+
h(
|
|
2567
|
+
"div",
|
|
2568
|
+
{ className: "dshsc-policy-head" },
|
|
2569
|
+
h(
|
|
2570
|
+
"div",
|
|
2571
|
+
null,
|
|
2572
|
+
h("div", { className: "dshsc-policy-title" }, `✨ ${t("intro.title")}`),
|
|
2573
|
+
h("div", { className: "dshsc-policy-sub", title: subtitle || name }, subtitle || name)
|
|
2574
|
+
),
|
|
2575
|
+
h("button", {
|
|
2576
|
+
type: "button",
|
|
2577
|
+
className: "dshsc-policy-close",
|
|
2578
|
+
"aria-label": t("action.close"),
|
|
2579
|
+
onClick: onClose
|
|
2580
|
+
}, "×")
|
|
2581
|
+
),
|
|
2582
|
+
intro.loading ? h("p", { className: "dshsc-intro-note" }, t("intro.loading")) : intro.intro ? h("p", { className: "dshsc-intro-text" }, intro.intro.text) : h("p", { className: "dshsc-intro-note" }, t("intro.empty")),
|
|
2583
|
+
intro.intro?.stale ? h("p", { className: "dshsc-intro-stale" }, t("intro.stale")) : null,
|
|
2584
|
+
failureKey ? h("p", { className: "dshsc-intro-error" }, t(failureKey)) : null,
|
|
2585
|
+
meta ? h("p", { className: "dshsc-intro-meta" }, meta) : null,
|
|
2586
|
+
available && !intro.intro && !intro.loading ? h("p", { className: "dshsc-intro-meta" }, hint || t("intro.hint")) : null,
|
|
2587
|
+
!available ? h("p", { className: "dshsc-intro-error" }, t("intro.unavailable")) : null,
|
|
2588
|
+
h("div", { className: "dshsc-intro-foot" }, actions)
|
|
2589
|
+
));
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// client/views/policy-popover.js
|
|
2593
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
2594
|
+
function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpark }) {
|
|
2595
|
+
const initial = row.mode === "hidden" ? "auto" : row.mode;
|
|
2596
|
+
const [mode, setMode] = import_react4.default.useState(initial);
|
|
2597
|
+
const [confirming, setConfirming] = import_react4.default.useState(false);
|
|
2598
|
+
const ref = import_react4.default.useRef(null);
|
|
2599
|
+
const [pos, setPos] = import_react4.default.useState({ left: 0, top: 0, visibility: "hidden" });
|
|
2600
|
+
useOverlayLayer(true, onClose);
|
|
2601
|
+
import_react4.default.useLayoutEffect(() => {
|
|
2602
|
+
const node = ref.current;
|
|
2603
|
+
if (!node || !anchor || typeof window === "undefined") return;
|
|
2604
|
+
const rect = node.getBoundingClientRect();
|
|
2605
|
+
const width = rect.width || 360;
|
|
2606
|
+
const height = rect.height || 320;
|
|
2607
|
+
let left = anchor.left + anchor.width / 2 - width / 2;
|
|
2608
|
+
left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
|
|
2609
|
+
let top = anchor.bottom + 8;
|
|
2610
|
+
if (top + height > window.innerHeight - 8) {
|
|
2611
|
+
top = Math.max(8, anchor.top - height - 8);
|
|
2612
|
+
}
|
|
2613
|
+
setPos({ left, top, visibility: "visible" });
|
|
2614
|
+
}, [anchor, confirming, mode]);
|
|
2615
|
+
import_react4.default.useEffect(() => {
|
|
2616
|
+
if (typeof document === "undefined") return void 0;
|
|
2617
|
+
const onPointerDown = (event) => {
|
|
2618
|
+
const target = event?.target;
|
|
2619
|
+
if (ref.current?.contains?.(target)) return;
|
|
2620
|
+
if (anchorEl?.contains?.(target)) return;
|
|
2621
|
+
onClose();
|
|
2622
|
+
};
|
|
2623
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2624
|
+
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2625
|
+
}, [anchorEl, onClose]);
|
|
2626
|
+
const hidden = row.mode === "hidden";
|
|
2627
|
+
const author = row.origin === "author" && row.mode === "manual";
|
|
2628
|
+
const flat = row.kind === "file";
|
|
2629
|
+
const dirty = mode !== initial || Boolean(row.legacy) || Boolean(row.drift);
|
|
2630
|
+
function choose(next) {
|
|
2631
|
+
setMode(next);
|
|
2632
|
+
setConfirming(false);
|
|
2633
|
+
}
|
|
2634
|
+
function submit() {
|
|
2635
|
+
if (mode === "hidden" && !hidden && !confirming) {
|
|
2636
|
+
setConfirming(true);
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
onSave(mode);
|
|
2640
|
+
}
|
|
2641
|
+
const option = (id, label, desc) => h(
|
|
2642
|
+
"button",
|
|
2643
|
+
{
|
|
2644
|
+
type: "button",
|
|
2645
|
+
key: `policy-${id}`,
|
|
2646
|
+
className: `dshsc-policyopt${mode === id ? " active" : ""}`,
|
|
2647
|
+
"aria-pressed": String(mode === id),
|
|
2648
|
+
disabled: busy || flat,
|
|
2649
|
+
onClick: () => choose(id)
|
|
2650
|
+
},
|
|
2651
|
+
h("span", { className: "dshsc-policyopt-dot", "aria-hidden": "true" }),
|
|
2652
|
+
h(
|
|
2653
|
+
"span",
|
|
2654
|
+
{ className: "dshsc-policyopt-body" },
|
|
2655
|
+
h("b", null, label),
|
|
2656
|
+
h("small", null, desc)
|
|
2657
|
+
)
|
|
2658
|
+
);
|
|
2659
|
+
return portalize(h(
|
|
2660
|
+
"div",
|
|
2661
|
+
{
|
|
2662
|
+
ref,
|
|
2663
|
+
className: "dshsc-policy-pop",
|
|
2664
|
+
role: "dialog",
|
|
2665
|
+
"aria-label": t("policy.title"),
|
|
2666
|
+
style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
|
|
2667
|
+
},
|
|
2668
|
+
h(
|
|
2669
|
+
"div",
|
|
2670
|
+
{ className: "dshsc-policy-head" },
|
|
2671
|
+
h(
|
|
2672
|
+
"div",
|
|
2673
|
+
null,
|
|
2674
|
+
h("div", { className: "dshsc-policy-title" }, t("policy.title")),
|
|
2675
|
+
h("div", { className: "dshsc-policy-sub" }, t("policy.subtitle"))
|
|
2676
|
+
),
|
|
2677
|
+
h("button", {
|
|
2678
|
+
type: "button",
|
|
2679
|
+
className: "dshsc-policy-close",
|
|
2680
|
+
"aria-label": t("action.close"),
|
|
2681
|
+
onClick: onClose
|
|
2682
|
+
}, "×")
|
|
2683
|
+
),
|
|
2684
|
+
author ? h("div", { className: "dshsc-policy-note author" }, t("policy.authorHint")) : null,
|
|
2685
|
+
row.legacy ? h("div", { className: "dshsc-policy-note" }, t("policy.legacyHint")) : null,
|
|
2686
|
+
row.drift ? h("div", { className: "dshsc-policy-note warn" }, t("policy.driftHint")) : null,
|
|
2687
|
+
hidden ? h(
|
|
2688
|
+
"div",
|
|
2689
|
+
{ className: "dshsc-policy-hidden" },
|
|
2690
|
+
h(
|
|
2691
|
+
"div",
|
|
2692
|
+
{ className: "dshsc-policyopt active static" },
|
|
2693
|
+
h("span", { className: "dshsc-policyopt-dot" }),
|
|
2694
|
+
h(
|
|
2695
|
+
"span",
|
|
2696
|
+
{ className: "dshsc-policyopt-body" },
|
|
2697
|
+
h("b", null, t("policy.hidden")),
|
|
2698
|
+
h("small", null, t("policy.hiddenDesc"))
|
|
2699
|
+
)
|
|
2700
|
+
),
|
|
2701
|
+
h("button", {
|
|
2702
|
+
type: "button",
|
|
2703
|
+
className: "dshsc-btn primary dshsc-policy-restore",
|
|
2704
|
+
disabled: busy,
|
|
2705
|
+
onClick: onUnpark
|
|
2706
|
+
}, t("policy.unpark")),
|
|
2707
|
+
h("p", { className: "dshsc-policy-sub" }, t("policy.hiddenHint"))
|
|
2708
|
+
) : h(
|
|
2709
|
+
"div",
|
|
2710
|
+
{ className: "dshsc-policy-options" },
|
|
2711
|
+
option("auto", t("policy.auto"), t("policy.autoDesc")),
|
|
2712
|
+
option("manual", t("policy.manual"), t("policy.manualDesc")),
|
|
2713
|
+
option("hidden", t("policy.hidden"), t("policy.hiddenDesc"))
|
|
2714
|
+
),
|
|
2715
|
+
confirming ? h(
|
|
2716
|
+
"div",
|
|
2717
|
+
{ className: "dshsc-policy-confirm" },
|
|
2718
|
+
h("b", null, t("policy.hiddenConfirmTitle")),
|
|
2719
|
+
h("p", null, t("policy.hiddenConfirmBody")),
|
|
2720
|
+
h(
|
|
2721
|
+
"div",
|
|
2722
|
+
{ className: "dshsc-policy-foot" },
|
|
2723
|
+
h("button", {
|
|
2724
|
+
type: "button",
|
|
2725
|
+
className: "dshsc-btn ghost",
|
|
2726
|
+
disabled: busy,
|
|
2727
|
+
onClick: () => setConfirming(false)
|
|
2728
|
+
}, t("action.cancel")),
|
|
2729
|
+
h("button", {
|
|
2730
|
+
type: "button",
|
|
2731
|
+
className: "dshsc-btn danger",
|
|
2732
|
+
disabled: busy,
|
|
2733
|
+
onClick: () => onSave("hidden")
|
|
2734
|
+
}, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
|
|
2735
|
+
)
|
|
2736
|
+
) : h(
|
|
2737
|
+
"div",
|
|
2738
|
+
{ className: "dshsc-policy-foot" },
|
|
2739
|
+
h("button", { type: "button", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
|
|
2740
|
+
hidden ? null : h("button", {
|
|
2741
|
+
type: "button",
|
|
2742
|
+
className: "dshsc-btn primary",
|
|
2743
|
+
disabled: busy || !dirty || flat,
|
|
2744
|
+
onClick: submit
|
|
2745
|
+
}, busy ? t("policy.saving") : t("action.save"))
|
|
2746
|
+
)
|
|
2747
|
+
), overlayHost());
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2020
2750
|
// client/views/installed.js
|
|
2021
2751
|
var UPDATABLE = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
2022
2752
|
function InstalledTab({
|
|
@@ -2036,7 +2766,102 @@ function InstalledTab({
|
|
|
2036
2766
|
actions
|
|
2037
2767
|
}) {
|
|
2038
2768
|
const updatable = (row) => UPDATABLE.has(row.status);
|
|
2769
|
+
const [policy, setPolicy] = import_react5.default.useState(null);
|
|
2770
|
+
const [intro, setIntro] = import_react5.default.useState(null);
|
|
2039
2771
|
const sharedTargets = new Set((data?.agents?.detected || []).filter((agent) => agent.kind === "agent" && agent.sharedHome === "yes").map((agent) => agent.target));
|
|
2772
|
+
import_react5.default.useEffect(() => {
|
|
2773
|
+
if (!policy) return;
|
|
2774
|
+
if (rows.some((row) => row.name === policy.row.name)) return;
|
|
2775
|
+
setPolicy(null);
|
|
2776
|
+
}, [policy, rows]);
|
|
2777
|
+
import_react5.default.useEffect(() => {
|
|
2778
|
+
if (!intro) return;
|
|
2779
|
+
if (rows.some((row) => row.name === intro.row.name)) return;
|
|
2780
|
+
setIntro(null);
|
|
2781
|
+
}, [intro, rows]);
|
|
2782
|
+
function openIntro(event, row) {
|
|
2783
|
+
const el = event.currentTarget;
|
|
2784
|
+
const rect = el.getBoundingClientRect();
|
|
2785
|
+
setIntro({
|
|
2786
|
+
row,
|
|
2787
|
+
anchorEl: el,
|
|
2788
|
+
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
function openPolicy(event, row) {
|
|
2792
|
+
if (busy !== null || row.kind === "file") return;
|
|
2793
|
+
if (row.mode === "manual" && row.origin === "author") return;
|
|
2794
|
+
const el = event.currentTarget;
|
|
2795
|
+
const rect = el.getBoundingClientRect();
|
|
2796
|
+
setPolicy({
|
|
2797
|
+
row,
|
|
2798
|
+
anchorEl: el,
|
|
2799
|
+
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
function policyChip(row) {
|
|
2803
|
+
if (row.status === "unmanaged") {
|
|
2804
|
+
return h("span", { className: "dshsc-chip muted", title: t("adopt.switchHint") }, t("policy.unmanaged"));
|
|
2805
|
+
}
|
|
2806
|
+
if (row.kind === "file") {
|
|
2807
|
+
return h("span", { className: "dshsc-chip muted", title: t("policy.flat") }, t("policy.flat"));
|
|
2808
|
+
}
|
|
2809
|
+
const mode = row.mode || "auto";
|
|
2810
|
+
if (mode === "hidden") {
|
|
2811
|
+
return h("button", {
|
|
2812
|
+
type: "button",
|
|
2813
|
+
className: "dshsc-chip hidden",
|
|
2814
|
+
disabled: busy !== null,
|
|
2815
|
+
title: t("policy.hiddenHint"),
|
|
2816
|
+
"aria-haspopup": "dialog",
|
|
2817
|
+
onClick: (event) => openPolicy(event, row)
|
|
2818
|
+
}, t("policy.hidden"));
|
|
2819
|
+
}
|
|
2820
|
+
if (mode === "manual" && row.origin === "author") {
|
|
2821
|
+
return h("span", {
|
|
2822
|
+
className: "dshsc-chip author",
|
|
2823
|
+
title: t("policy.authorHint")
|
|
2824
|
+
}, t("policy.author"));
|
|
2825
|
+
}
|
|
2826
|
+
if (row.drift) {
|
|
2827
|
+
return h("button", {
|
|
2828
|
+
type: "button",
|
|
2829
|
+
className: "dshsc-chip drift",
|
|
2830
|
+
disabled: busy !== null,
|
|
2831
|
+
title: t("policy.driftHint"),
|
|
2832
|
+
"aria-haspopup": "dialog",
|
|
2833
|
+
onClick: (event) => openPolicy(event, row)
|
|
2834
|
+
}, t("policy.drift"));
|
|
2835
|
+
}
|
|
2836
|
+
if (row.legacy === "dsh-off") {
|
|
2837
|
+
return h("button", {
|
|
2838
|
+
type: "button",
|
|
2839
|
+
className: "dshsc-chip legacy",
|
|
2840
|
+
disabled: busy !== null,
|
|
2841
|
+
title: t("policy.legacyHint"),
|
|
2842
|
+
"aria-haspopup": "dialog",
|
|
2843
|
+
onClick: (event) => openPolicy(event, row)
|
|
2844
|
+
}, t("policy.legacy"));
|
|
2845
|
+
}
|
|
2846
|
+
if (mode === "manual") {
|
|
2847
|
+
return h("button", {
|
|
2848
|
+
type: "button",
|
|
2849
|
+
className: "dshsc-chip manual",
|
|
2850
|
+
disabled: busy !== null,
|
|
2851
|
+
title: t("policy.manualOnlyHint"),
|
|
2852
|
+
"aria-haspopup": "dialog",
|
|
2853
|
+
onClick: (event) => openPolicy(event, row)
|
|
2854
|
+
}, t("policy.manual"));
|
|
2855
|
+
}
|
|
2856
|
+
return h("button", {
|
|
2857
|
+
type: "button",
|
|
2858
|
+
className: "dshsc-chip auto",
|
|
2859
|
+
disabled: busy !== null,
|
|
2860
|
+
title: t("policy.open"),
|
|
2861
|
+
"aria-haspopup": "dialog",
|
|
2862
|
+
onClick: (event) => openPolicy(event, row)
|
|
2863
|
+
}, t("policy.auto"));
|
|
2864
|
+
}
|
|
2040
2865
|
function statusTag(row) {
|
|
2041
2866
|
if (row.status === "unknown") return null;
|
|
2042
2867
|
if (row.status === "no-upstream") {
|
|
@@ -2104,9 +2929,32 @@ function InstalledTab({
|
|
|
2104
2929
|
}
|
|
2105
2930
|
return chips;
|
|
2106
2931
|
}
|
|
2932
|
+
function headView() {
|
|
2933
|
+
return h(
|
|
2934
|
+
"div",
|
|
2935
|
+
{ className: "dshsc-listhead" },
|
|
2936
|
+
h(
|
|
2937
|
+
"div",
|
|
2938
|
+
{ className: "dshsc-hskill" },
|
|
2939
|
+
h("span", { className: "dshsc-col-wide" }, t("column.skill")),
|
|
2940
|
+
h("span", { className: "dshsc-col-narrow" }, t("column.skillAgents"))
|
|
2941
|
+
),
|
|
2942
|
+
h("div", { className: "dshsc-hagents" }, t("column.agents")),
|
|
2943
|
+
h("div", { className: "dshsc-hpolicy" }, t("column.policy")),
|
|
2944
|
+
h(
|
|
2945
|
+
"div",
|
|
2946
|
+
{ className: "dshsc-hactions" },
|
|
2947
|
+
h("span", { className: "dshsc-col-wide" }, t("column.actions")),
|
|
2948
|
+
h("span", { className: "dshsc-col-narrow" }, t("column.policyActions"))
|
|
2949
|
+
)
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
function listView(listRows) {
|
|
2953
|
+
return h("div", { className: "dshsc-list" }, headView(), listRows);
|
|
2954
|
+
}
|
|
2107
2955
|
function rowView(row) {
|
|
2108
|
-
const
|
|
2109
|
-
const
|
|
2956
|
+
const parked = row.mode === "hidden";
|
|
2957
|
+
const off = parked || row.enabled === false;
|
|
2110
2958
|
const canUpdate = writable && updatable(row);
|
|
2111
2959
|
const hidden = !off && !row.visible;
|
|
2112
2960
|
const manualOnly = hidden && Boolean(row.visibility?.selfHidden);
|
|
@@ -2133,18 +2981,25 @@ function InstalledTab({
|
|
|
2133
2981
|
"aria-haspopup": "dialog",
|
|
2134
2982
|
onClick: () => actions.onView(row)
|
|
2135
2983
|
}, row.name),
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2984
|
+
data?.intro ? h(
|
|
2985
|
+
"button",
|
|
2986
|
+
{
|
|
2987
|
+
type: "button",
|
|
2988
|
+
className: "dshsc-intro-btn",
|
|
2989
|
+
title: t("intro.open"),
|
|
2990
|
+
"aria-haspopup": "dialog",
|
|
2991
|
+
onClick: (event) => openIntro(event, row)
|
|
2992
|
+
},
|
|
2993
|
+
h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
|
|
2994
|
+
t("intro.button")
|
|
2995
|
+
) : null,
|
|
2996
|
+
statusTag(row)
|
|
2143
2997
|
),
|
|
2144
2998
|
sourceLine(row),
|
|
2145
|
-
off ? h("div", { className: "dshsc-note" }, t("
|
|
2146
|
-
|
|
2147
|
-
|
|
2999
|
+
parked ? h("div", { className: "dshsc-note" }, t("policy.hiddenNote")) : row.legacy === "dsh-off" ? h("div", { className: "dshsc-note" }, t("policy.legacyNote")) : null,
|
|
3000
|
+
row.origin === "author" && row.mode === "manual" ? h("div", { className: "dshsc-note" }, t("policy.authorNote")) : null,
|
|
3001
|
+
row.drift ? h("div", { className: "dshsc-note warn" }, t("policy.driftNote")) : null,
|
|
3002
|
+
// 仅手动用行内 chip 表达;被更靠前的同名副本盖住时由下面的 foreignWinner 点名。
|
|
2148
3003
|
hidden && !manualOnly && row.visibility?.winnerIsCanonical !== false ? h("div", { className: "dshsc-note warn" }, t("visibility.hiddenHint")) : null,
|
|
2149
3004
|
row.visibility?.shadowScopeMismatch ? h("div", { className: "dshsc-note warn" }, t("visibility.scopeMismatch")) : null,
|
|
2150
3005
|
// 同名副本在更小的 rank 上:模型看到的不是这一份,必须点名路径
|
|
@@ -2156,53 +3011,79 @@ function InstalledTab({
|
|
|
2156
3011
|
row.status === "broken-link" ? h("div", { className: "dshsc-note warn" }, t("status.brokenHint")) : null
|
|
2157
3012
|
),
|
|
2158
3013
|
h("div", { className: "dshsc-agents" }, agentChips(row)),
|
|
3014
|
+
h("div", { className: "dshsc-policy" }, policyChip(row)),
|
|
2159
3015
|
h(
|
|
2160
3016
|
"div",
|
|
2161
3017
|
{ className: "dshsc-actions" },
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
3018
|
+
parked ? [
|
|
3019
|
+
h("button", {
|
|
3020
|
+
key: "restore",
|
|
3021
|
+
className: "dshsc-btn ghost sq dshsc-restore",
|
|
3022
|
+
disabled: !writable || busy !== null,
|
|
3023
|
+
title: t("policy.unpark"),
|
|
3024
|
+
"aria-label": `${t("policy.unpark")} ${row.name}`,
|
|
3025
|
+
onClick: () => actions.onRequestUnpark(row)
|
|
3026
|
+
}, h(RefreshIcon, { size: 13 })),
|
|
3027
|
+
h("button", {
|
|
3028
|
+
key: "delete",
|
|
3029
|
+
className: "dshsc-btn ghost danger sq",
|
|
3030
|
+
disabled: !writable || busy !== null,
|
|
3031
|
+
title: t("action.delete"),
|
|
3032
|
+
"aria-label": `${t("action.delete")} ${row.name}`,
|
|
3033
|
+
onClick: () => actions.onDelete(row)
|
|
3034
|
+
}, h(TrashIcon, { size: 13 }))
|
|
3035
|
+
] : [
|
|
3036
|
+
h("button", {
|
|
3037
|
+
key: "view",
|
|
3038
|
+
className: "dshsc-btn ghost sq dshsc-view",
|
|
3039
|
+
disabled: busy !== null,
|
|
3040
|
+
"aria-haspopup": "dialog",
|
|
3041
|
+
title: t("action.view"),
|
|
3042
|
+
"aria-label": `${t("action.view")} ${row.name}`,
|
|
3043
|
+
onClick: () => actions.onView(row)
|
|
3044
|
+
}, h(EyeIcon, { size: 13 })),
|
|
3045
|
+
row.status === "unmanaged" ? h("button", {
|
|
3046
|
+
key: "adopt",
|
|
3047
|
+
className: "dshsc-btn ghost",
|
|
3048
|
+
disabled: !writable || busy !== null,
|
|
3049
|
+
title: row.source ? row.source : t("meta.none"),
|
|
3050
|
+
onClick: () => void actions.onAdopt(row)
|
|
3051
|
+
}, t("action.adopt")) : h("button", {
|
|
3052
|
+
key: "update",
|
|
3053
|
+
className: `dshsc-btn ghost sq${canUpdate ? " dshsc-update" : ""}`,
|
|
3054
|
+
disabled: !canUpdate || busy !== null,
|
|
3055
|
+
title: t("action.update"),
|
|
3056
|
+
"aria-label": `${t("action.update")} ${row.name}`,
|
|
3057
|
+
onClick: () => actions.onUpdate(row)
|
|
3058
|
+
}, h(DownloadIcon, { size: 13 })),
|
|
3059
|
+
h("button", {
|
|
3060
|
+
key: "exports",
|
|
3061
|
+
className: "dshsc-btn ghost sq",
|
|
3062
|
+
disabled: busy !== null,
|
|
3063
|
+
title: t("action.exports"),
|
|
3064
|
+
"aria-label": `${t("action.exports")} ${row.name}`,
|
|
3065
|
+
onClick: () => actions.onExports(row)
|
|
3066
|
+
}, h(ShareIcon, { size: 13 })),
|
|
3067
|
+
h("button", {
|
|
3068
|
+
key: "delete",
|
|
3069
|
+
className: "dshsc-btn ghost danger sq",
|
|
3070
|
+
disabled: !writable || busy !== null,
|
|
3071
|
+
title: t("action.delete"),
|
|
3072
|
+
"aria-label": `${t("action.delete")} ${row.name}`,
|
|
3073
|
+
onClick: () => actions.onDelete(row)
|
|
3074
|
+
}, h(TrashIcon, { size: 13 }))
|
|
3075
|
+
]
|
|
2197
3076
|
)
|
|
2198
3077
|
);
|
|
2199
3078
|
}
|
|
2200
3079
|
function groupedRows() {
|
|
2201
3080
|
const groups = [];
|
|
2202
|
-
const visibleGroup = visibleRows.filter((row) => row.enabled !== false && row.visible);
|
|
2203
|
-
const
|
|
2204
|
-
const
|
|
2205
|
-
|
|
3081
|
+
const visibleGroup = visibleRows.filter((row) => row.mode === "auto" && row.enabled !== false && row.visible);
|
|
3082
|
+
const manualRows = visibleRows.filter((row) => row.mode === "manual");
|
|
3083
|
+
const offRows = visibleRows.filter((row) => row.mode === "hidden" || row.enabled === false);
|
|
3084
|
+
const hiddenRows = visibleRows.filter((row) => row.mode === "auto" && row.enabled !== false && !row.visible);
|
|
3085
|
+
if (visibleGroup.length) groups.push({ key: "visible", title: t("group.visible"), rows: visibleGroup, dot: true });
|
|
3086
|
+
if (manualRows.length) groups.push({ key: "manual", title: t("group.manual"), rows: manualRows });
|
|
2206
3087
|
if (offRows.length) groups.push({ key: "off", title: t("group.off"), rows: offRows });
|
|
2207
3088
|
if (hiddenRows.length) groups.push({ key: "hidden", title: t("group.hidden"), rows: hiddenRows });
|
|
2208
3089
|
for (const agent of data?.agents?.detected || []) {
|
|
@@ -2225,6 +3106,7 @@ function InstalledTab({
|
|
|
2225
3106
|
}, dot, h("span", null, label), h("span", { className: "n" }, counts[id]));
|
|
2226
3107
|
}
|
|
2227
3108
|
function listBar() {
|
|
3109
|
+
const updateDot = counts.update > 0 ? h("i", { className: "dshsc-fdot" }) : null;
|
|
2228
3110
|
return h(
|
|
2229
3111
|
"div",
|
|
2230
3112
|
{ className: "dshsc-listbar" },
|
|
@@ -2232,8 +3114,9 @@ function InstalledTab({
|
|
|
2232
3114
|
"div",
|
|
2233
3115
|
{ className: "dshsc-filters" },
|
|
2234
3116
|
filterChip("all", t("filter.all")),
|
|
2235
|
-
filterChip("update", t("filter.update"),
|
|
3117
|
+
filterChip("update", t("filter.update"), updateDot),
|
|
2236
3118
|
filterChip("unknown", t("filter.unknown")),
|
|
3119
|
+
filterChip("manual", t("filter.manual")),
|
|
2237
3120
|
filterChip("off", t("filter.off"))
|
|
2238
3121
|
),
|
|
2239
3122
|
h(
|
|
@@ -2344,8 +3227,8 @@ function InstalledTab({
|
|
|
2344
3227
|
h("span", null, group.title),
|
|
2345
3228
|
h("span", { className: "n" }, group.rows.length)
|
|
2346
3229
|
),
|
|
2347
|
-
|
|
2348
|
-
))) :
|
|
3230
|
+
listView(group.rows.map(rowView))
|
|
3231
|
+
))) : listView(visibleRows.map(rowView));
|
|
2349
3232
|
return h(
|
|
2350
3233
|
"div",
|
|
2351
3234
|
{ className: "dshsc-tabpane" },
|
|
@@ -2359,7 +3242,39 @@ function InstalledTab({
|
|
|
2359
3242
|
)
|
|
2360
3243
|
);
|
|
2361
3244
|
}
|
|
2362
|
-
|
|
3245
|
+
const introPopover = intro && data?.intro ? h(IntroPopover, {
|
|
3246
|
+
t,
|
|
3247
|
+
name: intro.row.name,
|
|
3248
|
+
stamp: [intro.row.commit || "", intro.row.currentHash || "", intro.row.updatedAt || "", intro.row.status || ""].join("|"),
|
|
3249
|
+
anchor: intro.anchor,
|
|
3250
|
+
anchorEl: intro.anchorEl,
|
|
3251
|
+
capability: data.intro,
|
|
3252
|
+
onClose: () => setIntro(null),
|
|
3253
|
+
onView: () => {
|
|
3254
|
+
const target = intro.row;
|
|
3255
|
+
setIntro(null);
|
|
3256
|
+
actions.onView(target);
|
|
3257
|
+
}
|
|
3258
|
+
}) : null;
|
|
3259
|
+
const popover = policy ? h(PolicyPopover, {
|
|
3260
|
+
t,
|
|
3261
|
+
row: policy.row,
|
|
3262
|
+
anchor: policy.anchor,
|
|
3263
|
+
anchorEl: policy.anchorEl,
|
|
3264
|
+
busy: busy !== null,
|
|
3265
|
+
onClose: () => setPolicy(null),
|
|
3266
|
+
onSave: (mode) => {
|
|
3267
|
+
const target = policy.row;
|
|
3268
|
+
setPolicy(null);
|
|
3269
|
+
void actions.onSetInvocation(target, mode);
|
|
3270
|
+
},
|
|
3271
|
+
onUnpark: () => {
|
|
3272
|
+
const target = policy.row;
|
|
3273
|
+
setPolicy(null);
|
|
3274
|
+
actions.onRequestUnpark(target);
|
|
3275
|
+
}
|
|
3276
|
+
}) : null;
|
|
3277
|
+
return h(import_react5.default.Fragment, null, content(), popover, introPopover);
|
|
2363
3278
|
}
|
|
2364
3279
|
|
|
2365
3280
|
// client/views/agent-tip.js
|
|
@@ -2394,6 +3309,14 @@ function AgentTip({ t, agentTip, agentTipPos, nodeRef }) {
|
|
|
2394
3309
|
}
|
|
2395
3310
|
|
|
2396
3311
|
// client/views/subscriptions.js
|
|
3312
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
3313
|
+
function repoOf(source) {
|
|
3314
|
+
const value = String(source || "").trim().toLowerCase().replace(/\.git$/, "");
|
|
3315
|
+
return value || null;
|
|
3316
|
+
}
|
|
3317
|
+
function shortCommit(value) {
|
|
3318
|
+
return typeof value === "string" ? value.slice(0, 7) : "";
|
|
3319
|
+
}
|
|
2397
3320
|
function SubscriptionsTab({
|
|
2398
3321
|
t,
|
|
2399
3322
|
subscriptions,
|
|
@@ -2401,12 +3324,72 @@ function SubscriptionsTab({
|
|
|
2401
3324
|
expanded,
|
|
2402
3325
|
busy,
|
|
2403
3326
|
writable,
|
|
3327
|
+
capability = null,
|
|
2404
3328
|
onToggle,
|
|
2405
3329
|
onRefresh,
|
|
2406
3330
|
onDelete,
|
|
2407
3331
|
onInstall,
|
|
2408
3332
|
onAdd
|
|
2409
3333
|
}) {
|
|
3334
|
+
const [intro, setIntro] = import_react6.default.useState(null);
|
|
3335
|
+
import_react6.default.useEffect(() => {
|
|
3336
|
+
if (!intro) return;
|
|
3337
|
+
const subscription = subscriptions.find((item) => item.id === intro.subscriptionId);
|
|
3338
|
+
const entries = subscription?.snapshot?.entries || [];
|
|
3339
|
+
if (entries.some((entry) => entry.name === intro.name)) return;
|
|
3340
|
+
setIntro(null);
|
|
3341
|
+
}, [intro, subscriptions]);
|
|
3342
|
+
function openIntro(event, subscription, entry) {
|
|
3343
|
+
const el = event.currentTarget;
|
|
3344
|
+
const rect = el.getBoundingClientRect();
|
|
3345
|
+
setIntro({
|
|
3346
|
+
subscriptionId: subscription.id,
|
|
3347
|
+
name: entry.name,
|
|
3348
|
+
anchorEl: el,
|
|
3349
|
+
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
function introButton(subscription, entry) {
|
|
3353
|
+
return h(
|
|
3354
|
+
"button",
|
|
3355
|
+
{
|
|
3356
|
+
type: "button",
|
|
3357
|
+
className: "dshsc-intro-btn",
|
|
3358
|
+
title: t("intro.open"),
|
|
3359
|
+
"aria-haspopup": "dialog",
|
|
3360
|
+
onClick: (clickEvent) => openIntro(clickEvent, subscription, entry)
|
|
3361
|
+
},
|
|
3362
|
+
h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
|
|
3363
|
+
t("intro.button")
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
const introSubscription = intro ? subscriptions.find((item) => item.id === intro.subscriptionId) || null : null;
|
|
3367
|
+
const introEntry = introSubscription ? (introSubscription.snapshot?.entries || []).find((entry) => entry.name === intro.name) || null : null;
|
|
3368
|
+
const introPopover = intro && introEntry && capability ? (() => {
|
|
3369
|
+
const commit = introSubscription.snapshot?.commit || "";
|
|
3370
|
+
const installedAlready = rows.some((row) => {
|
|
3371
|
+
if (row.name !== introEntry.name) return false;
|
|
3372
|
+
if (row.subscriptionId === introSubscription.id) return true;
|
|
3373
|
+
return repoOf(row.source) === repoOf(introSubscription.source);
|
|
3374
|
+
});
|
|
3375
|
+
return h(IntroPopover, {
|
|
3376
|
+
t,
|
|
3377
|
+
name: introEntry.name,
|
|
3378
|
+
stamp: commit,
|
|
3379
|
+
subtitle: `${introSubscription.source}@${shortCommit(commit)}`,
|
|
3380
|
+
endpoint: `/subscriptions/${encodeURIComponent(introSubscription.id)}/entries/${encodeURIComponent(introEntry.name)}/intro`,
|
|
3381
|
+
capability,
|
|
3382
|
+
anchor: intro.anchor,
|
|
3383
|
+
anchorEl: intro.anchorEl,
|
|
3384
|
+
hint: t("intro.hintRemote").replace("{source}", introSubscription.source).replace("{commit}", shortCommit(commit) || "—"),
|
|
3385
|
+
viewLabel: t("action.install"),
|
|
3386
|
+
onView: installedAlready ? null : () => {
|
|
3387
|
+
setIntro(null);
|
|
3388
|
+
void onInstall(introSubscription, introEntry);
|
|
3389
|
+
},
|
|
3390
|
+
onClose: () => setIntro(null)
|
|
3391
|
+
});
|
|
3392
|
+
})() : null;
|
|
2410
3393
|
if (subscriptions.length === 0) {
|
|
2411
3394
|
return h(
|
|
2412
3395
|
"div",
|
|
@@ -2429,81 +3412,133 @@ function SubscriptionsTab({
|
|
|
2429
3412
|
);
|
|
2430
3413
|
}
|
|
2431
3414
|
return h(
|
|
2432
|
-
|
|
2433
|
-
|
|
3415
|
+
import_react6.default.Fragment,
|
|
3416
|
+
null,
|
|
2434
3417
|
h(
|
|
2435
3418
|
"div",
|
|
2436
|
-
{ className: "dshsc-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
3419
|
+
{ className: "dshsc-tabpane" },
|
|
3420
|
+
// 表头与仓库行共用同一个滚动容器,位置 sticky;宽屏 4 列,窄屏收成「仓库」/「操作」。
|
|
3421
|
+
h(
|
|
3422
|
+
"div",
|
|
3423
|
+
{ className: "dshsc-listscroll dshsc-subscroll" },
|
|
3424
|
+
h(
|
|
2442
3425
|
"div",
|
|
2443
|
-
{ className: "dshsc-
|
|
2444
|
-
h(
|
|
3426
|
+
{ className: "dshsc-subhead" },
|
|
3427
|
+
h("div", { className: "dshsc-hrepo" }, t("column.repo")),
|
|
3428
|
+
h("div", { className: "dshsc-href" }, t("column.ref")),
|
|
3429
|
+
h("div", { className: "dshsc-hcount" }, t("column.entries")),
|
|
3430
|
+
h("div", { className: "dshsc-hact" }, t("column.actions"))
|
|
3431
|
+
),
|
|
3432
|
+
subscriptions.map((subscription) => {
|
|
3433
|
+
const entries = subscription.snapshot?.entries || [];
|
|
3434
|
+
const repo = repoOf(subscription.source);
|
|
3435
|
+
const rowsByName = new Map(rows.map((row) => [row.name, row]));
|
|
3436
|
+
const installedRow = (name) => {
|
|
3437
|
+
const row = rowsByName.get(name);
|
|
3438
|
+
if (!row) return null;
|
|
3439
|
+
if (row.subscriptionId === subscription.id) return row;
|
|
3440
|
+
return repo && repoOf(row.source) === repo ? row : null;
|
|
3441
|
+
};
|
|
3442
|
+
const installedCount = entries.filter((entry) => installedRow(entry.name)).length;
|
|
3443
|
+
const open = Boolean(expanded[subscription.id]);
|
|
3444
|
+
return h(
|
|
2445
3445
|
"div",
|
|
2446
|
-
{ className: "dshsc-sub-
|
|
2447
|
-
h("button", {
|
|
2448
|
-
className: "dshsc-caret",
|
|
2449
|
-
"aria-expanded": String(open),
|
|
2450
|
-
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
2451
|
-
onClick: () => onToggle(subscription.id)
|
|
2452
|
-
}, h(CaretIcon, { open })),
|
|
2453
|
-
h("span", { className: "dshsc-name" }, subscription.source),
|
|
2454
|
-
h("span", { className: "dshsc-tag" }, subscription.ref || t("subscription.defaultRef")),
|
|
2455
|
-
h(
|
|
2456
|
-
"span",
|
|
2457
|
-
{ className: "dshsc-substat" },
|
|
2458
|
-
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedNames.size}`
|
|
2459
|
-
),
|
|
3446
|
+
{ className: "dshsc-sub-block", key: subscription.id },
|
|
2460
3447
|
h(
|
|
2461
3448
|
"div",
|
|
2462
|
-
{ className: "dshsc-
|
|
2463
|
-
h("button", {
|
|
2464
|
-
className: "dshsc-btn sq",
|
|
2465
|
-
disabled: busy !== null,
|
|
2466
|
-
title: t("action.checkUpdates"),
|
|
2467
|
-
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
2468
|
-
onClick: () => void onRefresh(subscription)
|
|
2469
|
-
}, h(RefreshIcon, { size: 13 })),
|
|
2470
|
-
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
2471
|
-
)
|
|
2472
|
-
),
|
|
2473
|
-
open ? h("div", { className: "dshsc-sub-list" }, entries.map((entry) => {
|
|
2474
|
-
const installedAlready = installedNames.has(entry.name);
|
|
2475
|
-
return h(
|
|
2476
|
-
"div",
|
|
2477
|
-
{ className: "dshsc-sub-entry", key: entry.name },
|
|
2478
|
-
h("span", { className: "dshsc-name" }, entry.name),
|
|
3449
|
+
{ className: "dshsc-sub-row" },
|
|
2479
3450
|
h(
|
|
2480
|
-
"
|
|
2481
|
-
{ className:
|
|
2482
|
-
|
|
2483
|
-
|
|
3451
|
+
"div",
|
|
3452
|
+
{ className: "dshsc-sub-repo" },
|
|
3453
|
+
h("button", {
|
|
3454
|
+
className: "dshsc-caret",
|
|
3455
|
+
"aria-expanded": String(open),
|
|
3456
|
+
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
3457
|
+
onClick: () => onToggle(subscription.id)
|
|
3458
|
+
}, h(CaretIcon, { open })),
|
|
3459
|
+
h("span", { className: "dshsc-name" }, subscription.source)
|
|
3460
|
+
),
|
|
3461
|
+
// 宽屏铺成 Ref / 条目两列;窄屏回到仓库名下方一行。
|
|
3462
|
+
h(
|
|
3463
|
+
"div",
|
|
3464
|
+
{ className: "dshsc-sub-meta" },
|
|
3465
|
+
h("span", { className: "dshsc-tag dshsc-subref" }, subscription.ref || t("subscription.defaultRef")),
|
|
3466
|
+
h(
|
|
3467
|
+
"span",
|
|
3468
|
+
{ className: "dshsc-substat" },
|
|
3469
|
+
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedCount}`
|
|
3470
|
+
)
|
|
2484
3471
|
),
|
|
2485
3472
|
h(
|
|
2486
3473
|
"div",
|
|
2487
3474
|
{ className: "dshsc-actions" },
|
|
2488
|
-
|
|
2489
|
-
className: "dshsc-btn",
|
|
2490
|
-
disabled:
|
|
2491
|
-
|
|
2492
|
-
|
|
3475
|
+
h("button", {
|
|
3476
|
+
className: "dshsc-btn sq",
|
|
3477
|
+
disabled: busy !== null,
|
|
3478
|
+
title: t("action.checkUpdates"),
|
|
3479
|
+
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
3480
|
+
onClick: () => void onRefresh(subscription)
|
|
3481
|
+
}, h(RefreshIcon, { size: 13 })),
|
|
3482
|
+
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
2493
3483
|
)
|
|
2494
|
-
)
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
3484
|
+
),
|
|
3485
|
+
open ? h(
|
|
3486
|
+
"div",
|
|
3487
|
+
{ className: "dshsc-sub-list" },
|
|
3488
|
+
h(
|
|
3489
|
+
"div",
|
|
3490
|
+
{ className: "dshsc-entryhead" },
|
|
3491
|
+
h("div", null, t("column.skill")),
|
|
3492
|
+
h("div", null, t("column.status")),
|
|
3493
|
+
h("div", { className: "right" }, t("column.actions"))
|
|
3494
|
+
),
|
|
3495
|
+
entries.map((entry) => {
|
|
3496
|
+
const installedAlready = Boolean(installedRow(entry.name));
|
|
3497
|
+
return h(
|
|
3498
|
+
"div",
|
|
3499
|
+
{ className: "dshsc-sub-entry", key: entry.name },
|
|
3500
|
+
h(
|
|
3501
|
+
"div",
|
|
3502
|
+
{ className: "dshsc-sub-skill" },
|
|
3503
|
+
h(
|
|
3504
|
+
"div",
|
|
3505
|
+
{ className: "dshsc-sub-namerow" },
|
|
3506
|
+
h("span", { className: "dshsc-name" }, entry.name),
|
|
3507
|
+
capability ? introButton(subscription, entry) : null
|
|
3508
|
+
),
|
|
3509
|
+
entry.description ? h("div", { className: "dshsc-sub-desc", title: entry.description }, entry.description) : null
|
|
3510
|
+
),
|
|
3511
|
+
h(
|
|
3512
|
+
"span",
|
|
3513
|
+
{ className: `dshsc-state${installedAlready ? " ok" : ""}` },
|
|
3514
|
+
installedAlready ? "✓ " : "",
|
|
3515
|
+
installedAlready ? t("installed") : t("notInstalled")
|
|
3516
|
+
),
|
|
3517
|
+
h(
|
|
3518
|
+
"div",
|
|
3519
|
+
{ className: "dshsc-actions" },
|
|
3520
|
+
installedAlready ? h("span", { className: "dshsc-noop" }, "-") : h("button", {
|
|
3521
|
+
className: "dshsc-btn",
|
|
3522
|
+
disabled: !writable || busy !== null,
|
|
3523
|
+
onClick: () => void onInstall(subscription, entry)
|
|
3524
|
+
}, t("action.install"))
|
|
3525
|
+
)
|
|
3526
|
+
);
|
|
3527
|
+
})
|
|
3528
|
+
) : null
|
|
3529
|
+
);
|
|
3530
|
+
})
|
|
3531
|
+
)
|
|
3532
|
+
),
|
|
3533
|
+
introPopover
|
|
2499
3534
|
);
|
|
2500
3535
|
}
|
|
2501
3536
|
|
|
2502
3537
|
// client/views/modals.js
|
|
2503
|
-
var
|
|
3538
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
2504
3539
|
|
|
2505
3540
|
// client/views/add-skill.js
|
|
2506
|
-
var
|
|
3541
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
2507
3542
|
|
|
2508
3543
|
// client/views/overlay.js
|
|
2509
3544
|
function Overlay({ t, title, children, footer, onCancel }) {
|
|
@@ -2533,13 +3568,13 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
2533
3568
|
|
|
2534
3569
|
// client/views/add-skill.js
|
|
2535
3570
|
function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
2536
|
-
const [source, setSource] =
|
|
2537
|
-
const [phase, setPhase] =
|
|
2538
|
-
const [options, setOptions] =
|
|
2539
|
-
const [selected, setSelected] =
|
|
2540
|
-
const [manual, setManual] =
|
|
2541
|
-
const [skipped, setSkipped] =
|
|
2542
|
-
const [error, setError] =
|
|
3571
|
+
const [source, setSource] = import_react7.default.useState("");
|
|
3572
|
+
const [phase, setPhase] = import_react7.default.useState("idle");
|
|
3573
|
+
const [options, setOptions] = import_react7.default.useState([]);
|
|
3574
|
+
const [selected, setSelected] = import_react7.default.useState(() => /* @__PURE__ */ new Set());
|
|
3575
|
+
const [manual, setManual] = import_react7.default.useState("");
|
|
3576
|
+
const [skipped, setSkipped] = import_react7.default.useState(0);
|
|
3577
|
+
const [error, setError] = import_react7.default.useState(null);
|
|
2543
3578
|
const reset = (value) => {
|
|
2544
3579
|
setSource(value);
|
|
2545
3580
|
setPhase("idle");
|
|
@@ -2675,7 +3710,7 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
2675
3710
|
|
|
2676
3711
|
// client/views/modals.js
|
|
2677
3712
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
2678
|
-
const [form, setForm] =
|
|
3713
|
+
const [form, setForm] = import_react8.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
2679
3714
|
const incomplete = fields.some(
|
|
2680
3715
|
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
2681
3716
|
);
|
|
@@ -2723,7 +3758,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
|
2723
3758
|
const candidates = agents.filter(
|
|
2724
3759
|
(agent) => agent.kind === "agent" && agent.exists && agent.exportable
|
|
2725
3760
|
);
|
|
2726
|
-
const [targets, setTargets] =
|
|
3761
|
+
const [targets, setTargets] = import_react8.default.useState(() => {
|
|
2727
3762
|
const available = new Set(candidates.map((agent) => agent.target));
|
|
2728
3763
|
return new Set((row.exports || []).map((item) => item.target).filter((target) => available.has(target)));
|
|
2729
3764
|
});
|
|
@@ -2765,7 +3800,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
|
2765
3800
|
);
|
|
2766
3801
|
}
|
|
2767
3802
|
function DefaultExportModal({ t, settings, agents, onCancel, onSave }) {
|
|
2768
|
-
const [targets, setTargets] =
|
|
3803
|
+
const [targets, setTargets] = import_react8.default.useState(() => new Set(
|
|
2769
3804
|
Array.isArray(settings.exportTargets) ? settings.exportTargets : []
|
|
2770
3805
|
));
|
|
2771
3806
|
const toggle = (target) => setTargets((prev) => {
|
|
@@ -2844,6 +3879,17 @@ function Modals({ t, modal, data, autoLinkAgents, actions, onClose, onSaveAutoLi
|
|
|
2844
3879
|
onSave: (targets) => void onSaveAutoLink(targets)
|
|
2845
3880
|
});
|
|
2846
3881
|
}
|
|
3882
|
+
if (modal.type === "confirm-unpark") {
|
|
3883
|
+
const previous = modal.row?.park?.previousMode === "manual" ? t("policy.manual") : t("policy.auto");
|
|
3884
|
+
return h(ConfirmModal, {
|
|
3885
|
+
t,
|
|
3886
|
+
title: t("policy.unparkTitle"),
|
|
3887
|
+
body: t("policy.unparkBody").replace("{mode}", previous),
|
|
3888
|
+
confirmLabel: t("policy.unpark"),
|
|
3889
|
+
onCancel: onClose,
|
|
3890
|
+
onConfirm: modal.onConfirm
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
2847
3893
|
if (modal.type === "confirm-delete") {
|
|
2848
3894
|
return h(ConfirmModal, {
|
|
2849
3895
|
t,
|
|
@@ -2883,7 +3929,7 @@ ${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
|
|
|
2883
3929
|
}
|
|
2884
3930
|
|
|
2885
3931
|
// client/views/skill-reader.js
|
|
2886
|
-
var
|
|
3932
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
2887
3933
|
|
|
2888
3934
|
// client/markdown.js
|
|
2889
3935
|
function classifyHref(href) {
|
|
@@ -3319,7 +4365,7 @@ function formatBytes(bytes) {
|
|
|
3319
4365
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3320
4366
|
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3321
4367
|
}
|
|
3322
|
-
function
|
|
4368
|
+
function formatWhen2(iso) {
|
|
3323
4369
|
if (!iso) return null;
|
|
3324
4370
|
const date = new Date(iso);
|
|
3325
4371
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -3379,37 +4425,37 @@ function SkillReader({
|
|
|
3379
4425
|
onToast
|
|
3380
4426
|
}) {
|
|
3381
4427
|
const { state, file, loadFile, retry } = useSkillReader(row);
|
|
3382
|
-
const [tab, setTab] = import_react5.default.useState(initialTab);
|
|
3383
|
-
const [format, setFormat] = import_react5.default.useState("rendered");
|
|
3384
|
-
const [docOverride, setDocOverride] = import_react5.default.useState(null);
|
|
3385
|
-
const [fileFormat, setFileFormat] = import_react5.default.useState("rendered");
|
|
3386
|
-
const [tocActive, setTocActive] = import_react5.default.useState(null);
|
|
3387
|
-
const [descOpen, setDescOpen] = import_react5.default.useState(false);
|
|
3388
|
-
const [descClamped, setDescClamped] = import_react5.default.useState(false);
|
|
3389
|
-
const bodyRef = import_react5.default.useRef(null);
|
|
3390
|
-
const dialogRef = import_react5.default.useRef(null);
|
|
3391
|
-
const descRef = import_react5.default.useRef(null);
|
|
3392
|
-
const scrollRef = import_react5.default.useRef(0);
|
|
3393
|
-
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
3394
4428
|
const name = row?.name || "";
|
|
4429
|
+
const [tab, setTab] = import_react9.default.useState(initialTab);
|
|
4430
|
+
const [format, setFormat] = import_react9.default.useState("rendered");
|
|
4431
|
+
const [docOverride, setDocOverride] = import_react9.default.useState(null);
|
|
4432
|
+
const [fileFormat, setFileFormat] = import_react9.default.useState("rendered");
|
|
4433
|
+
const [tocActive, setTocActive] = import_react9.default.useState(null);
|
|
4434
|
+
const [descOpen, setDescOpen] = import_react9.default.useState(false);
|
|
4435
|
+
const [descClamped, setDescClamped] = import_react9.default.useState(false);
|
|
4436
|
+
const bodyRef = import_react9.default.useRef(null);
|
|
4437
|
+
const dialogRef = import_react9.default.useRef(null);
|
|
4438
|
+
const descRef = import_react9.default.useRef(null);
|
|
4439
|
+
const scrollRef = import_react9.default.useRef(0);
|
|
4440
|
+
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
3395
4441
|
const tree = state.tree;
|
|
3396
4442
|
const files = tree?.files || [];
|
|
3397
4443
|
const fileCount = files.filter((entry) => entry.kind === "file").length;
|
|
3398
4444
|
const bodyText = docOverride ? docOverride.content : state.body?.content || "";
|
|
3399
4445
|
const description = row?.description || state.body?.meta?.description || "";
|
|
3400
4446
|
const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
|
|
3401
|
-
|
|
4447
|
+
import_react9.default.useEffect(() => {
|
|
3402
4448
|
setDocOverride(null);
|
|
3403
4449
|
scrollRef.current = 0;
|
|
3404
4450
|
}, [name]);
|
|
3405
|
-
const descOwnerRef =
|
|
3406
|
-
|
|
4451
|
+
const descOwnerRef = import_react9.default.useRef(name);
|
|
4452
|
+
import_react9.default.useEffect(() => {
|
|
3407
4453
|
if (descOwnerRef.current === name) return;
|
|
3408
4454
|
descOwnerRef.current = name;
|
|
3409
4455
|
setDescOpen(false);
|
|
3410
4456
|
setDescClamped(false);
|
|
3411
4457
|
}, [name]);
|
|
3412
|
-
|
|
4458
|
+
import_react9.default.useLayoutEffect(() => {
|
|
3413
4459
|
const node = descRef.current;
|
|
3414
4460
|
if (!node) return void 0;
|
|
3415
4461
|
const measure = () => {
|
|
@@ -3431,16 +4477,16 @@ function SkillReader({
|
|
|
3431
4477
|
window.removeEventListener("resize", measure);
|
|
3432
4478
|
};
|
|
3433
4479
|
}, [description, descOpen]);
|
|
3434
|
-
|
|
4480
|
+
import_react9.default.useEffect(() => {
|
|
3435
4481
|
if (!initialFile) return;
|
|
3436
4482
|
setTab("files");
|
|
3437
4483
|
void loadFile(initialFile);
|
|
3438
4484
|
}, [initialFile, loadFile]);
|
|
3439
|
-
|
|
4485
|
+
import_react9.default.useEffect(() => {
|
|
3440
4486
|
const node = bodyRef.current;
|
|
3441
4487
|
if (node && scrollRef.current) node.scrollTop = scrollRef.current;
|
|
3442
4488
|
});
|
|
3443
|
-
|
|
4489
|
+
import_react9.default.useEffect(() => {
|
|
3444
4490
|
if (variant !== "drawer" || typeof document === "undefined") return void 0;
|
|
3445
4491
|
const previous = document.activeElement;
|
|
3446
4492
|
const node = dialogRef.current;
|
|
@@ -3467,12 +4513,12 @@ function SkillReader({
|
|
|
3467
4513
|
previous?.focus?.();
|
|
3468
4514
|
};
|
|
3469
4515
|
}, [variant, onClose]);
|
|
3470
|
-
|
|
4516
|
+
import_react9.default.useEffect(() => {
|
|
3471
4517
|
if (variant !== "page" || typeof document === "undefined") return;
|
|
3472
4518
|
const node = document.getElementById("dshsc-reader-name");
|
|
3473
4519
|
node?.focus?.();
|
|
3474
4520
|
}, [variant, name]);
|
|
3475
|
-
|
|
4521
|
+
import_react9.default.useEffect(() => {
|
|
3476
4522
|
if (variant !== "page" || typeof window === "undefined") return void 0;
|
|
3477
4523
|
const node = bodyRef.current;
|
|
3478
4524
|
if (!node?.querySelectorAll) return void 0;
|
|
@@ -3568,7 +4614,7 @@ function SkillReader({
|
|
|
3568
4614
|
h("span", { className: "dshsc-code" }, row?.source ? `${row.source}${row.commit ? `#${short(row.commit)}` : row.ref ? `#${row.ref}` : ""}` : t("meta.noUpstream"))
|
|
3569
4615
|
),
|
|
3570
4616
|
tree ? h("span", null, t("viewer.fileCount").replace("{n}", String(fileCount))) : null,
|
|
3571
|
-
row?.updatedAt ? h("span", null,
|
|
4617
|
+
row?.updatedAt ? h("span", null, formatWhen2(row.updatedAt)) : null
|
|
3572
4618
|
)
|
|
3573
4619
|
);
|
|
3574
4620
|
const tabsBar = h(
|
|
@@ -3785,8 +4831,8 @@ function SkillReader({
|
|
|
3785
4831
|
["viewer.info.hash", hash ? h("span", { className: "mono" }, hash) : t("viewer.info.none"), "mono"]
|
|
3786
4832
|
];
|
|
3787
4833
|
const localRows = [
|
|
3788
|
-
["viewer.info.installedAt",
|
|
3789
|
-
["viewer.info.updatedAt",
|
|
4834
|
+
["viewer.info.installedAt", formatWhen2(row?.installedAt) || t("viewer.info.none")],
|
|
4835
|
+
["viewer.info.updatedAt", formatWhen2(row?.updatedAt) || t("viewer.info.none")],
|
|
3790
4836
|
["viewer.info.size", tree ? `${formatBytes(tree.totalBytes)} · ${t("viewer.fileCount").replace("{n}", String(fileCount))}` : t("viewer.info.none")],
|
|
3791
4837
|
["viewer.info.localChange", row?.status === "locally-modified" || row?.status === "conflict" ? t("viewer.info.localChangeYes") : t("viewer.info.localChangeNone")]
|
|
3792
4838
|
];
|
|
@@ -3910,19 +4956,19 @@ function SkillReader({
|
|
|
3910
4956
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
3911
4957
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
3912
4958
|
const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
|
|
3913
|
-
const [tab, setTab] =
|
|
3914
|
-
const [view, setView] =
|
|
3915
|
-
const [modal, setModal] =
|
|
3916
|
-
const [expanded, setExpanded] =
|
|
3917
|
-
const [placementBusy, setPlacementBusy] =
|
|
3918
|
-
const [placementOverride, setPlacementOverride] =
|
|
3919
|
-
const [query, setQuery] =
|
|
3920
|
-
const [viewer, setViewer] =
|
|
3921
|
-
const rootRef =
|
|
3922
|
-
const scrollPos =
|
|
3923
|
-
const restoreScroll =
|
|
3924
|
-
const [filter, setFilter] =
|
|
3925
|
-
const [builtinOpen, setBuiltinOpen] =
|
|
4959
|
+
const [tab, setTab] = import_react10.default.useState("installed");
|
|
4960
|
+
const [view, setView] = import_react10.default.useState("flat");
|
|
4961
|
+
const [modal, setModal] = import_react10.default.useState(null);
|
|
4962
|
+
const [expanded, setExpanded] = import_react10.default.useState({});
|
|
4963
|
+
const [placementBusy, setPlacementBusy] = import_react10.default.useState(null);
|
|
4964
|
+
const [placementOverride, setPlacementOverride] = import_react10.default.useState(null);
|
|
4965
|
+
const [query, setQuery] = import_react10.default.useState("");
|
|
4966
|
+
const [viewer, setViewer] = import_react10.default.useState(null);
|
|
4967
|
+
const rootRef = import_react10.default.useRef(null);
|
|
4968
|
+
const scrollPos = import_react10.default.useRef(null);
|
|
4969
|
+
const restoreScroll = import_react10.default.useRef(null);
|
|
4970
|
+
const [filter, setFilter] = import_react10.default.useState("all");
|
|
4971
|
+
const [builtinOpen, setBuiltinOpen] = import_react10.default.useState(false);
|
|
3926
4972
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
3927
4973
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
3928
4974
|
const rows = data?.installs || [];
|
|
@@ -3932,18 +4978,20 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3932
4978
|
const updateCount = rows.filter(updatable).length;
|
|
3933
4979
|
const bulkCount = rows.filter((row) => row.status === "update-available").length;
|
|
3934
4980
|
const unknownCount = rows.filter((row) => row.status === "unknown").length;
|
|
3935
|
-
const offCount = rows.filter((row) => row.enabled === false).length;
|
|
3936
|
-
const
|
|
4981
|
+
const offCount = rows.filter((row) => row.mode === "hidden" || row.enabled === false).length;
|
|
4982
|
+
const manualCount = rows.filter((row) => row.mode === "manual").length;
|
|
4983
|
+
const counts = { all: rows.length, update: updateCount, unknown: unknownCount, manual: manualCount, off: offCount };
|
|
3937
4984
|
const queryText = query.trim().toLowerCase();
|
|
3938
4985
|
const visibleRows = rows.filter((row) => {
|
|
3939
4986
|
if (filter === "update" && !updatable(row)) return false;
|
|
3940
4987
|
if (filter === "unknown" && row.status !== "unknown") return false;
|
|
3941
|
-
if (filter === "
|
|
4988
|
+
if (filter === "manual" && row.mode !== "manual") return false;
|
|
4989
|
+
if (filter === "off" && row.mode !== "hidden" && row.enabled !== false) return false;
|
|
3942
4990
|
if (!queryText) return true;
|
|
3943
4991
|
const agentLabels = row.exports.map((item) => item.label);
|
|
3944
4992
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
3945
4993
|
});
|
|
3946
|
-
|
|
4994
|
+
import_react10.default.useEffect(() => {
|
|
3947
4995
|
if (!viewer || !data) return;
|
|
3948
4996
|
if (rows.some((row) => row.name === viewer.name)) return;
|
|
3949
4997
|
setViewer(null);
|
|
@@ -3977,7 +5025,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3977
5025
|
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
3978
5026
|
setViewer(null);
|
|
3979
5027
|
}
|
|
3980
|
-
|
|
5028
|
+
import_react10.default.useLayoutEffect(() => {
|
|
3981
5029
|
if (restoreScroll.current === null) return;
|
|
3982
5030
|
const target = restoreScroll.current;
|
|
3983
5031
|
restoreScroll.current = null;
|
|
@@ -4006,6 +5054,8 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4006
5054
|
onView: (row) => openViewer(row),
|
|
4007
5055
|
onAdopt: actions.adopt,
|
|
4008
5056
|
onToggleMode: actions.toggleMode,
|
|
5057
|
+
onSetInvocation: actions.setInvocation,
|
|
5058
|
+
onRequestUnpark: actions.requestUnpark,
|
|
4009
5059
|
onUpdate: actions.requestUpdate,
|
|
4010
5060
|
onDelete: actions.requestDelete,
|
|
4011
5061
|
onExports: (row) => setModal({ type: "exports", row }),
|
|
@@ -4044,7 +5094,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4044
5094
|
onToast: (next) => setToast(next)
|
|
4045
5095
|
}) : null;
|
|
4046
5096
|
const listNode = h(
|
|
4047
|
-
|
|
5097
|
+
import_react10.default.Fragment,
|
|
4048
5098
|
null,
|
|
4049
5099
|
h(Header, {
|
|
4050
5100
|
t,
|
|
@@ -4102,6 +5152,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4102
5152
|
expanded,
|
|
4103
5153
|
busy,
|
|
4104
5154
|
writable,
|
|
5155
|
+
capability: data?.intro || null,
|
|
4105
5156
|
...subscriptionActions
|
|
4106
5157
|
}),
|
|
4107
5158
|
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|