@morehao/dsh-skills-console 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -65
- package/client.js +457 -457
- package/cordis.patch.yml +2 -1
- package/lib/agent-table.json +133 -64
- package/lib/core/agents.js +68 -9
- package/lib/core/exporter.js +137 -0
- package/lib/core/inventory.js +73 -39
- package/lib/core/migrations.js +178 -0
- package/lib/core/paths.js +13 -0
- package/lib/core/policy.js +373 -0
- package/lib/core/remote.js +3 -1
- package/lib/core/roots.js +182 -0
- package/lib/core/service.js +391 -137
- package/lib/core/shadow.js +195 -0
- package/lib/core/skill-md.js +23 -4
- package/lib/core/state.js +293 -42
- package/lib/core/visibility.js +145 -0
- package/lib/index.js +27 -0
- package/lib/routes.js +13 -3
- package/package.json +3 -2
package/client.js
CHANGED
|
@@ -86,18 +86,19 @@ var DICT = {
|
|
|
86
86
|
"action.unsubscribe": "取消订阅",
|
|
87
87
|
"action.view": "查看",
|
|
88
88
|
"action.hide": "收起",
|
|
89
|
-
"action.
|
|
89
|
+
"action.exports": "导出设置",
|
|
90
90
|
"action.install": "安装",
|
|
91
91
|
"action.expand": "展开",
|
|
92
92
|
"action.collapse": "收起",
|
|
93
93
|
"action.adopt": "接管",
|
|
94
94
|
"action.save": "保存",
|
|
95
95
|
"action.cancel": "取消",
|
|
96
|
+
"action.close": "关闭",
|
|
96
97
|
"action.choose": "选择目录",
|
|
97
98
|
"action.retry": "重新加载",
|
|
98
99
|
"view.flat": "平铺",
|
|
99
100
|
"view.agent": "按 Agent",
|
|
100
|
-
|
|
101
|
+
exportDefault: "新安装时自动导出",
|
|
101
102
|
"settings.title": "插件设置",
|
|
102
103
|
"placement.label": "侧边栏入口",
|
|
103
104
|
"placement.effect": "切换后立即生效",
|
|
@@ -123,23 +124,20 @@ var DICT = {
|
|
|
123
124
|
"toast.checked": "检查完成",
|
|
124
125
|
"mode.on": "启用",
|
|
125
126
|
"mode.off": "停用",
|
|
126
|
-
"mode.offHint": "
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"autoLink.detected": "安装后自动接入",
|
|
141
|
-
"autoLink.none": "本机没有检测到其他 Agent",
|
|
142
|
-
"autoLink.hint": "列表里的「dsh(全局)」固定勾选、不能取消;这里只影响之后安装时的自动接入,已安装的技能请在「接入设置」里改。",
|
|
127
|
+
"mode.offHint": "已停用:DSH 的模型调用与 `/` 手动调用都看不到它;技能文件、导出关系与其他 Agent 都不受影响",
|
|
128
|
+
"visibility.hiddenHint": "已启用,但扫描目录里没有它:确认 ~/.agents/skills 下真有这个技能,或检查 DSH_AGENTS_HOME。",
|
|
129
|
+
"visibility.manualOnlyHint": "该技能自己声明了 `disable-model-invocation`:模型调用看不到它,手动 `/` 调用仍可用。",
|
|
130
|
+
"visibility.scopeMismatch": "当前有另一份同名技能排在前面(项目或用户目录);停用只影响 canonical,不会隐藏它。",
|
|
131
|
+
"visibility.foreignWinner": "模型看到的是另一份同名技能:{path}(它的扫描根 rank 更小,会遮住这一份)",
|
|
132
|
+
"exports.targets": "导出到哪些 Agent",
|
|
133
|
+
"exports.detectedHint": "只列出本机已安装(目录存在)的 Agent。导出只影响它们;DSH 自己看不看得见由 DSH 决定,和导出无关。",
|
|
134
|
+
"exports.canonical": "技能本体:{path};下面的链只是把它暴露给不直接读该目录的 Agent。",
|
|
135
|
+
"exports.none": "本机没有可导出的 Agent 目录",
|
|
136
|
+
"exports.hint": "保存后按勾选建立或移除软链;技能本体留在原位置,不会移动。",
|
|
137
|
+
"exports.sharedUnknown": "共享根未验证",
|
|
138
|
+
"exportDefault.detected": "装完后默认导出到",
|
|
139
|
+
"exportDefault.none": "本机没有需要导出的 Agent",
|
|
140
|
+
"exportDefault.hint": "默认一个都不勾:技能装进 ~/.agents/skills 后 DSH 本来就能看见。导出只用于仍只认私有目录的 Agent,且只影响之后的安装。",
|
|
143
141
|
"empty.skills": "还没有安装任何技能",
|
|
144
142
|
"empty.skillsHint": "在终端跑 `npx skills add owner/repo`,或点右上角「添加技能」",
|
|
145
143
|
"empty.subs": "还没有添加订阅",
|
|
@@ -167,17 +165,19 @@ var DICT = {
|
|
|
167
165
|
"form.subHint": "只记录你关注这个仓库,不会安装任何技能。",
|
|
168
166
|
"subscription.defaultRef": "默认分支",
|
|
169
167
|
"confirm.deleteTitle": "删除技能",
|
|
170
|
-
"confirm.deleteBody": "
|
|
168
|
+
"confirm.deleteBody": "会从本机移除,断开本插件建立的软链并删除 canonical / lock 条目;目录先备份到回收站(保留 7 天)。不会删除你的源仓库。",
|
|
171
169
|
"confirm.updateTitle": "本地有改动",
|
|
172
170
|
"confirm.updateBody": "更新会覆盖这些本地改动;旧内容已备份,可回滚。",
|
|
173
171
|
"error.conflictTitle": "删除被拦截",
|
|
174
172
|
"busy.working": "处理中…",
|
|
175
173
|
"meta.from": "来自",
|
|
176
174
|
"meta.noUpstream": "无远端来源 · 不检测更新",
|
|
177
|
-
"meta.
|
|
178
|
-
"meta.
|
|
179
|
-
"meta.
|
|
180
|
-
"meta.
|
|
175
|
+
"meta.noExport": "未导出",
|
|
176
|
+
"meta.noExportHint": "没有给不支持共享根的 Agent 建导出链;直读 ~/.agents/skills 的 Agent 装好即见,无需导出。",
|
|
177
|
+
"meta.modelVisible": "模型可见",
|
|
178
|
+
"meta.modelHidden": "模型不可见",
|
|
179
|
+
"meta.manualOnly": "仅手动",
|
|
180
|
+
"meta.agentsAll": "已导出的 Agent",
|
|
181
181
|
"meta.none": "无",
|
|
182
182
|
"filter.all": "全部",
|
|
183
183
|
"filter.update": "可更新",
|
|
@@ -189,16 +189,15 @@ var DICT = {
|
|
|
189
189
|
"empty.filtered": "没有匹配的技能",
|
|
190
190
|
"empty.filteredHint": "换个关键词,或清除筛选条件",
|
|
191
191
|
"hint.unknown": "还有 {n} 个技能未检查更新。",
|
|
192
|
-
"group.
|
|
193
|
-
"group.dshProject": "DSH · 仅特定项目",
|
|
192
|
+
"group.visible": "模型可见",
|
|
194
193
|
"group.off": "已停用",
|
|
195
|
-
"group.
|
|
194
|
+
"group.hidden": "启用但不可见",
|
|
196
195
|
installed: "已安装",
|
|
197
196
|
notInstalled: "未安装",
|
|
198
197
|
skillCount: "个技能",
|
|
199
198
|
visibleOnly: "模型可见,但不在本地技能库",
|
|
200
|
-
visibleOnlyOther: "
|
|
201
|
-
visibleOnlyBuiltin: "本插件仅管理 ~/.agents/skills
|
|
199
|
+
visibleOnlyOther: "模型能看到,但不在 ~/.agents/skills",
|
|
200
|
+
visibleOnlyBuiltin: "本插件仅管理 ~/.agents/skills 下的技能,内置技能不在管理范围内",
|
|
202
201
|
"builtin.view": "查看内置技能({n})",
|
|
203
202
|
"builtin.hide": "收起内置技能",
|
|
204
203
|
external: "非本插件创建",
|
|
@@ -250,10 +249,8 @@ var DICT = {
|
|
|
250
249
|
"viewer.info.localChangeNone": "无(与安装时一致)",
|
|
251
250
|
"viewer.info.localChangeYes": "有本地改动",
|
|
252
251
|
"viewer.info.links": "接入",
|
|
253
|
-
"viewer.info.
|
|
254
|
-
"viewer.info.
|
|
255
|
-
"viewer.info.scopeGlobal": "全局",
|
|
256
|
-
"viewer.info.scopeProject": "仅项目",
|
|
252
|
+
"viewer.info.exported": "已导出",
|
|
253
|
+
"viewer.info.visibility": "可见性",
|
|
257
254
|
"viewer.info.mode": "状态",
|
|
258
255
|
"viewer.info.status": "更新",
|
|
259
256
|
"viewer.info.none": "无"
|
|
@@ -273,18 +270,19 @@ var DICT = {
|
|
|
273
270
|
"action.unsubscribe": "Unsubscribe",
|
|
274
271
|
"action.view": "View",
|
|
275
272
|
"action.hide": "Hide",
|
|
276
|
-
"action.
|
|
273
|
+
"action.exports": "Export settings",
|
|
277
274
|
"action.install": "Install",
|
|
278
275
|
"action.expand": "Expand",
|
|
279
276
|
"action.collapse": "Collapse",
|
|
280
277
|
"action.adopt": "Adopt",
|
|
281
278
|
"action.save": "Save",
|
|
282
279
|
"action.cancel": "Cancel",
|
|
280
|
+
"action.close": "Close",
|
|
283
281
|
"action.choose": "Choose folder",
|
|
284
282
|
"action.retry": "Reload",
|
|
285
283
|
"view.flat": "Flat",
|
|
286
284
|
"view.agent": "By agent",
|
|
287
|
-
"
|
|
285
|
+
"exportDefault": "Auto-export on install",
|
|
288
286
|
"settings.title": "Plugin settings",
|
|
289
287
|
"placement.label": "Sidebar entry",
|
|
290
288
|
"placement.effect": "Applies immediately",
|
|
@@ -309,23 +307,20 @@ var DICT = {
|
|
|
309
307
|
"toast.checked": "Checked",
|
|
310
308
|
"mode.on": "Enabled",
|
|
311
309
|
"mode.off": "Disabled",
|
|
312
|
-
"mode.offHint": "Disabled
|
|
313
|
-
"
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"autoLink.detected": "Auto-link after install",
|
|
327
|
-
"autoLink.none": "No other agents detected on this machine",
|
|
328
|
-
"autoLink.hint": 'The "dsh (global)" entry is always checked and cannot be unchecked; this only affects future installs. Change existing skills under "Link settings".',
|
|
310
|
+
"mode.offHint": "Disabled for DSH’s model and `/` calls; the skill files, exports, and other agents are untouched",
|
|
311
|
+
"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": "This skill declares `disable-model-invocation`: the model cannot invoke it, but manual `/` invocation still works.",
|
|
313
|
+
"visibility.scopeMismatch": "Another copy of this skill ranks first (project or user directory); disabling only affects the canonical copy.",
|
|
314
|
+
"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": "Export to agents",
|
|
316
|
+
"exports.detectedHint": "Only installed agents (existing directories) are listed. Exports only affect them; whether DSH sees the skill is up to DSH, not exports.",
|
|
317
|
+
"exports.canonical": "Skill source: {path}; the entries below only expose it to agents that do not read that directory directly.",
|
|
318
|
+
"exports.none": "No agent directories available for export on this machine",
|
|
319
|
+
"exports.hint": "Saving only adds or removes links; the skill itself stays where it is.",
|
|
320
|
+
"exports.sharedUnknown": "shared root unverified",
|
|
321
|
+
"exportDefault.detected": "Export new installs to",
|
|
322
|
+
"exportDefault.none": "No agents need exporting on this machine",
|
|
323
|
+
"exportDefault.hint": "Nothing is checked by default: once a skill lands in ~/.agents/skills, DSH already sees it. Exports only matter for agents that still read their own directory, and only affect future installs.",
|
|
329
324
|
"empty.skills": "No skills installed yet",
|
|
330
325
|
"empty.skillsHint": 'Run `npx skills add owner/repo` or use "Add skill".',
|
|
331
326
|
"empty.subs": "No subscriptions yet",
|
|
@@ -353,17 +348,19 @@ var DICT = {
|
|
|
353
348
|
"form.subHint": "Only records the repository; installs nothing.",
|
|
354
349
|
"subscription.defaultRef": "default branch",
|
|
355
350
|
"confirm.deleteTitle": "Delete skill",
|
|
356
|
-
"confirm.deleteBody": "Removes it locally, disconnects this plugin’s links and
|
|
351
|
+
"confirm.deleteBody": "Removes it locally, disconnects this plugin’s links, and deletes the canonical directory and lock entry. A snapshot stays in trash for 7 days. Your source repo is untouched.",
|
|
357
352
|
"confirm.updateTitle": "Local changes",
|
|
358
353
|
"confirm.updateBody": "Updating overwrites local changes; the old content is backed up and can be rolled back.",
|
|
359
354
|
"error.conflictTitle": "Delete blocked",
|
|
360
355
|
"busy.working": "Working…",
|
|
361
356
|
"meta.from": "from",
|
|
362
357
|
"meta.noUpstream": "no upstream · updates not checked",
|
|
363
|
-
"meta.
|
|
364
|
-
"meta.
|
|
365
|
-
"meta.
|
|
366
|
-
"meta.
|
|
358
|
+
"meta.noExport": "Not exported",
|
|
359
|
+
"meta.noExportHint": "No export links to agents that need them; agents that read ~/.agents/skills can use it as-is.",
|
|
360
|
+
"meta.modelVisible": "visible to the model",
|
|
361
|
+
"meta.modelHidden": "hidden from the model",
|
|
362
|
+
"meta.manualOnly": "Manual only",
|
|
363
|
+
"meta.agentsAll": "Exported agents",
|
|
367
364
|
"meta.none": "none",
|
|
368
365
|
"filter.all": "All",
|
|
369
366
|
"filter.update": "Updates",
|
|
@@ -375,16 +372,15 @@ var DICT = {
|
|
|
375
372
|
"empty.filtered": "No matching skills",
|
|
376
373
|
"empty.filteredHint": "Try another keyword, or clear the filters",
|
|
377
374
|
"hint.unknown": "{n} skills have not been checked for updates.",
|
|
378
|
-
"group.
|
|
379
|
-
"group.dshProject": "DSH · project only",
|
|
375
|
+
"group.visible": "Model visible",
|
|
380
376
|
"group.off": "Disabled",
|
|
381
|
-
"group.
|
|
377
|
+
"group.hidden": "Enabled but hidden",
|
|
382
378
|
installed: "installed",
|
|
383
379
|
notInstalled: "not installed",
|
|
384
380
|
skillCount: "skills",
|
|
385
381
|
visibleOnly: "Visible to the model but not in the local skill library",
|
|
386
|
-
visibleOnlyOther: "
|
|
387
|
-
visibleOnlyBuiltin: "This plugin only manages skills under ~/.agents/skills;
|
|
382
|
+
visibleOnlyOther: "The model can see these, but they are not under ~/.agents/skills",
|
|
383
|
+
visibleOnlyBuiltin: "This plugin only manages skills under ~/.agents/skills; built-in skills are out of scope",
|
|
388
384
|
"builtin.view": "View built-ins ({n})",
|
|
389
385
|
"builtin.hide": "Hide built-ins",
|
|
390
386
|
external: "created outside this plugin",
|
|
@@ -437,10 +433,8 @@ var DICT = {
|
|
|
437
433
|
"viewer.info.localChangeNone": "None (matches install)",
|
|
438
434
|
"viewer.info.localChangeYes": "Modified locally",
|
|
439
435
|
"viewer.info.links": "Linked",
|
|
440
|
-
"viewer.info.
|
|
441
|
-
"viewer.info.
|
|
442
|
-
"viewer.info.scopeGlobal": "Global",
|
|
443
|
-
"viewer.info.scopeProject": "Project only",
|
|
436
|
+
"viewer.info.exported": "Exported",
|
|
437
|
+
"viewer.info.visibility": "Visibility",
|
|
444
438
|
"viewer.info.mode": "State",
|
|
445
439
|
"viewer.info.status": "Updates",
|
|
446
440
|
"viewer.info.none": "None"
|
|
@@ -848,7 +842,26 @@ function FolderIcon({ size = 12 }) {
|
|
|
848
842
|
}
|
|
849
843
|
|
|
850
844
|
// client/section.js
|
|
851
|
-
var
|
|
845
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
846
|
+
|
|
847
|
+
// client/scroll.js
|
|
848
|
+
function isScroller(node, tolerance = 4) {
|
|
849
|
+
if (!node || typeof getComputedStyle !== "function") return false;
|
|
850
|
+
const style = getComputedStyle(node);
|
|
851
|
+
return Boolean(style && /(auto|scroll)/.test(style.overflowY || "") && node.scrollHeight > node.clientHeight + tolerance);
|
|
852
|
+
}
|
|
853
|
+
function nearestScroller(node, tolerance = 4) {
|
|
854
|
+
let current = node?.parentElement || null;
|
|
855
|
+
while (current) {
|
|
856
|
+
if (isScroller(current, tolerance)) return current;
|
|
857
|
+
current = current.parentElement || null;
|
|
858
|
+
}
|
|
859
|
+
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
860
|
+
}
|
|
861
|
+
function scrollHost(node, tolerance = 4) {
|
|
862
|
+
if (isScroller(node, tolerance)) return node;
|
|
863
|
+
return node && nearestScroller(node, tolerance) || node;
|
|
864
|
+
}
|
|
852
865
|
|
|
853
866
|
// client/styles.js
|
|
854
867
|
var CSS = `
|
|
@@ -1004,6 +1017,10 @@ var CSS = `
|
|
|
1004
1017
|
.dshsc-row.off { opacity: .7; }
|
|
1005
1018
|
.dshsc-cell { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
1006
1019
|
.dshsc-namerow { display: flex; align-items: center; gap: 7px; min-width: 0; }
|
|
1020
|
+
/* 可见性状态点:中性表达(实心=模型可见,空心=模型不可见),不绑定任何 Agent 身份 */
|
|
1021
|
+
.dshsc-vdot { flex: none; width: 6px; height: 6px; border-radius: 50%; box-sizing: border-box;
|
|
1022
|
+
background: var(--dsw-alias-state-success-primary); opacity: .75; }
|
|
1023
|
+
.dshsc-vdot.hidden { background: transparent; border: 1px solid var(--dsw-alias-label-tertiary); opacity: .9; }
|
|
1007
1024
|
.dshsc-name { font-size: 13px; font-weight: 600; letter-spacing: -.005em; min-width: 0;
|
|
1008
1025
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1009
1026
|
/* 技能名本身是查看入口:清掉 button 默认外观,保留 hover 下划线提示可点 */
|
|
@@ -1029,9 +1046,6 @@ var CSS = `
|
|
|
1029
1046
|
.dshsc-agent { display: inline-flex; align-items: center; gap: 5px; flex: none; height: 20px; padding: 0 7px;
|
|
1030
1047
|
border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; color: var(--dsw-alias-label-secondary);
|
|
1031
1048
|
background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent); font-size: 11px; white-space: nowrap; }
|
|
1032
|
-
.dshsc-agent.dsh { color: var(--dsw-alias-brand-primary); border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 30%, var(--dsw-alias-border-l1));
|
|
1033
|
-
background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent); }
|
|
1034
|
-
.dshsc-agent .dshsc-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: .8; }
|
|
1035
1049
|
.dshsc-agent.more, .dshsc-agent.scope { border-style: dashed; background: transparent; color: var(--dsw-alias-label-tertiary); }
|
|
1036
1050
|
.dshsc-agent.more { margin: 0; font: inherit; font-size: 11px; line-height: 1; cursor: pointer;
|
|
1037
1051
|
-webkit-appearance: none; appearance: none; }
|
|
@@ -1267,8 +1281,9 @@ var CSS = `
|
|
|
1267
1281
|
color: var(--dsw-alias-label-secondary); line-height: 1.6; }
|
|
1268
1282
|
.dshsc-reader-placeholder b { color: var(--dsw-alias-label-primary); font-size: 13px; }
|
|
1269
1283
|
|
|
1270
|
-
/* 文件 tab
|
|
1271
|
-
.dshsc-reader-files { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
|
1284
|
+
/* 文件 tab:抽屉里上下排;整页(宽容器)左树右预览 */
|
|
1285
|
+
.dshsc-reader-files { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; }
|
|
1286
|
+
.dshsc-reader-page .dshsc-reader-files { grid-template-columns: minmax(200px, 240px) minmax(0, 1fr); }
|
|
1272
1287
|
.dshsc-reader-tree { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: auto;
|
|
1273
1288
|
max-height: 300px; background: var(--dsw-alias-bg-layer-1); }
|
|
1274
1289
|
.dshsc-reader-preview { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: hidden;
|
|
@@ -1386,8 +1401,11 @@ var CSS = `
|
|
|
1386
1401
|
background: var(--dsw-alias-bg-layer-1); }
|
|
1387
1402
|
.dshsc-reader-hero .dshsc-reader-head { border-bottom: 0; }
|
|
1388
1403
|
.dshsc-reader-hero .dshsc-reader-desc { -webkit-line-clamp: 3; max-width: 78ch; }
|
|
1389
|
-
.dshsc-reader-layout { flex: 1; min-height: 0; display: grid; grid-template-columns:
|
|
1404
|
+
.dshsc-reader-layout { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr);
|
|
1390
1405
|
gap: 18px; align-items: stretch; }
|
|
1406
|
+
/* 只有目录真的在的时候才开第二轨:否则唯一的正文列会被自动摆进 180px 的目录轨,
|
|
1407
|
+
文件 / 信息 tab 会窄成一条(树里的文件名被截断、预览挤到下面)。 */
|
|
1408
|
+
.dshsc-reader-layout.has-toc { grid-template-columns: 180px minmax(0, 1fr); }
|
|
1391
1409
|
.dshsc-reader-toc { position: sticky; top: 0; display: flex; flex-direction: column; gap: 1px;
|
|
1392
1410
|
max-height: 70vh; overflow: auto; padding-right: 4px; }
|
|
1393
1411
|
.dshsc-reader-toc-title { font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
|
|
@@ -1413,11 +1431,20 @@ var CSS = `
|
|
|
1413
1431
|
@container dshsc (max-width: 860px) {
|
|
1414
1432
|
/* 行内「查看」先让位:技能名本身仍是入口 */
|
|
1415
1433
|
.dshsc-row .dshsc-view { display: none; }
|
|
1416
|
-
.dshsc-reader-layout { grid-template-columns: minmax(0, 1fr); }
|
|
1434
|
+
.dshsc-reader-layout.has-toc { grid-template-columns: minmax(0, 1fr); }
|
|
1435
|
+
.dshsc-reader-page .dshsc-reader-files { grid-template-columns: minmax(0, 1fr); }
|
|
1417
1436
|
.dshsc-reader-toc { display: none; }
|
|
1418
1437
|
.dshsc-reader-page .dshsc-reader-desc { -webkit-line-clamp: 2; }
|
|
1419
1438
|
}
|
|
1420
1439
|
|
|
1440
|
+
/* 描述默认按行数截断;真被截断时下面给「展开 / 收起」,所以要压在上面所有 clamp 规则之后 */
|
|
1441
|
+
.dshsc-reader-descblock { display: flex; flex-direction: column; align-items: flex-start; }
|
|
1442
|
+
.dshsc-reader-desc.expanded { display: block; overflow: visible; -webkit-line-clamp: unset; }
|
|
1443
|
+
.dshsc-reader-more { margin-top: 3px; padding: 0; border: 0; border-radius: 4px; background: transparent;
|
|
1444
|
+
color: var(--dsw-alias-brand-primary); font: inherit; font-size: 11.5px; line-height: 1.6; cursor: pointer; }
|
|
1445
|
+
.dshsc-reader-more:hover { text-decoration: underline; }
|
|
1446
|
+
.dshsc-reader-more:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1447
|
+
|
|
1421
1448
|
.dshsc-toast { position: fixed; right: 18px; bottom: 18px; z-index: 70; font-size: 12px; padding: 10px 13px; border-radius: 9px;
|
|
1422
1449
|
background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l2);
|
|
1423
1450
|
box-shadow: 0 8px 24px rgba(0,0,0,.22); max-width: 420px; }
|
|
@@ -1435,7 +1462,7 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1435
1462
|
});
|
|
1436
1463
|
}
|
|
1437
1464
|
async function toggleMode(row) {
|
|
1438
|
-
const mode = row.
|
|
1465
|
+
const mode = row.enabled === false ? "on" : "off";
|
|
1439
1466
|
await run(`mode:${row.name}`, () => apiCall("POST", `/skills/${encodeURIComponent(row.name)}/mode`, { mode }));
|
|
1440
1467
|
}
|
|
1441
1468
|
async function updateOne(row, overwrite) {
|
|
@@ -1498,17 +1525,20 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1498
1525
|
}
|
|
1499
1526
|
});
|
|
1500
1527
|
}
|
|
1501
|
-
async function
|
|
1502
|
-
const current = new Set(row.
|
|
1503
|
-
const desired = new Set(
|
|
1528
|
+
async function saveExports(row, targets) {
|
|
1529
|
+
const current = new Set(row.exports.map((item) => item.target));
|
|
1530
|
+
const desired = new Set(targets);
|
|
1504
1531
|
const toRemove = [...current].filter((target) => !desired.has(target));
|
|
1505
|
-
|
|
1532
|
+
const toAdd = [...desired].filter((target) => !current.has(target));
|
|
1533
|
+
await run(`exports:${row.name}`, async () => {
|
|
1506
1534
|
if (toRemove.length > 0) {
|
|
1507
|
-
await apiCall("POST", "/
|
|
1535
|
+
await apiCall("POST", "/exports", { skill: row.name, targets: toRemove, action: "remove" });
|
|
1536
|
+
}
|
|
1537
|
+
if (toAdd.length > 0) {
|
|
1538
|
+
await apiCall("POST", "/exports", { skill: row.name, targets: toAdd, action: "add" });
|
|
1508
1539
|
}
|
|
1509
|
-
await apiCall("POST", "/links", { skill: row.name, targets: [...desired], action: "add", scopeProjects });
|
|
1510
1540
|
});
|
|
1511
|
-
setToast({ kind: "ok", text: `${row.name} · ${t("action.
|
|
1541
|
+
setToast({ kind: "ok", text: `${row.name} · ${t("action.exports")} ✓` });
|
|
1512
1542
|
}
|
|
1513
1543
|
function requestAddSkill() {
|
|
1514
1544
|
setModal({ type: "add-skill" });
|
|
@@ -1566,7 +1596,7 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1566
1596
|
updateAll,
|
|
1567
1597
|
checkUpdates,
|
|
1568
1598
|
requestDelete,
|
|
1569
|
-
|
|
1599
|
+
saveExports,
|
|
1570
1600
|
requestAddSkill,
|
|
1571
1601
|
requestAddSubscription,
|
|
1572
1602
|
probeSkills,
|
|
@@ -1913,10 +1943,10 @@ function Header({
|
|
|
1913
1943
|
h("button", {
|
|
1914
1944
|
className: "dshsc-btn",
|
|
1915
1945
|
disabled: !writable,
|
|
1916
|
-
title: `${t("
|
|
1917
|
-
"aria-label": `${t("
|
|
1946
|
+
title: `${t("exportDefault")}:${autoLinkLabel}`,
|
|
1947
|
+
"aria-label": `${t("exportDefault")}:${autoLinkLabel}`,
|
|
1918
1948
|
onClick: onOpenAutoLink
|
|
1919
|
-
}, `${t("
|
|
1949
|
+
}, `${t("exportDefault")} · ${autoLinkCount} ▾`),
|
|
1920
1950
|
h("button", {
|
|
1921
1951
|
className: "dshsc-btn primary",
|
|
1922
1952
|
disabled: !writable,
|
|
@@ -2006,6 +2036,7 @@ function InstalledTab({
|
|
|
2006
2036
|
actions
|
|
2007
2037
|
}) {
|
|
2008
2038
|
const updatable = (row) => UPDATABLE.has(row.status);
|
|
2039
|
+
const sharedTargets = new Set((data?.agents?.detected || []).filter((agent) => agent.kind === "agent" && agent.sharedHome === "yes").map((agent) => agent.target));
|
|
2009
2040
|
function statusTag(row) {
|
|
2010
2041
|
if (row.status === "unknown") return null;
|
|
2011
2042
|
if (row.status === "no-upstream") {
|
|
@@ -2037,21 +2068,25 @@ function InstalledTab({
|
|
|
2037
2068
|
);
|
|
2038
2069
|
}
|
|
2039
2070
|
function agentChips(row) {
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
if (
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2071
|
+
const exported = (row.exports || []).filter(
|
|
2072
|
+
(item) => !item.broken && !sharedTargets.has(item.target)
|
|
2073
|
+
);
|
|
2074
|
+
if (exported.length === 0) {
|
|
2075
|
+
return [h("span", {
|
|
2076
|
+
key: "none",
|
|
2077
|
+
className: "dshsc-unlinked",
|
|
2078
|
+
title: t("meta.noExportHint")
|
|
2079
|
+
}, t("meta.noExport"))];
|
|
2048
2080
|
}
|
|
2049
|
-
const chips =
|
|
2050
|
-
|
|
2081
|
+
const chips = [];
|
|
2082
|
+
const labels = exported.map((item) => item.label);
|
|
2083
|
+
const shown = exported.slice(0, 3);
|
|
2084
|
+
shown.forEach((item, index) => chips.push(h("span", {
|
|
2085
|
+
className: "dshsc-agent",
|
|
2051
2086
|
key: `agent-${index}`,
|
|
2052
|
-
title: label
|
|
2053
|
-
}, label
|
|
2054
|
-
if (
|
|
2087
|
+
title: item.label
|
|
2088
|
+
}, item.label)));
|
|
2089
|
+
if (exported.length > shown.length) {
|
|
2055
2090
|
const open = agentTip?.rowName === row.name;
|
|
2056
2091
|
chips.push(h("button", {
|
|
2057
2092
|
type: "button",
|
|
@@ -2065,21 +2100,17 @@ function InstalledTab({
|
|
|
2065
2100
|
onFocus: (event) => actions.onOpenAgentTip(event, row.name, labels),
|
|
2066
2101
|
onMouseLeave: actions.onCloseAgentTip,
|
|
2067
2102
|
onBlur: actions.onCloseAgentTip
|
|
2068
|
-
}, `+${
|
|
2069
|
-
}
|
|
2070
|
-
if (scopeNames.length > 0) {
|
|
2071
|
-
chips.push(h("span", {
|
|
2072
|
-
className: "dshsc-agent scope",
|
|
2073
|
-
key: "scope",
|
|
2074
|
-
title: `${t("meta.scope")}:${projects.length > 0 ? projects.join("、") : scopeNames.join("、")}`
|
|
2075
|
-
}, `${t("meta.onlyProject")} ${scopeNames.join("、")}`));
|
|
2103
|
+
}, `+${exported.length - shown.length}`));
|
|
2076
2104
|
}
|
|
2077
2105
|
return chips;
|
|
2078
2106
|
}
|
|
2079
2107
|
function rowView(row) {
|
|
2080
|
-
const off = row.
|
|
2108
|
+
const off = row.enabled === false;
|
|
2081
2109
|
const tracked = row.status !== "unmanaged";
|
|
2082
2110
|
const canUpdate = writable && updatable(row);
|
|
2111
|
+
const hidden = !off && !row.visible;
|
|
2112
|
+
const manualOnly = hidden && Boolean(row.visibility?.selfHidden);
|
|
2113
|
+
const visLabel = hidden ? t("meta.modelHidden") : t("meta.modelVisible");
|
|
2083
2114
|
return h(
|
|
2084
2115
|
"div",
|
|
2085
2116
|
{ className: `dshsc-row${off ? " off" : ""}`, key: row.name },
|
|
@@ -2089,6 +2120,12 @@ function InstalledTab({
|
|
|
2089
2120
|
h(
|
|
2090
2121
|
"div",
|
|
2091
2122
|
{ className: "dshsc-namerow" },
|
|
2123
|
+
off ? null : h("span", {
|
|
2124
|
+
className: `dshsc-vdot${hidden ? " hidden" : ""}`,
|
|
2125
|
+
role: "img",
|
|
2126
|
+
title: visLabel,
|
|
2127
|
+
"aria-label": visLabel
|
|
2128
|
+
}),
|
|
2092
2129
|
h("button", {
|
|
2093
2130
|
type: "button",
|
|
2094
2131
|
className: "dshsc-name dshsc-namebtn",
|
|
@@ -2097,10 +2134,25 @@ function InstalledTab({
|
|
|
2097
2134
|
onClick: () => actions.onView(row)
|
|
2098
2135
|
}, row.name),
|
|
2099
2136
|
statusTag(row),
|
|
2137
|
+
manualOnly ? h("span", {
|
|
2138
|
+
className: "dshsc-tag off",
|
|
2139
|
+
title: t("visibility.manualOnlyHint"),
|
|
2140
|
+
"aria-label": t("visibility.manualOnlyHint")
|
|
2141
|
+
}, t("meta.manualOnly")) : null,
|
|
2100
2142
|
off ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null
|
|
2101
2143
|
),
|
|
2102
2144
|
sourceLine(row),
|
|
2103
2145
|
off ? h("div", { className: "dshsc-note" }, t("mode.offHint")) : null,
|
|
2146
|
+
// manual-only 用名字旁的「仅手动」标志表达,不占一行警告;
|
|
2147
|
+
// 被更靠前的同名副本盖住时由下面的 foreignWinner 点名,这里不再补「扫描目录里没有它」。
|
|
2148
|
+
hidden && !manualOnly && row.visibility?.winnerIsCanonical !== false ? h("div", { className: "dshsc-note warn" }, t("visibility.hiddenHint")) : null,
|
|
2149
|
+
row.visibility?.shadowScopeMismatch ? h("div", { className: "dshsc-note warn" }, t("visibility.scopeMismatch")) : null,
|
|
2150
|
+
// 同名副本在更小的 rank 上:模型看到的不是这一份,必须点名路径
|
|
2151
|
+
row.visibility?.winnerIsCanonical === false ? h(
|
|
2152
|
+
"div",
|
|
2153
|
+
{ className: "dshsc-note warn" },
|
|
2154
|
+
t("visibility.foreignWinner").replace("{path}", row.visibility.winnerPath || "?")
|
|
2155
|
+
) : null,
|
|
2104
2156
|
row.status === "broken-link" ? h("div", { className: "dshsc-note warn" }, t("status.brokenHint")) : null
|
|
2105
2157
|
),
|
|
2106
2158
|
h("div", { className: "dshsc-agents" }, agentChips(row)),
|
|
@@ -2110,12 +2162,12 @@ function InstalledTab({
|
|
|
2110
2162
|
h("button", {
|
|
2111
2163
|
className: "dshsc-switch",
|
|
2112
2164
|
role: "switch",
|
|
2113
|
-
"aria-checked": String(tracked && row.
|
|
2165
|
+
"aria-checked": String(tracked && row.enabled !== false),
|
|
2114
2166
|
"aria-label": `${row.name} ${t("mode.on")}/${t("mode.off")}`,
|
|
2115
2167
|
// 未纳入追踪时调 setMode 只会报「没有安装记录」,不如直接禁用
|
|
2116
2168
|
disabled: busy !== null || !tracked,
|
|
2117
2169
|
onClick: () => void actions.onToggleMode(row),
|
|
2118
|
-
title: tracked ? row.
|
|
2170
|
+
title: tracked ? row.enabled !== false ? t("mode.off") : t("mode.on") : t("adopt.switchHint")
|
|
2119
2171
|
}, h("span", { className: "dshsc-knob" })),
|
|
2120
2172
|
h("button", {
|
|
2121
2173
|
className: "dshsc-btn ghost dshsc-view",
|
|
@@ -2134,7 +2186,7 @@ function InstalledTab({
|
|
|
2134
2186
|
disabled: !canUpdate || busy !== null,
|
|
2135
2187
|
onClick: () => actions.onUpdate(row)
|
|
2136
2188
|
}, t("action.update")),
|
|
2137
|
-
h("button", { className: "dshsc-btn ghost", disabled: busy !== null, onClick: () => actions.
|
|
2189
|
+
h("button", { className: "dshsc-btn ghost", disabled: busy !== null, onClick: () => actions.onExports(row) }, t("action.exports")),
|
|
2138
2190
|
h("button", {
|
|
2139
2191
|
className: "dshsc-btn ghost danger sq",
|
|
2140
2192
|
disabled: !writable || busy !== null,
|
|
@@ -2147,18 +2199,17 @@ function InstalledTab({
|
|
|
2147
2199
|
}
|
|
2148
2200
|
function groupedRows() {
|
|
2149
2201
|
const groups = [];
|
|
2150
|
-
const
|
|
2151
|
-
const
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2154
|
-
const unlinkedRows = visibleRows.filter((row) => row.mode === "on" && !row.scopeProjects?.length && !row.links.some((link) => !link.broken));
|
|
2155
|
-
groups.push({ key: "dsh-global", title: t("group.dshGlobal"), rows: dshRows, dot: true });
|
|
2156
|
-
if (projectRows.length) groups.push({ key: "dsh-project", title: t("group.dshProject"), rows: projectRows, dot: true });
|
|
2202
|
+
const visibleGroup = visibleRows.filter((row) => row.enabled !== false && row.visible);
|
|
2203
|
+
const offRows = visibleRows.filter((row) => row.enabled === false);
|
|
2204
|
+
const hiddenRows = visibleRows.filter((row) => row.enabled !== false && !row.visible);
|
|
2205
|
+
groups.push({ key: "visible", title: t("group.visible"), rows: visibleGroup, dot: true });
|
|
2157
2206
|
if (offRows.length) groups.push({ key: "off", title: t("group.off"), rows: offRows });
|
|
2158
|
-
if (
|
|
2207
|
+
if (hiddenRows.length) groups.push({ key: "hidden", title: t("group.hidden"), rows: hiddenRows });
|
|
2159
2208
|
for (const agent of data?.agents?.detected || []) {
|
|
2160
2209
|
if (agent.kind !== "agent") continue;
|
|
2161
|
-
const matched = visibleRows.filter(
|
|
2210
|
+
const matched = visibleRows.filter(
|
|
2211
|
+
(row) => (row.exports || []).some((item) => item.target === agent.target && !item.broken)
|
|
2212
|
+
);
|
|
2162
2213
|
if (matched.length) groups.push({ key: agent.target, title: agent.label, rows: matched });
|
|
2163
2214
|
}
|
|
2164
2215
|
return groups.filter((group) => group.rows.length > 0);
|
|
@@ -2313,6 +2364,7 @@ function InstalledTab({
|
|
|
2313
2364
|
|
|
2314
2365
|
// client/views/agent-tip.js
|
|
2315
2366
|
function AgentTip({ t, agentTip, agentTipPos, nodeRef }) {
|
|
2367
|
+
const labels = agentTip.labels || [];
|
|
2316
2368
|
const node = h(
|
|
2317
2369
|
"div",
|
|
2318
2370
|
{
|
|
@@ -2326,15 +2378,16 @@ function AgentTip({ t, agentTip, agentTipPos, nodeRef }) {
|
|
|
2326
2378
|
"div",
|
|
2327
2379
|
{ className: "dshsc-agenttip-head" },
|
|
2328
2380
|
h("span", null, t("meta.agentsAll")),
|
|
2329
|
-
h("span", { className: "dshsc-agenttip-n" },
|
|
2381
|
+
h("span", { className: "dshsc-agenttip-n" }, labels.length)
|
|
2330
2382
|
),
|
|
2331
2383
|
h(
|
|
2332
2384
|
"div",
|
|
2333
2385
|
{ className: "dshsc-agenttip-list" },
|
|
2334
|
-
|
|
2335
|
-
className:
|
|
2336
|
-
key: `agenttip-${index}
|
|
2337
|
-
|
|
2386
|
+
labels.map((label, index) => h("span", {
|
|
2387
|
+
className: "dshsc-agent",
|
|
2388
|
+
key: `agenttip-${index}`,
|
|
2389
|
+
title: label
|
|
2390
|
+
}, label))
|
|
2338
2391
|
)
|
|
2339
2392
|
);
|
|
2340
2393
|
return portalize(node);
|
|
@@ -2447,7 +2500,12 @@ function SubscriptionsTab({
|
|
|
2447
2500
|
}
|
|
2448
2501
|
|
|
2449
2502
|
// client/views/modals.js
|
|
2503
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
2504
|
+
|
|
2505
|
+
// client/views/add-skill.js
|
|
2450
2506
|
var import_react3 = __toESM(require("react"), 1);
|
|
2507
|
+
|
|
2508
|
+
// client/views/overlay.js
|
|
2451
2509
|
function Overlay({ t, title, children, footer, onCancel }) {
|
|
2452
2510
|
useOverlayLayer(true, onCancel);
|
|
2453
2511
|
return portalize(h(
|
|
@@ -2472,212 +2530,8 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
2472
2530
|
)
|
|
2473
2531
|
), overlayHost());
|
|
2474
2532
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
const incomplete = fields.some(
|
|
2478
|
-
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
2479
|
-
);
|
|
2480
|
-
return h(
|
|
2481
|
-
Overlay,
|
|
2482
|
-
{
|
|
2483
|
-
t,
|
|
2484
|
-
title,
|
|
2485
|
-
onCancel,
|
|
2486
|
-
footer: [
|
|
2487
|
-
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2488
|
-
h("button", {
|
|
2489
|
-
key: "s",
|
|
2490
|
-
className: "dshsc-btn primary",
|
|
2491
|
-
disabled: incomplete,
|
|
2492
|
-
onClick: () => onSubmit(form)
|
|
2493
|
-
}, t("action.save"))
|
|
2494
|
-
]
|
|
2495
|
-
},
|
|
2496
|
-
hint ? h("p", { className: "dshsc-sub" }, hint) : null,
|
|
2497
|
-
...fields.map((field) => h(
|
|
2498
|
-
"label",
|
|
2499
|
-
{ className: "dshsc-field", key: field.key },
|
|
2500
|
-
h("span", null, field.label),
|
|
2501
|
-
h("input", {
|
|
2502
|
-
value: form[field.key],
|
|
2503
|
-
placeholder: field.placeholder,
|
|
2504
|
-
onChange: (event) => setForm((prev) => ({ ...prev, [field.key]: event.target.value }))
|
|
2505
|
-
})
|
|
2506
|
-
))
|
|
2507
|
-
);
|
|
2508
|
-
}
|
|
2509
|
-
function ConfirmModal({ t, title, body, onCancel, onConfirm, danger, confirmLabel }) {
|
|
2510
|
-
return h(Overlay, {
|
|
2511
|
-
t,
|
|
2512
|
-
title,
|
|
2513
|
-
onCancel,
|
|
2514
|
-
footer: [
|
|
2515
|
-
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2516
|
-
h("button", { key: "o", className: `dshsc-btn${danger ? " danger" : " primary"}`, onClick: onConfirm }, confirmLabel || t("action.save"))
|
|
2517
|
-
]
|
|
2518
|
-
}, h("p", { className: "dshsc-sub", style: { whiteSpace: "pre-wrap" } }, body));
|
|
2519
|
-
}
|
|
2520
|
-
function LinksModal({ t, row, agents, workspaces, pickDirectory, onCancel, onSave }) {
|
|
2521
|
-
const candidates = agents.filter(
|
|
2522
|
-
(agent) => agent.kind === "dsh" || agent.kind !== "project" && agent.exists
|
|
2523
|
-
);
|
|
2524
|
-
const [targets, setTargets] = import_react3.default.useState(() => {
|
|
2525
|
-
const visible = new Set(candidates.map((agent) => agent.target));
|
|
2526
|
-
const initial = new Set(row.links.map((link) => link.target).filter((target) => visible.has(target)));
|
|
2527
|
-
initial.add("dsh-global");
|
|
2528
|
-
return initial;
|
|
2529
|
-
});
|
|
2530
|
-
const [scopeProjects, setScopeProjects] = import_react3.default.useState(row.scopeProjects || []);
|
|
2531
|
-
const [scopeMode, setScopeMode] = import_react3.default.useState((row.scopeProjects || []).length > 0 ? "projects" : "global");
|
|
2532
|
-
const workspacePaths = new Set(workspaces.map((workspace) => workspace.path));
|
|
2533
|
-
const otherProjects = scopeProjects.filter((project) => !workspacePaths.has(project));
|
|
2534
|
-
const toggle = (target) => setTargets((prev) => {
|
|
2535
|
-
const next = new Set(prev);
|
|
2536
|
-
if (next.has(target)) next.delete(target);
|
|
2537
|
-
else next.add(target);
|
|
2538
|
-
return next;
|
|
2539
|
-
});
|
|
2540
|
-
const toggleProject = (project) => setScopeProjects((prev) => prev.includes(project) ? prev.filter((item) => item !== project) : [...prev, project]);
|
|
2541
|
-
const chooseDirectory = async () => {
|
|
2542
|
-
const dir = await pickDirectory?.();
|
|
2543
|
-
if (!dir) return;
|
|
2544
|
-
setScopeProjects((prev) => prev.includes(dir) ? prev : [...prev, dir]);
|
|
2545
|
-
};
|
|
2546
|
-
return h(
|
|
2547
|
-
Overlay,
|
|
2548
|
-
{
|
|
2549
|
-
t,
|
|
2550
|
-
title: `${t("action.links")} · ${row.name}`,
|
|
2551
|
-
onCancel,
|
|
2552
|
-
footer: [
|
|
2553
|
-
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2554
|
-
h("button", {
|
|
2555
|
-
key: "s",
|
|
2556
|
-
className: "dshsc-btn primary",
|
|
2557
|
-
onClick: () => onSave([...targets], scopeMode === "projects" ? scopeProjects.filter((item) => item.trim()) : [])
|
|
2558
|
-
}, t("action.save"))
|
|
2559
|
-
]
|
|
2560
|
-
},
|
|
2561
|
-
h("div", { className: "dshsc-group" }, t("links.targets")),
|
|
2562
|
-
h("p", { className: "dshsc-sub" }, t("links.detectedHint")),
|
|
2563
|
-
h("p", { className: "dshsc-sub" }, t("links.canonical").replace("{path}", `~/.agents/skills/${row.name}`)),
|
|
2564
|
-
...candidates.map((agent) => h(
|
|
2565
|
-
"label",
|
|
2566
|
-
{ className: `dshsc-check${agent.target === "dsh-global" ? " locked" : ""}`, key: agent.target },
|
|
2567
|
-
h("input", {
|
|
2568
|
-
type: "checkbox",
|
|
2569
|
-
checked: agent.target === "dsh-global" ? true : targets.has(agent.target),
|
|
2570
|
-
disabled: agent.target === "dsh-global",
|
|
2571
|
-
onChange: () => toggle(agent.target)
|
|
2572
|
-
}),
|
|
2573
|
-
h("span", null, agent.kind === "dsh" ? t("links.dshGlobal") : agent.label),
|
|
2574
|
-
agent.target === "dsh-global" ? h("small", null, t("links.required")) : null,
|
|
2575
|
-
h("small", { title: agent.dir }, agent.dir)
|
|
2576
|
-
)),
|
|
2577
|
-
h("div", { className: "dshsc-group" }, t("links.scope")),
|
|
2578
|
-
h(
|
|
2579
|
-
"label",
|
|
2580
|
-
{ className: "dshsc-check" },
|
|
2581
|
-
h("input", { type: "radio", name: `scope-${row.name}`, checked: scopeMode === "global", onChange: () => setScopeMode("global") }),
|
|
2582
|
-
h("span", null, t("links.scopeGlobal"))
|
|
2583
|
-
),
|
|
2584
|
-
h(
|
|
2585
|
-
"label",
|
|
2586
|
-
{ className: "dshsc-check" },
|
|
2587
|
-
h("input", { type: "radio", name: `scope-${row.name}`, checked: scopeMode === "projects", onChange: () => setScopeMode("projects") }),
|
|
2588
|
-
h("span", null, t("links.scopeProjects"))
|
|
2589
|
-
),
|
|
2590
|
-
scopeMode === "projects" ? h(
|
|
2591
|
-
"div",
|
|
2592
|
-
null,
|
|
2593
|
-
h("div", { className: "dshsc-group" }, t("links.workspaces")),
|
|
2594
|
-
workspaces.length === 0 ? h("p", { className: "dshsc-sub" }, t("links.noWorkspaces")) : h("div", { className: "dshsc-panel" }, workspaces.map((workspace) => h(
|
|
2595
|
-
"label",
|
|
2596
|
-
{ className: "dshsc-check", key: workspace.id },
|
|
2597
|
-
h("input", {
|
|
2598
|
-
type: "checkbox",
|
|
2599
|
-
checked: scopeProjects.includes(workspace.path),
|
|
2600
|
-
onChange: () => toggleProject(workspace.path)
|
|
2601
|
-
}),
|
|
2602
|
-
h("span", null, workspace.title),
|
|
2603
|
-
h("small", null, workspace.path)
|
|
2604
|
-
))),
|
|
2605
|
-
otherProjects.length > 0 ? h("div", { className: "dshsc-group" }, t("links.selected")) : null,
|
|
2606
|
-
...otherProjects.map((project) => h(
|
|
2607
|
-
"div",
|
|
2608
|
-
{ className: "dshsc-sub-entry", key: project },
|
|
2609
|
-
h("span", { className: "dshsc-code" }, project),
|
|
2610
|
-
h(
|
|
2611
|
-
"div",
|
|
2612
|
-
{ className: "dshsc-actions" },
|
|
2613
|
-
h("button", {
|
|
2614
|
-
className: "dshsc-btn ghost danger",
|
|
2615
|
-
onClick: () => setScopeProjects((prev) => prev.filter((item) => item !== project))
|
|
2616
|
-
}, t("action.delete"))
|
|
2617
|
-
)
|
|
2618
|
-
)),
|
|
2619
|
-
h("button", { className: "dshsc-btn", style: { alignSelf: "flex-start", marginTop: 6 }, onClick: () => void chooseDirectory() }, t("links.pickDirectory"))
|
|
2620
|
-
) : null,
|
|
2621
|
-
h("p", { className: "dshsc-sub" }, t("links.hint"))
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
|
-
function AutoLinkModal({ t, settings, agents, dsh, onCancel, onSave }) {
|
|
2625
|
-
const [targets, setTargets] = import_react3.default.useState(() => new Set(
|
|
2626
|
-
Array.isArray(settings.autoLinkTargets) ? settings.autoLinkTargets : agents.map((agent) => agent.target)
|
|
2627
|
-
));
|
|
2628
|
-
const toggle = (target) => setTargets((prev) => {
|
|
2629
|
-
const next = new Set(prev);
|
|
2630
|
-
if (next.has(target)) next.delete(target);
|
|
2631
|
-
else next.add(target);
|
|
2632
|
-
return next;
|
|
2633
|
-
});
|
|
2634
|
-
return h(
|
|
2635
|
-
Overlay,
|
|
2636
|
-
{
|
|
2637
|
-
t,
|
|
2638
|
-
title: t("autoLink"),
|
|
2639
|
-
onCancel,
|
|
2640
|
-
footer: [
|
|
2641
|
-
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2642
|
-
h("button", { key: "s", className: "dshsc-btn primary", onClick: () => onSave([...targets]) }, t("action.save"))
|
|
2643
|
-
]
|
|
2644
|
-
},
|
|
2645
|
-
h("div", { className: "dshsc-group" }, t("autoLink.detected")),
|
|
2646
|
-
h(
|
|
2647
|
-
"div",
|
|
2648
|
-
{ className: "dshsc-panel" },
|
|
2649
|
-
// dsh(全局)始终接入:列表里也勾上,只是不能改,勾选数与页头的计数器才对得上
|
|
2650
|
-
h(
|
|
2651
|
-
"label",
|
|
2652
|
-
{ className: "dshsc-check locked", key: "dsh-global" },
|
|
2653
|
-
h("input", {
|
|
2654
|
-
type: "checkbox",
|
|
2655
|
-
checked: true,
|
|
2656
|
-
disabled: true,
|
|
2657
|
-
readOnly: true,
|
|
2658
|
-
onChange: () => {
|
|
2659
|
-
}
|
|
2660
|
-
}),
|
|
2661
|
-
h("span", null, dsh?.label || t("links.dshGlobal")),
|
|
2662
|
-
h("small", null, t("links.required")),
|
|
2663
|
-
dsh?.dir ? h("small", { title: dsh.dir }, dsh.dir) : null
|
|
2664
|
-
),
|
|
2665
|
-
...agents.map((agent) => h(
|
|
2666
|
-
"label",
|
|
2667
|
-
{ className: "dshsc-check", key: agent.target },
|
|
2668
|
-
h("input", {
|
|
2669
|
-
type: "checkbox",
|
|
2670
|
-
checked: targets.has(agent.target),
|
|
2671
|
-
onChange: () => toggle(agent.target)
|
|
2672
|
-
}),
|
|
2673
|
-
h("span", null, agent.label),
|
|
2674
|
-
h("small", null, agent.dir)
|
|
2675
|
-
))
|
|
2676
|
-
),
|
|
2677
|
-
agents.length === 0 ? h("p", { className: "dshsc-sub" }, t("autoLink.none")) : null,
|
|
2678
|
-
h("p", { className: "dshsc-sub" }, t("autoLink.hint"))
|
|
2679
|
-
);
|
|
2680
|
-
}
|
|
2533
|
+
|
|
2534
|
+
// client/views/add-skill.js
|
|
2681
2535
|
function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
2682
2536
|
const [source, setSource] = import_react3.default.useState("");
|
|
2683
2537
|
const [phase, setPhase] = import_react3.default.useState("idle");
|
|
@@ -2818,7 +2672,135 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
2818
2672
|
h("p", { className: "dshsc-sub" }, t("form.hint"))
|
|
2819
2673
|
);
|
|
2820
2674
|
}
|
|
2821
|
-
|
|
2675
|
+
|
|
2676
|
+
// client/views/modals.js
|
|
2677
|
+
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
2678
|
+
const [form, setForm] = import_react4.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
2679
|
+
const incomplete = fields.some(
|
|
2680
|
+
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
2681
|
+
);
|
|
2682
|
+
return h(
|
|
2683
|
+
Overlay,
|
|
2684
|
+
{
|
|
2685
|
+
t,
|
|
2686
|
+
title,
|
|
2687
|
+
onCancel,
|
|
2688
|
+
footer: [
|
|
2689
|
+
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2690
|
+
h("button", {
|
|
2691
|
+
key: "s",
|
|
2692
|
+
className: "dshsc-btn primary",
|
|
2693
|
+
disabled: incomplete,
|
|
2694
|
+
onClick: () => onSubmit(form)
|
|
2695
|
+
}, t("action.save"))
|
|
2696
|
+
]
|
|
2697
|
+
},
|
|
2698
|
+
hint ? h("p", { className: "dshsc-sub" }, hint) : null,
|
|
2699
|
+
...fields.map((field) => h(
|
|
2700
|
+
"label",
|
|
2701
|
+
{ className: "dshsc-field", key: field.key },
|
|
2702
|
+
h("span", null, field.label),
|
|
2703
|
+
h("input", {
|
|
2704
|
+
value: form[field.key],
|
|
2705
|
+
placeholder: field.placeholder,
|
|
2706
|
+
onChange: (event) => setForm((prev) => ({ ...prev, [field.key]: event.target.value }))
|
|
2707
|
+
})
|
|
2708
|
+
))
|
|
2709
|
+
);
|
|
2710
|
+
}
|
|
2711
|
+
function ConfirmModal({ t, title, body, onCancel, onConfirm, danger, confirmLabel, hideCancel = false }) {
|
|
2712
|
+
return h(Overlay, {
|
|
2713
|
+
t,
|
|
2714
|
+
title,
|
|
2715
|
+
onCancel,
|
|
2716
|
+
footer: [
|
|
2717
|
+
...hideCancel ? [] : [h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel"))],
|
|
2718
|
+
h("button", { key: "o", className: `dshsc-btn${danger ? " danger" : " primary"}`, onClick: onConfirm }, confirmLabel || t("action.save"))
|
|
2719
|
+
]
|
|
2720
|
+
}, h("p", { className: "dshsc-sub", style: { whiteSpace: "pre-wrap" } }, body));
|
|
2721
|
+
}
|
|
2722
|
+
function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
2723
|
+
const candidates = agents.filter(
|
|
2724
|
+
(agent) => agent.kind === "agent" && agent.exists && agent.exportable
|
|
2725
|
+
);
|
|
2726
|
+
const [targets, setTargets] = import_react4.default.useState(() => {
|
|
2727
|
+
const available = new Set(candidates.map((agent) => agent.target));
|
|
2728
|
+
return new Set((row.exports || []).map((item) => item.target).filter((target) => available.has(target)));
|
|
2729
|
+
});
|
|
2730
|
+
const toggle = (target) => setTargets((prev) => {
|
|
2731
|
+
const next = new Set(prev);
|
|
2732
|
+
if (next.has(target)) next.delete(target);
|
|
2733
|
+
else next.add(target);
|
|
2734
|
+
return next;
|
|
2735
|
+
});
|
|
2736
|
+
return h(
|
|
2737
|
+
Overlay,
|
|
2738
|
+
{
|
|
2739
|
+
t,
|
|
2740
|
+
title: `${t("action.exports")} · ${row.name}`,
|
|
2741
|
+
onCancel,
|
|
2742
|
+
footer: [
|
|
2743
|
+
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2744
|
+
h("button", { key: "s", className: "dshsc-btn primary", onClick: () => onSave([...targets]) }, t("action.save"))
|
|
2745
|
+
]
|
|
2746
|
+
},
|
|
2747
|
+
h("div", { className: "dshsc-group" }, t("exports.targets")),
|
|
2748
|
+
h("p", { className: "dshsc-sub" }, t("exports.detectedHint")),
|
|
2749
|
+
h("p", { className: "dshsc-sub" }, t("exports.canonical").replace("{path}", `~/.agents/skills/${row.name}`)),
|
|
2750
|
+
candidates.length === 0 ? h("p", { className: "dshsc-sub" }, t("exports.none")) : null,
|
|
2751
|
+
...candidates.map((agent) => h(
|
|
2752
|
+
"label",
|
|
2753
|
+
{ className: "dshsc-check", key: agent.target },
|
|
2754
|
+
h("input", {
|
|
2755
|
+
type: "checkbox",
|
|
2756
|
+
checked: targets.has(agent.target),
|
|
2757
|
+
onChange: () => toggle(agent.target)
|
|
2758
|
+
}),
|
|
2759
|
+
h("span", null, agent.label),
|
|
2760
|
+
// 候选里已经没有「读共享根」的 agent 了;'no' 是常态不必标注,只提示没验证过的
|
|
2761
|
+
agent.sharedHome === "unknown" ? h("small", null, t("exports.sharedUnknown")) : null,
|
|
2762
|
+
h("small", { title: agent.dir }, agent.dir)
|
|
2763
|
+
)),
|
|
2764
|
+
h("p", { className: "dshsc-sub" }, t("exports.hint"))
|
|
2765
|
+
);
|
|
2766
|
+
}
|
|
2767
|
+
function DefaultExportModal({ t, settings, agents, onCancel, onSave }) {
|
|
2768
|
+
const [targets, setTargets] = import_react4.default.useState(() => new Set(
|
|
2769
|
+
Array.isArray(settings.exportTargets) ? settings.exportTargets : []
|
|
2770
|
+
));
|
|
2771
|
+
const toggle = (target) => setTargets((prev) => {
|
|
2772
|
+
const next = new Set(prev);
|
|
2773
|
+
if (next.has(target)) next.delete(target);
|
|
2774
|
+
else next.add(target);
|
|
2775
|
+
return next;
|
|
2776
|
+
});
|
|
2777
|
+
return h(
|
|
2778
|
+
Overlay,
|
|
2779
|
+
{
|
|
2780
|
+
t,
|
|
2781
|
+
title: t("exportDefault"),
|
|
2782
|
+
onCancel,
|
|
2783
|
+
footer: [
|
|
2784
|
+
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
2785
|
+
h("button", { key: "s", className: "dshsc-btn primary", onClick: () => onSave([...targets]) }, t("action.save"))
|
|
2786
|
+
]
|
|
2787
|
+
},
|
|
2788
|
+
h("div", { className: "dshsc-group" }, t("exportDefault.detected")),
|
|
2789
|
+
agents.length === 0 ? h("p", { className: "dshsc-sub" }, t("exportDefault.none")) : h("div", { className: "dshsc-panel" }, agents.map((agent) => h(
|
|
2790
|
+
"label",
|
|
2791
|
+
{ className: "dshsc-check", key: agent.target },
|
|
2792
|
+
h("input", {
|
|
2793
|
+
type: "checkbox",
|
|
2794
|
+
checked: targets.has(agent.target),
|
|
2795
|
+
onChange: () => toggle(agent.target)
|
|
2796
|
+
}),
|
|
2797
|
+
h("span", null, agent.label),
|
|
2798
|
+
h("small", null, agent.dir)
|
|
2799
|
+
))),
|
|
2800
|
+
h("p", { className: "dshsc-sub" }, t("exportDefault.hint"))
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
function Modals({ t, modal, data, autoLinkAgents, actions, onClose, onSaveAutoLink }) {
|
|
2822
2804
|
if (modal.type === "add-skill") {
|
|
2823
2805
|
return h(AddSkillModal, {
|
|
2824
2806
|
t,
|
|
@@ -2841,26 +2823,23 @@ function Modals({ t, modal, data, autoLinkAgents, autoLinkDsh, pickDirectory, ac
|
|
|
2841
2823
|
onSubmit: (form) => void actions.addSubscription(form)
|
|
2842
2824
|
});
|
|
2843
2825
|
}
|
|
2844
|
-
if (modal.type === "
|
|
2845
|
-
return h(
|
|
2826
|
+
if (modal.type === "exports") {
|
|
2827
|
+
return h(ExportsModal, {
|
|
2846
2828
|
t,
|
|
2847
2829
|
row: modal.row,
|
|
2848
2830
|
agents: data?.agents?.all || [],
|
|
2849
|
-
workspaces: data?.workspaces || [],
|
|
2850
|
-
pickDirectory,
|
|
2851
2831
|
onCancel: onClose,
|
|
2852
|
-
onSave: (targets
|
|
2832
|
+
onSave: (targets) => {
|
|
2853
2833
|
onClose();
|
|
2854
|
-
void actions.
|
|
2834
|
+
void actions.saveExports(modal.row, targets);
|
|
2855
2835
|
}
|
|
2856
2836
|
});
|
|
2857
2837
|
}
|
|
2858
2838
|
if (modal.type === "autolink") {
|
|
2859
|
-
return h(
|
|
2839
|
+
return h(DefaultExportModal, {
|
|
2860
2840
|
t,
|
|
2861
2841
|
settings: data?.settings || {},
|
|
2862
2842
|
agents: autoLinkAgents,
|
|
2863
|
-
dsh: autoLinkDsh,
|
|
2864
2843
|
onCancel: onClose,
|
|
2865
2844
|
onSave: (targets) => void onSaveAutoLink(targets)
|
|
2866
2845
|
});
|
|
@@ -2871,6 +2850,7 @@ function Modals({ t, modal, data, autoLinkAgents, autoLinkDsh, pickDirectory, ac
|
|
|
2871
2850
|
title: t("confirm.deleteTitle"),
|
|
2872
2851
|
body: t("confirm.deleteBody"),
|
|
2873
2852
|
danger: true,
|
|
2853
|
+
confirmLabel: t("action.delete"),
|
|
2874
2854
|
onCancel: onClose,
|
|
2875
2855
|
onConfirm: modal.onConfirm
|
|
2876
2856
|
});
|
|
@@ -2880,6 +2860,7 @@ function Modals({ t, modal, data, autoLinkAgents, autoLinkDsh, pickDirectory, ac
|
|
|
2880
2860
|
t,
|
|
2881
2861
|
title: t("confirm.updateTitle"),
|
|
2882
2862
|
body: t("confirm.updateBody"),
|
|
2863
|
+
confirmLabel: t("action.update"),
|
|
2883
2864
|
onCancel: onClose,
|
|
2884
2865
|
onConfirm: modal.onConfirm
|
|
2885
2866
|
});
|
|
@@ -2893,14 +2874,16 @@ function Modals({ t, modal, data, autoLinkAgents, autoLinkDsh, pickDirectory, ac
|
|
|
2893
2874
|
${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
|
|
2894
2875
|
onCancel: onClose,
|
|
2895
2876
|
onConfirm: onClose,
|
|
2896
|
-
confirmLabel: t("action.
|
|
2877
|
+
confirmLabel: t("action.close"),
|
|
2878
|
+
danger: true,
|
|
2879
|
+
hideCancel: true
|
|
2897
2880
|
});
|
|
2898
2881
|
}
|
|
2899
2882
|
return null;
|
|
2900
2883
|
}
|
|
2901
2884
|
|
|
2902
2885
|
// client/views/skill-reader.js
|
|
2903
|
-
var
|
|
2886
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
2904
2887
|
|
|
2905
2888
|
// client/markdown.js
|
|
2906
2889
|
function classifyHref(href) {
|
|
@@ -3183,7 +3166,7 @@ function resolveRelativeHref(currentDir, href) {
|
|
|
3183
3166
|
return { relPath: out.join("/"), anchor: target.kind === "anchor" ? target.href : "" };
|
|
3184
3167
|
}
|
|
3185
3168
|
|
|
3186
|
-
// client/views/
|
|
3169
|
+
// client/views/markdown-view.js
|
|
3187
3170
|
var PARSE_CACHE = /* @__PURE__ */ new Map();
|
|
3188
3171
|
function parseCached(text) {
|
|
3189
3172
|
const key = String(text ?? "");
|
|
@@ -3196,49 +3179,6 @@ function parseCached(text) {
|
|
|
3196
3179
|
}
|
|
3197
3180
|
return parsed;
|
|
3198
3181
|
}
|
|
3199
|
-
function formatBytes(bytes) {
|
|
3200
|
-
const value = Number(bytes) || 0;
|
|
3201
|
-
if (value < 1024) return `${value} B`;
|
|
3202
|
-
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3203
|
-
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3204
|
-
}
|
|
3205
|
-
function formatWhen(iso) {
|
|
3206
|
-
if (!iso) return null;
|
|
3207
|
-
const date = new Date(iso);
|
|
3208
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
3209
|
-
const pad = (value) => String(value).padStart(2, "0");
|
|
3210
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
3211
|
-
}
|
|
3212
|
-
var MARKDOWN = /\.(md|markdown)$/i;
|
|
3213
|
-
function isMarkdownPath(relPath) {
|
|
3214
|
-
return MARKDOWN.test(String(relPath || ""));
|
|
3215
|
-
}
|
|
3216
|
-
function isScroller(node) {
|
|
3217
|
-
if (!node || typeof getComputedStyle !== "function") return false;
|
|
3218
|
-
const style = getComputedStyle(node);
|
|
3219
|
-
return Boolean(style && /(auto|scroll)/.test(style.overflowY || "") && node.scrollHeight > node.clientHeight + 4);
|
|
3220
|
-
}
|
|
3221
|
-
function nearestScroller(node) {
|
|
3222
|
-
let current = node?.parentElement || null;
|
|
3223
|
-
while (current) {
|
|
3224
|
-
if (isScroller(current)) return current;
|
|
3225
|
-
current = current.parentElement || null;
|
|
3226
|
-
}
|
|
3227
|
-
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3228
|
-
}
|
|
3229
|
-
function scrollHost(node) {
|
|
3230
|
-
if (isScroller(node)) return node;
|
|
3231
|
-
return node && nearestScroller(node) || node;
|
|
3232
|
-
}
|
|
3233
|
-
var STATUS_KEY = {
|
|
3234
|
-
current: "status.current",
|
|
3235
|
-
"update-available": "status.update",
|
|
3236
|
-
"locally-modified": "status.local",
|
|
3237
|
-
conflict: "status.conflict",
|
|
3238
|
-
"upstream-deleted": "status.deleted",
|
|
3239
|
-
"broken-link": "status.broken",
|
|
3240
|
-
unmanaged: "status.unmanaged"
|
|
3241
|
-
};
|
|
3242
3182
|
function inlineNodes(nodes, { onOpenFile, onOpenAnchor, keyPrefix = "i" }) {
|
|
3243
3183
|
if (!Array.isArray(nodes)) return null;
|
|
3244
3184
|
return nodes.map((node, index) => {
|
|
@@ -3371,6 +3311,34 @@ function Markdown({ text, onOpenFile, onOpenAnchor }) {
|
|
|
3371
3311
|
const { blocks } = parseCached(text);
|
|
3372
3312
|
return h("div", { className: "dshsc-md" }, blockNodes(blocks, { onOpenFile, onOpenAnchor }));
|
|
3373
3313
|
}
|
|
3314
|
+
|
|
3315
|
+
// client/views/skill-reader.js
|
|
3316
|
+
function formatBytes(bytes) {
|
|
3317
|
+
const value = Number(bytes) || 0;
|
|
3318
|
+
if (value < 1024) return `${value} B`;
|
|
3319
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3320
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3321
|
+
}
|
|
3322
|
+
function formatWhen(iso) {
|
|
3323
|
+
if (!iso) return null;
|
|
3324
|
+
const date = new Date(iso);
|
|
3325
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
3326
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3327
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
3328
|
+
}
|
|
3329
|
+
var MARKDOWN = /\.(md|markdown)$/i;
|
|
3330
|
+
function isMarkdownPath(relPath) {
|
|
3331
|
+
return MARKDOWN.test(String(relPath || ""));
|
|
3332
|
+
}
|
|
3333
|
+
var STATUS_KEY = {
|
|
3334
|
+
current: "status.current",
|
|
3335
|
+
"update-available": "status.update",
|
|
3336
|
+
"locally-modified": "status.local",
|
|
3337
|
+
conflict: "status.conflict",
|
|
3338
|
+
"upstream-deleted": "status.deleted",
|
|
3339
|
+
"broken-link": "status.broken",
|
|
3340
|
+
unmanaged: "status.unmanaged"
|
|
3341
|
+
};
|
|
3374
3342
|
function TabButton({ id, label, count, active, onSelect }) {
|
|
3375
3343
|
return h("button", {
|
|
3376
3344
|
type: "button",
|
|
@@ -3407,18 +3375,21 @@ function SkillReader({
|
|
|
3407
3375
|
onClose,
|
|
3408
3376
|
onOpenPage,
|
|
3409
3377
|
onUpdate,
|
|
3410
|
-
|
|
3378
|
+
onExports,
|
|
3411
3379
|
onToast
|
|
3412
3380
|
}) {
|
|
3413
3381
|
const { state, file, loadFile, retry } = useSkillReader(row);
|
|
3414
|
-
const [tab, setTab] =
|
|
3415
|
-
const [format, setFormat] =
|
|
3416
|
-
const [docOverride, setDocOverride] =
|
|
3417
|
-
const [fileFormat, setFileFormat] =
|
|
3418
|
-
const [tocActive, setTocActive] =
|
|
3419
|
-
const
|
|
3420
|
-
const
|
|
3421
|
-
const
|
|
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);
|
|
3422
3393
|
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
3423
3394
|
const name = row?.name || "";
|
|
3424
3395
|
const tree = state.tree;
|
|
@@ -3427,20 +3398,49 @@ function SkillReader({
|
|
|
3427
3398
|
const bodyText = docOverride ? docOverride.content : state.body?.content || "";
|
|
3428
3399
|
const description = row?.description || state.body?.meta?.description || "";
|
|
3429
3400
|
const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
|
|
3430
|
-
|
|
3401
|
+
import_react5.default.useEffect(() => {
|
|
3431
3402
|
setDocOverride(null);
|
|
3432
3403
|
scrollRef.current = 0;
|
|
3433
3404
|
}, [name]);
|
|
3434
|
-
|
|
3405
|
+
const descOwnerRef = import_react5.default.useRef(name);
|
|
3406
|
+
import_react5.default.useEffect(() => {
|
|
3407
|
+
if (descOwnerRef.current === name) return;
|
|
3408
|
+
descOwnerRef.current = name;
|
|
3409
|
+
setDescOpen(false);
|
|
3410
|
+
setDescClamped(false);
|
|
3411
|
+
}, [name]);
|
|
3412
|
+
import_react5.default.useLayoutEffect(() => {
|
|
3413
|
+
const node = descRef.current;
|
|
3414
|
+
if (!node) return void 0;
|
|
3415
|
+
const measure = () => {
|
|
3416
|
+
if (descOpen) return;
|
|
3417
|
+
const scroll = Number(node.scrollHeight) || 0;
|
|
3418
|
+
const client = Number(node.clientHeight) || 0;
|
|
3419
|
+
setDescClamped(scroll > client + 1);
|
|
3420
|
+
};
|
|
3421
|
+
measure();
|
|
3422
|
+
if (typeof window === "undefined" || !window.addEventListener) return void 0;
|
|
3423
|
+
window.addEventListener("resize", measure);
|
|
3424
|
+
let observer = null;
|
|
3425
|
+
if (typeof ResizeObserver === "function") {
|
|
3426
|
+
observer = new ResizeObserver(measure);
|
|
3427
|
+
observer.observe(node);
|
|
3428
|
+
}
|
|
3429
|
+
return () => {
|
|
3430
|
+
observer?.disconnect?.();
|
|
3431
|
+
window.removeEventListener("resize", measure);
|
|
3432
|
+
};
|
|
3433
|
+
}, [description, descOpen]);
|
|
3434
|
+
import_react5.default.useEffect(() => {
|
|
3435
3435
|
if (!initialFile) return;
|
|
3436
3436
|
setTab("files");
|
|
3437
3437
|
void loadFile(initialFile);
|
|
3438
3438
|
}, [initialFile, loadFile]);
|
|
3439
|
-
|
|
3439
|
+
import_react5.default.useEffect(() => {
|
|
3440
3440
|
const node = bodyRef.current;
|
|
3441
3441
|
if (node && scrollRef.current) node.scrollTop = scrollRef.current;
|
|
3442
3442
|
});
|
|
3443
|
-
|
|
3443
|
+
import_react5.default.useEffect(() => {
|
|
3444
3444
|
if (variant !== "drawer" || typeof document === "undefined") return void 0;
|
|
3445
3445
|
const previous = document.activeElement;
|
|
3446
3446
|
const node = dialogRef.current;
|
|
@@ -3467,12 +3467,12 @@ function SkillReader({
|
|
|
3467
3467
|
previous?.focus?.();
|
|
3468
3468
|
};
|
|
3469
3469
|
}, [variant, onClose]);
|
|
3470
|
-
|
|
3470
|
+
import_react5.default.useEffect(() => {
|
|
3471
3471
|
if (variant !== "page" || typeof document === "undefined") return;
|
|
3472
3472
|
const node = document.getElementById("dshsc-reader-name");
|
|
3473
3473
|
node?.focus?.();
|
|
3474
3474
|
}, [variant, name]);
|
|
3475
|
-
|
|
3475
|
+
import_react5.default.useEffect(() => {
|
|
3476
3476
|
if (variant !== "page" || typeof window === "undefined") return void 0;
|
|
3477
3477
|
const node = bodyRef.current;
|
|
3478
3478
|
if (!node?.querySelectorAll) return void 0;
|
|
@@ -3533,7 +3533,7 @@ function SkillReader({
|
|
|
3533
3533
|
{ className: "dshsc-reader-title" },
|
|
3534
3534
|
h("h2", { id: "dshsc-reader-name", tabIndex: -1 }, name || t("viewer.title")),
|
|
3535
3535
|
row?.status && STATUS_KEY[row.status] ? h("span", { className: `dshsc-tag ${row.status === "current" ? "ok" : "warn"}` }, t(STATUS_KEY[row.status])) : null,
|
|
3536
|
-
row?.
|
|
3536
|
+
row?.enabled === false ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null,
|
|
3537
3537
|
variant === "drawer" ? h("button", {
|
|
3538
3538
|
type: "button",
|
|
3539
3539
|
className: "dshsc-reader-close",
|
|
@@ -3542,7 +3542,22 @@ function SkillReader({
|
|
|
3542
3542
|
onClick: () => onClose?.()
|
|
3543
3543
|
}, "×") : null
|
|
3544
3544
|
),
|
|
3545
|
-
description ? h(
|
|
3545
|
+
description ? h(
|
|
3546
|
+
"div",
|
|
3547
|
+
{ className: "dshsc-reader-descblock" },
|
|
3548
|
+
h("p", {
|
|
3549
|
+
className: `dshsc-reader-desc${descOpen ? " expanded" : ""}`,
|
|
3550
|
+
id: "dshsc-reader-desc",
|
|
3551
|
+
ref: descRef
|
|
3552
|
+
}, description),
|
|
3553
|
+
descClamped ? h("button", {
|
|
3554
|
+
type: "button",
|
|
3555
|
+
className: "dshsc-reader-more",
|
|
3556
|
+
"aria-expanded": String(descOpen),
|
|
3557
|
+
"aria-controls": "dshsc-reader-desc",
|
|
3558
|
+
onClick: () => setDescOpen((prev) => !prev)
|
|
3559
|
+
}, t(descOpen ? "action.collapse" : "action.expand")) : null
|
|
3560
|
+
) : null,
|
|
3546
3561
|
h(
|
|
3547
3562
|
"div",
|
|
3548
3563
|
{ className: "dshsc-reader-meta" },
|
|
@@ -3757,8 +3772,7 @@ function SkillReader({
|
|
|
3757
3772
|
);
|
|
3758
3773
|
}
|
|
3759
3774
|
function infoPanel() {
|
|
3760
|
-
const
|
|
3761
|
-
const projects = row?.scopeProjects || [];
|
|
3775
|
+
const exportLabels = (row?.exports || []).filter((item) => !item.broken).map((item) => item.label);
|
|
3762
3776
|
const hash = row?.lockHash || row?.currentHash || null;
|
|
3763
3777
|
const rows = [];
|
|
3764
3778
|
rows.push(["viewer.info.name", h("span", { className: "mono" }, name), "mono"]);
|
|
@@ -3777,9 +3791,9 @@ function SkillReader({
|
|
|
3777
3791
|
["viewer.info.localChange", row?.status === "locally-modified" || row?.status === "conflict" ? t("viewer.info.localChangeYes") : t("viewer.info.localChangeNone")]
|
|
3778
3792
|
];
|
|
3779
3793
|
const linkRows = [
|
|
3780
|
-
["viewer.info.
|
|
3781
|
-
["viewer.info.
|
|
3782
|
-
["viewer.info.mode", row?.
|
|
3794
|
+
["viewer.info.visibility", row?.visible ? t("meta.modelVisible") : t("meta.modelHidden")],
|
|
3795
|
+
["viewer.info.exported", exportLabels.length > 0 ? exportLabels.join("、") : t("viewer.info.none")],
|
|
3796
|
+
["viewer.info.mode", row?.enabled === false ? t("status.off") : t("mode.on")],
|
|
3783
3797
|
["viewer.info.status", STATUS_KEY[row?.status] ? t(STATUS_KEY[row.status]) : t("status.unknown")]
|
|
3784
3798
|
];
|
|
3785
3799
|
return h(
|
|
@@ -3826,7 +3840,7 @@ function SkillReader({
|
|
|
3826
3840
|
onClick: () => void copyTextWithToast(row?.path || "", "viewer.copied")
|
|
3827
3841
|
}, t("viewer.copyPath")),
|
|
3828
3842
|
variant === "drawer" && onOpenPage ? h("button", { type: "button", className: "dshsc-btn ghost", onClick: () => onOpenPage() }, t("viewer.openPage")) : null,
|
|
3829
|
-
|
|
3843
|
+
onExports ? h("button", { type: "button", className: "dshsc-btn", onClick: () => onExports?.(row) }, t("action.exports")) : null,
|
|
3830
3844
|
onUpdate ? h("button", {
|
|
3831
3845
|
type: "button",
|
|
3832
3846
|
className: "dshsc-btn tint",
|
|
@@ -3835,6 +3849,7 @@ function SkillReader({
|
|
|
3835
3849
|
}, t("action.update")) : null
|
|
3836
3850
|
);
|
|
3837
3851
|
if (variant === "page") {
|
|
3852
|
+
const tocShown = headings.length > 1;
|
|
3838
3853
|
return h(
|
|
3839
3854
|
"div",
|
|
3840
3855
|
{ className: "dshsc-reader-page" },
|
|
@@ -3860,8 +3875,8 @@ function SkillReader({
|
|
|
3860
3875
|
h("div", { className: "dshsc-reader-hero" }, header),
|
|
3861
3876
|
h(
|
|
3862
3877
|
"div",
|
|
3863
|
-
{ className:
|
|
3864
|
-
|
|
3878
|
+
{ className: `dshsc-reader-layout${tocShown ? " has-toc" : ""}` },
|
|
3879
|
+
tocShown ? h(
|
|
3865
3880
|
"nav",
|
|
3866
3881
|
{ className: "dshsc-reader-toc", "aria-label": t("viewer.toc") },
|
|
3867
3882
|
h("div", { className: "dshsc-reader-toc-title" }, t("viewer.toc")),
|
|
@@ -3895,19 +3910,19 @@ function SkillReader({
|
|
|
3895
3910
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
3896
3911
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
3897
3912
|
const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
|
|
3898
|
-
const [tab, setTab] =
|
|
3899
|
-
const [view, setView] =
|
|
3900
|
-
const [modal, setModal] =
|
|
3901
|
-
const [expanded, setExpanded] =
|
|
3902
|
-
const [placementBusy, setPlacementBusy] =
|
|
3903
|
-
const [placementOverride, setPlacementOverride] =
|
|
3904
|
-
const [query, setQuery] =
|
|
3905
|
-
const [viewer, setViewer] =
|
|
3906
|
-
const rootRef =
|
|
3907
|
-
const scrollPos =
|
|
3908
|
-
const restoreScroll =
|
|
3909
|
-
const [filter, setFilter] =
|
|
3910
|
-
const [builtinOpen, setBuiltinOpen] =
|
|
3913
|
+
const [tab, setTab] = import_react6.default.useState("installed");
|
|
3914
|
+
const [view, setView] = import_react6.default.useState("flat");
|
|
3915
|
+
const [modal, setModal] = import_react6.default.useState(null);
|
|
3916
|
+
const [expanded, setExpanded] = import_react6.default.useState({});
|
|
3917
|
+
const [placementBusy, setPlacementBusy] = import_react6.default.useState(null);
|
|
3918
|
+
const [placementOverride, setPlacementOverride] = import_react6.default.useState(null);
|
|
3919
|
+
const [query, setQuery] = import_react6.default.useState("");
|
|
3920
|
+
const [viewer, setViewer] = import_react6.default.useState(null);
|
|
3921
|
+
const rootRef = import_react6.default.useRef(null);
|
|
3922
|
+
const scrollPos = import_react6.default.useRef(null);
|
|
3923
|
+
const restoreScroll = import_react6.default.useRef(null);
|
|
3924
|
+
const [filter, setFilter] = import_react6.default.useState("all");
|
|
3925
|
+
const [builtinOpen, setBuiltinOpen] = import_react6.default.useState(false);
|
|
3911
3926
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
3912
3927
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
3913
3928
|
const rows = data?.installs || [];
|
|
@@ -3917,46 +3932,32 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3917
3932
|
const updateCount = rows.filter(updatable).length;
|
|
3918
3933
|
const bulkCount = rows.filter((row) => row.status === "update-available").length;
|
|
3919
3934
|
const unknownCount = rows.filter((row) => row.status === "unknown").length;
|
|
3920
|
-
const offCount = rows.filter((row) => row.
|
|
3935
|
+
const offCount = rows.filter((row) => row.enabled === false).length;
|
|
3921
3936
|
const counts = { all: rows.length, update: updateCount, unknown: unknownCount, off: offCount };
|
|
3922
3937
|
const queryText = query.trim().toLowerCase();
|
|
3923
3938
|
const visibleRows = rows.filter((row) => {
|
|
3924
3939
|
if (filter === "update" && !updatable(row)) return false;
|
|
3925
3940
|
if (filter === "unknown" && row.status !== "unknown") return false;
|
|
3926
|
-
if (filter === "off" && row.
|
|
3941
|
+
if (filter === "off" && row.enabled !== false) return false;
|
|
3927
3942
|
if (!queryText) return true;
|
|
3928
|
-
const agentLabels = row.
|
|
3943
|
+
const agentLabels = row.exports.map((item) => item.label);
|
|
3929
3944
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
3930
3945
|
});
|
|
3931
|
-
|
|
3946
|
+
import_react6.default.useEffect(() => {
|
|
3932
3947
|
if (!viewer || !data) return;
|
|
3933
3948
|
if (rows.some((row) => row.name === viewer.name)) return;
|
|
3934
3949
|
setViewer(null);
|
|
3935
3950
|
setToast({ kind: "err", text: t("viewer.gone") });
|
|
3936
3951
|
}, [data, rows, t, viewer]);
|
|
3937
|
-
const
|
|
3938
|
-
const
|
|
3939
|
-
const autoLinkAgents = Array.isArray(
|
|
3940
|
-
const
|
|
3941
|
-
const autoLinkLabel = [t("links.dshGlobal"), ...autoLinkAgents.map((agent) => agent.label)].join("、");
|
|
3952
|
+
const exportTargetOptions = (data?.agents?.detected || []).filter((agent) => agent.kind === "agent" && agent.exportable);
|
|
3953
|
+
const exportSelection = data?.settings?.exportTargets;
|
|
3954
|
+
const autoLinkAgents = Array.isArray(exportSelection) ? exportTargetOptions.filter((agent) => exportSelection.includes(agent.target)) : [];
|
|
3955
|
+
const autoLinkLabel = autoLinkAgents.length > 0 ? autoLinkAgents.map((agent) => agent.label).join("、") : t("exportDefault.none");
|
|
3942
3956
|
const placement = (placementOverride ?? data?.settings?.placement) === "sidebar" ? "sidebar" : "settings";
|
|
3943
|
-
function getScrollParent(node) {
|
|
3944
|
-
let current = node?.parentElement || null;
|
|
3945
|
-
while (current) {
|
|
3946
|
-
if (typeof getComputedStyle === "function") {
|
|
3947
|
-
const style = getComputedStyle(current);
|
|
3948
|
-
if (style && /(auto|scroll)/.test(style.overflowY || "") && current.scrollHeight > current.clientHeight) {
|
|
3949
|
-
return current;
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
current = current.parentElement || null;
|
|
3953
|
-
}
|
|
3954
|
-
return typeof document !== "undefined" ? document.scrollingElement || null : null;
|
|
3955
|
-
}
|
|
3956
3957
|
function listScroller() {
|
|
3957
3958
|
const node = rootRef.current?.querySelector?.(".dshsc-listscroll");
|
|
3958
|
-
if (node
|
|
3959
|
-
return
|
|
3959
|
+
if (isScroller(node)) return node;
|
|
3960
|
+
return nearestScroller(rootRef.current);
|
|
3960
3961
|
}
|
|
3961
3962
|
function openViewer(row, mode = "drawer") {
|
|
3962
3963
|
if (!row) return;
|
|
@@ -3976,7 +3977,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3976
3977
|
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
3977
3978
|
setViewer(null);
|
|
3978
3979
|
}
|
|
3979
|
-
|
|
3980
|
+
import_react6.default.useLayoutEffect(() => {
|
|
3980
3981
|
if (restoreScroll.current === null) return;
|
|
3981
3982
|
const target = restoreScroll.current;
|
|
3982
3983
|
restoreScroll.current = null;
|
|
@@ -3999,7 +4000,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
3999
4000
|
}
|
|
4000
4001
|
async function saveAutoLink(targets) {
|
|
4001
4002
|
setModal(null);
|
|
4002
|
-
await run("settings", () => apiCall("POST", "/settings", {
|
|
4003
|
+
await run("settings", () => apiCall("POST", "/settings", { exportTargets: targets }));
|
|
4003
4004
|
}
|
|
4004
4005
|
const installedActions = {
|
|
4005
4006
|
onView: (row) => openViewer(row),
|
|
@@ -4007,7 +4008,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4007
4008
|
onToggleMode: actions.toggleMode,
|
|
4008
4009
|
onUpdate: actions.requestUpdate,
|
|
4009
4010
|
onDelete: actions.requestDelete,
|
|
4010
|
-
|
|
4011
|
+
onExports: (row) => setModal({ type: "exports", row }),
|
|
4011
4012
|
onCheckUpdates: actions.checkUpdates,
|
|
4012
4013
|
onUpdateAll: actions.updateAll,
|
|
4013
4014
|
onAddSkill: actions.requestAddSkill,
|
|
@@ -4039,11 +4040,11 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4039
4040
|
onClose: closeViewer,
|
|
4040
4041
|
onOpenPage: viewer.mode === "drawer" ? openPage : null,
|
|
4041
4042
|
onUpdate: actions.requestUpdate,
|
|
4042
|
-
|
|
4043
|
+
onExports: (row) => setModal({ type: "exports", row }),
|
|
4043
4044
|
onToast: (next) => setToast(next)
|
|
4044
4045
|
}) : null;
|
|
4045
4046
|
const listNode = h(
|
|
4046
|
-
|
|
4047
|
+
import_react6.default.Fragment,
|
|
4047
4048
|
null,
|
|
4048
4049
|
h(Header, {
|
|
4049
4050
|
t,
|
|
@@ -4051,7 +4052,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4051
4052
|
plugin: data?.plugin,
|
|
4052
4053
|
writable,
|
|
4053
4054
|
autoLinkLabel,
|
|
4054
|
-
autoLinkCount: autoLinkAgents.length
|
|
4055
|
+
autoLinkCount: autoLinkAgents.length,
|
|
4055
4056
|
tab,
|
|
4056
4057
|
settingsOpen,
|
|
4057
4058
|
settingsRef,
|
|
@@ -4116,14 +4117,13 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4116
4117
|
t,
|
|
4117
4118
|
modal,
|
|
4118
4119
|
data,
|
|
4119
|
-
autoLinkAgents:
|
|
4120
|
-
autoLinkDsh,
|
|
4120
|
+
autoLinkAgents: exportTargetOptions,
|
|
4121
4121
|
pickDirectory,
|
|
4122
4122
|
actions: {
|
|
4123
4123
|
probeSkills: actions.probeSkills,
|
|
4124
4124
|
installSkills: actions.installSkills,
|
|
4125
4125
|
addSubscription: actions.addSubscription,
|
|
4126
|
-
|
|
4126
|
+
saveExports: actions.saveExports
|
|
4127
4127
|
},
|
|
4128
4128
|
onClose: () => setModal(null),
|
|
4129
4129
|
onSaveAutoLink: saveAutoLink
|