dsh-skill-hub 0.2.0 → 0.2.2
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/CONTRIBUTING.md +1 -1
- package/README.md +181 -249
- package/README.zh.md +275 -0
- package/lib/client.js +395 -257
- package/lib/client.js.map +1 -1
- package/lib/index.js +126 -26
- package/lib/types/client/SkillHubSettingsCard.d.ts +23 -17
- package/lib/types/client/api.d.ts +9 -3
- package/lib/types/client/grouping.d.ts +3 -0
- package/lib/types/client/index.d.ts +12 -6
- package/lib/types/client/locales.d.ts +8 -4
- package/lib/types/client/market-catalog.d.ts +1 -1
- package/lib/types/client/panel/MarketView.d.ts +5 -5
- package/lib/types/client/panel/SkillHubPanel.d.ts +1 -1
- package/lib/types/client/panel/SourcesView.d.ts +5 -3
- package/lib/types/client/panel/useSkillHub.d.ts +4 -0
- package/lib/types/client/settings-card.d.ts +5 -4
- package/lib/types/index.d.ts +12 -1
- package/lib/types/protocol.d.ts +23 -0
- package/lib/types/skillfs.d.ts +1 -1
- package/lib/types/update.d.ts +1 -1
- package/package.json +28 -27
- package/src/client/SkillHubSettingsCard.tsx +25 -14
- package/src/client/api.ts +9 -6
- package/src/client/grouping.test.ts +12 -0
- package/src/client/grouping.ts +10 -1
- package/src/client/index.tsx +25 -19
- package/src/client/locales.ts +16 -8
- package/src/client/market-catalog.ts +1 -1
- package/src/client/panel/MarketView.tsx +77 -64
- package/src/client/panel/SkillHubPanel.tsx +18 -1
- package/src/client/panel/SkillRow.tsx +17 -2
- package/src/client/panel/SourcesView.tsx +92 -5
- package/src/client/panel/panel.module.css +9 -1
- package/src/client/panel/useSkillHub.ts +25 -11
- package/src/client/settings-card.tsx +5 -4
- package/src/index.ts +78 -31
- package/src/protocol.ts +24 -0
- package/src/routes.test.ts +55 -0
- package/src/routes.ts +90 -10
- package/src/skillfs.ts +1 -1
- package/src/update.ts +1 -1
- package/lib/types/client/api-config-scope.d.ts +0 -48
- package/src/client/api-config-scope.test.ts +0 -79
- package/src/client/api-config-scope.ts +0 -119
package/lib/client.js
CHANGED
|
@@ -88,11 +88,14 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
}
|
|
89
89
|
/** The browser half's only data entry point. */
|
|
90
90
|
var SkillHubApi = class {
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
/** Catalog lookup options (cwd selects a workspace's project skills). */
|
|
92
|
+
catalog(options) {
|
|
93
|
+
return fetchWithTimeout(options?.cwd !== void 0 && options.cwd !== "" ? SKILL_HUB_API.catalog + "?cwd=" + encodeURIComponent(options.cwd) : SKILL_HUB_API.catalog).then((response) => readJson(response));
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
/** One skill's detail (cwd selects a workspace's project skills). */
|
|
96
|
+
async skill(name, options) {
|
|
97
|
+
const cwd = options?.cwd !== void 0 && options.cwd !== "" ? "&cwd=" + encodeURIComponent(options.cwd) : "";
|
|
98
|
+
return (await readJson(await fetchWithTimeout(SKILL_HUB_API.skill + "?name=" + encodeURIComponent(name) + cwd))).skill;
|
|
96
99
|
}
|
|
97
100
|
/** Move one writable skill into the restorable trash. */
|
|
98
101
|
async deleteSkill(name) {
|
|
@@ -135,7 +138,7 @@ window.__ModuleLoader__.load({
|
|
|
135
138
|
body: JSON.stringify(payload)
|
|
136
139
|
}));
|
|
137
140
|
}
|
|
138
|
-
/** The user's added market sources
|
|
141
|
+
/** The user's added market sources. */
|
|
139
142
|
async market() {
|
|
140
143
|
return readJson(await fetchWithTimeout(SKILL_HUB_API.market));
|
|
141
144
|
}
|
|
@@ -294,102 +297,6 @@ window.__ModuleLoader__.load({
|
|
|
294
297
|
}
|
|
295
298
|
};
|
|
296
299
|
//#endregion
|
|
297
|
-
//#region src/client/api-config-scope.ts
|
|
298
|
-
/** Model-invocable dot color default. Single source for the TS side; the
|
|
299
|
-
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
300
|
-
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
301
|
-
/** User-invocable dot color default. Single source for the TS side; the
|
|
302
|
-
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
303
|
-
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
304
|
-
/** Built-in defaults every field inherits until the user overrides it. */
|
|
305
|
-
const DEFAULTS = {
|
|
306
|
-
enabled: true,
|
|
307
|
-
announceToAgent: true,
|
|
308
|
-
showUseCount: true,
|
|
309
|
-
showUseTime: true,
|
|
310
|
-
showGroupSummary: true
|
|
311
|
-
};
|
|
312
|
-
/** Boolean config fields (color fields are strings). */
|
|
313
|
-
const BOOLEAN_FIELDS = /* @__PURE__ */ new Set([
|
|
314
|
-
"enabled",
|
|
315
|
-
"announceToAgent",
|
|
316
|
-
"showUseCount",
|
|
317
|
-
"showUseTime",
|
|
318
|
-
"showGroupSummary"
|
|
319
|
-
]);
|
|
320
|
-
/** Config fields the card edits (used to guard set/unset field names). */
|
|
321
|
-
const FIELDS = /* @__PURE__ */ new Set([
|
|
322
|
-
"enabled",
|
|
323
|
-
"announceToAgent",
|
|
324
|
-
"dotModelColor",
|
|
325
|
-
"dotUserColor",
|
|
326
|
-
"showUseCount",
|
|
327
|
-
"showUseTime",
|
|
328
|
-
"showGroupSummary"
|
|
329
|
-
]);
|
|
330
|
-
/** FormScope-compatible reactive config handle (see module doc). */
|
|
331
|
-
var ApiConfigScope = class {
|
|
332
|
-
api;
|
|
333
|
-
status = "loading";
|
|
334
|
-
value = { ...DEFAULTS };
|
|
335
|
-
saved = {};
|
|
336
|
-
listeners = /* @__PURE__ */ new Set();
|
|
337
|
-
/** @param api - the browser-half API client (shared with the skill panel). */
|
|
338
|
-
constructor(api) {
|
|
339
|
-
this.api = api;
|
|
340
|
-
this.load();
|
|
341
|
-
}
|
|
342
|
-
async load() {
|
|
343
|
-
try {
|
|
344
|
-
const response = await this.api.config();
|
|
345
|
-
this.value = response.config;
|
|
346
|
-
this.saved = { ...response.saved };
|
|
347
|
-
this.status = "ready";
|
|
348
|
-
} catch {
|
|
349
|
-
this.status = "unavailable";
|
|
350
|
-
}
|
|
351
|
-
this.publish();
|
|
352
|
-
}
|
|
353
|
-
subscribe(listener) {
|
|
354
|
-
this.listeners.add(listener);
|
|
355
|
-
return () => {
|
|
356
|
-
this.listeners.delete(listener);
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
getSnapshot() {
|
|
360
|
-
return {
|
|
361
|
-
status: this.status,
|
|
362
|
-
writable: true,
|
|
363
|
-
value: { ...this.value },
|
|
364
|
-
base: { ...DEFAULTS },
|
|
365
|
-
user: { ...this.saved }
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
/** Write one field through the config route and adopt the persisted state. */
|
|
369
|
-
async set(field, value) {
|
|
370
|
-
if (!FIELDS.has(field)) return;
|
|
371
|
-
const patch = BOOLEAN_FIELDS.has(field) ? { [field]: Boolean(value) } : { [field]: typeof value === "string" ? value : void 0 };
|
|
372
|
-
await this.apply(patch);
|
|
373
|
-
}
|
|
374
|
-
/** Clear one field's override so it re-inherits the default. */
|
|
375
|
-
async unset(field) {
|
|
376
|
-
if (!FIELDS.has(field)) return;
|
|
377
|
-
await this.apply({ [field]: void 0 });
|
|
378
|
-
}
|
|
379
|
-
/** Persist a patch, then re-seed from the route's fresh response. */
|
|
380
|
-
async apply(patch) {
|
|
381
|
-
const request = {};
|
|
382
|
-
for (const [key, value] of Object.entries(patch)) request[key] = value === void 0 ? null : value;
|
|
383
|
-
const response = await this.api.saveConfig(request);
|
|
384
|
-
this.value = response.config;
|
|
385
|
-
this.saved = { ...response.saved };
|
|
386
|
-
this.publish();
|
|
387
|
-
}
|
|
388
|
-
publish() {
|
|
389
|
-
for (const listener of this.listeners) listener();
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
//#endregion
|
|
393
300
|
//#region src/client/locales.ts
|
|
394
301
|
/**
|
|
395
302
|
* dsh-skill-hub surface copy (zh/en). The dictionary type is re-exported
|
|
@@ -419,19 +326,19 @@ window.__ModuleLoader__.load({
|
|
|
419
326
|
"panel.emptyAll": "本地还没有技能。点击「新建技能」创建第一个。",
|
|
420
327
|
"panel.loading": "读取中…",
|
|
421
328
|
"panel.incomplete": "(目录可能不完整)",
|
|
422
|
-
"
|
|
329
|
+
"panel.workspacePlaceholder": "工作区路径(回车应用)",
|
|
330
|
+
"panel.workspaceHint": "填写项目路径后,面板同时显示该项目 .dsh/skills 与 .agents/skills 下的技能(只读);留空回车恢复用户级视图。",
|
|
331
|
+
"panel.workspaceClear": "清除工作区",
|
|
423
332
|
"market.addPlaceholder": "owner/repo 或 https://github.com/…",
|
|
424
333
|
"market.addSource": "添加源",
|
|
425
334
|
"market.noSources": "还没有市场源。从下方内置市场添加,或输入一个 GitHub 仓库。",
|
|
426
|
-
"market.
|
|
335
|
+
"market.title": "市场",
|
|
427
336
|
"market.catalogHint": "精选技能仓库,添加后即可扫描安装,并持续检查更新。",
|
|
428
337
|
"market.catalog.anthropics": "Anthropic 官方技能合集(Claude Skills)",
|
|
429
338
|
"market.catalog.superpowers": "Superpowers 社区技能合集",
|
|
430
339
|
"market.catalog.mattpocock": "Matt Pocock 技能集",
|
|
431
340
|
"market.catalog.openDesign": "设计技能合集(design-templates)",
|
|
432
|
-
"market.added": "已添加",
|
|
433
341
|
"market.add": "添加",
|
|
434
|
-
"market.mySources": "我的市场源",
|
|
435
342
|
"market.installed": "已装 {count}",
|
|
436
343
|
"market.updatable": "可更新 {count}",
|
|
437
344
|
"market.deletedUpstream": "上游已删 {count}",
|
|
@@ -496,6 +403,9 @@ window.__ModuleLoader__.load({
|
|
|
496
403
|
"groups.empty": "还没有场景。先新建一个,再把技能归类。",
|
|
497
404
|
"groups.noCollections": "暂无来源组。从市场导入技能后自动按来源聚合。",
|
|
498
405
|
"groups.personal": "个人",
|
|
406
|
+
"groups.project": "项目级",
|
|
407
|
+
"groups.subdivide": "细分",
|
|
408
|
+
"groups.merge": "合并",
|
|
499
409
|
"groups.noWritable": "该组没有可写技能(只读技能无法由中枢开关)",
|
|
500
410
|
"groups.closeAll": "全部关闭",
|
|
501
411
|
"groups.keepOn": "保留开启",
|
|
@@ -522,6 +432,7 @@ window.__ModuleLoader__.load({
|
|
|
522
432
|
"row.disable": "禁用",
|
|
523
433
|
"row.enable": "启用",
|
|
524
434
|
"row.delete": "移入回收站",
|
|
435
|
+
"row.open": "查看 {name} 详情(回车)",
|
|
525
436
|
"delete.confirmTitle": "移入回收站?",
|
|
526
437
|
"delete.confirmText": "确定把「{name}」移入回收站吗?可随时恢复。",
|
|
527
438
|
"delete.confirm": "移入回收站",
|
|
@@ -623,19 +534,19 @@ window.__ModuleLoader__.load({
|
|
|
623
534
|
"panel.emptyAll": "No skills installed yet. Create the first one.",
|
|
624
535
|
"panel.loading": "Loading…",
|
|
625
536
|
"panel.incomplete": "(catalog may be incomplete)",
|
|
626
|
-
"
|
|
537
|
+
"panel.workspacePlaceholder": "Workspace path (Enter to apply)",
|
|
538
|
+
"panel.workspaceHint": "Enter a project path to also show that workspace's .dsh/skills and .agents/skills skills (read-only); clear + Enter to return to the user-level view.",
|
|
539
|
+
"panel.workspaceClear": "Clear workspace",
|
|
627
540
|
"market.addPlaceholder": "owner/repo or https://github.com/…",
|
|
628
541
|
"market.addSource": "Add source",
|
|
629
542
|
"market.noSources": "No market sources yet. Add one from the built-in catalog below, or enter a GitHub repo.",
|
|
630
|
-
"market.
|
|
543
|
+
"market.title": "Market",
|
|
631
544
|
"market.catalogHint": "Curated skill repos — add one to scan and install, with ongoing update checks.",
|
|
632
545
|
"market.catalog.anthropics": "Official Anthropic skill collection (Claude Skills)",
|
|
633
546
|
"market.catalog.superpowers": "Superpowers community skill collection",
|
|
634
547
|
"market.catalog.mattpocock": "Matt Pocock skill collection",
|
|
635
548
|
"market.catalog.openDesign": "Design skill collection (design-templates)",
|
|
636
|
-
"market.added": "Added",
|
|
637
549
|
"market.add": "Add",
|
|
638
|
-
"market.mySources": "My sources",
|
|
639
550
|
"market.installed": "{count} installed",
|
|
640
551
|
"market.updatable": "{count} updatable",
|
|
641
552
|
"market.deletedUpstream": "{count} deleted upstream",
|
|
@@ -700,6 +611,9 @@ window.__ModuleLoader__.load({
|
|
|
700
611
|
"groups.empty": "No scenes yet. Create one and start categorizing skills.",
|
|
701
612
|
"groups.noCollections": "No source groups yet. Import skills from the market to group them by source.",
|
|
702
613
|
"groups.personal": "Personal",
|
|
614
|
+
"groups.project": "Project",
|
|
615
|
+
"groups.subdivide": "Split",
|
|
616
|
+
"groups.merge": "Merge",
|
|
703
617
|
"groups.noWritable": "No writable skills in this group (read-only skills cannot be switched from the hub)",
|
|
704
618
|
"groups.closeAll": "Close all",
|
|
705
619
|
"groups.keepOn": "Keep on",
|
|
@@ -726,6 +640,7 @@ window.__ModuleLoader__.load({
|
|
|
726
640
|
"row.disable": "Disable",
|
|
727
641
|
"row.enable": "Enable",
|
|
728
642
|
"row.delete": "Move to trash",
|
|
643
|
+
"row.open": "View {name} details (Enter)",
|
|
729
644
|
"delete.confirmTitle": "Move to trash?",
|
|
730
645
|
"delete.confirmText": "Move \"{name}\" to trash? It can be restored anytime.",
|
|
731
646
|
"delete.confirm": "Move to trash",
|
|
@@ -1301,11 +1216,17 @@ window.__ModuleLoader__.load({
|
|
|
1301
1216
|
};
|
|
1302
1217
|
//#endregion
|
|
1303
1218
|
//#region src/client/SkillHubSettingsCard.tsx
|
|
1219
|
+
/** Model-invocable dot color default. Single source for the TS side; the
|
|
1220
|
+
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
1221
|
+
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
1222
|
+
/** User-invocable dot color default. Single source for the TS side; the
|
|
1223
|
+
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
1224
|
+
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
1304
1225
|
/** Bridges the hub's config scope onto the card's staged form. */
|
|
1305
1226
|
var SkillHubSettingsCardController = class {
|
|
1306
1227
|
form;
|
|
1307
1228
|
store;
|
|
1308
|
-
/** @param scope - the hub
|
|
1229
|
+
/** @param scope - the hub settings scope the card edits (FormScope-compatible). */
|
|
1309
1230
|
constructor(scope) {
|
|
1310
1231
|
this.form = new CardForm(scope, [
|
|
1311
1232
|
booleanField("enabled"),
|
|
@@ -1512,15 +1433,23 @@ window.__ModuleLoader__.load({
|
|
|
1512
1433
|
}
|
|
1513
1434
|
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
1514
1435
|
const PRIVATE_SOURCE = "private";
|
|
1436
|
+
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
1437
|
+
function isProjectSource(source) {
|
|
1438
|
+
return source === "project-dsh" || source === "project-agents";
|
|
1439
|
+
}
|
|
1515
1440
|
/**
|
|
1516
1441
|
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
1517
1442
|
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
1518
1443
|
* skillName → repo derivation, so filtering follows the tracked source
|
|
1519
1444
|
* records instead of the filesystem root a skill happens to live under.
|
|
1445
|
+
* 项目级技能(有 workspace 归属)永远不算「个人」。
|
|
1520
1446
|
*/
|
|
1521
1447
|
function filterBySource(skills, source, origins) {
|
|
1522
1448
|
if (source === "all") return [...skills];
|
|
1523
|
-
return skills.filter((skill) =>
|
|
1449
|
+
return skills.filter((skill) => {
|
|
1450
|
+
if (isProjectSource(skill.source)) return false;
|
|
1451
|
+
return (origins[skill.name] ?? "private") === source;
|
|
1452
|
+
});
|
|
1524
1453
|
}
|
|
1525
1454
|
/**
|
|
1526
1455
|
* Sort a skill list in place-safe copy order: name ascending, added
|
|
@@ -1583,7 +1512,7 @@ window.__ModuleLoader__.load({
|
|
|
1583
1512
|
}
|
|
1584
1513
|
//#endregion
|
|
1585
1514
|
//#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
|
|
1586
|
-
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{align-items:center;gap:8px;padding:12px 14px 4px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;min-width:0;font-size:13px;font-weight:600}._6VtqdG_groupOps{flex:none;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}";
|
|
1515
|
+
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{align-items:center;gap:8px;padding:12px 14px 4px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;min-width:0;font-size:13px;font-weight:600}._6VtqdG_groupOps{flex:none;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}";
|
|
1587
1516
|
const tagId = "dsh-skill-hub/panel.module.css";
|
|
1588
1517
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1589
1518
|
const tag = document.createElement("style");
|
|
@@ -1664,6 +1593,7 @@ window.__ModuleLoader__.load({
|
|
|
1664
1593
|
"opDanger": "_6VtqdG_opDanger",
|
|
1665
1594
|
"panel": "_6VtqdG_panel",
|
|
1666
1595
|
"primary": "_6VtqdG_primary",
|
|
1596
|
+
"projectNest": "_6VtqdG_projectNest",
|
|
1667
1597
|
"row": "_6VtqdG_row",
|
|
1668
1598
|
"rowDesc": "_6VtqdG_rowDesc",
|
|
1669
1599
|
"rowMain": "_6VtqdG_rowMain",
|
|
@@ -1701,7 +1631,9 @@ window.__ModuleLoader__.load({
|
|
|
1701
1631
|
"titleIcon": "_6VtqdG_titleIcon",
|
|
1702
1632
|
"updateLink": "_6VtqdG_updateLink",
|
|
1703
1633
|
"useCount": "_6VtqdG_useCount",
|
|
1704
|
-
"useTime": "_6VtqdG_useTime"
|
|
1634
|
+
"useTime": "_6VtqdG_useTime",
|
|
1635
|
+
"workspaceBox": "_6VtqdG_workspaceBox",
|
|
1636
|
+
"workspaceInput": "_6VtqdG_workspaceInput"
|
|
1705
1637
|
};
|
|
1706
1638
|
//#endregion
|
|
1707
1639
|
//#region src/client/panel/SourceStatusBadge.tsx
|
|
@@ -2055,11 +1987,23 @@ window.__ModuleLoader__.load({
|
|
|
2055
1987
|
const stat = hub.uses.get(skill.name);
|
|
2056
1988
|
const count = stat?.count ?? 0;
|
|
2057
1989
|
const lastUsed = stat?.lastUsed;
|
|
1990
|
+
/** 键盘打开详情:Enter 或空格。仅当焦点在行本身(而非行内按钮)时生效。 */
|
|
1991
|
+
const onKeyDown = (event) => {
|
|
1992
|
+
if (event.target !== event.currentTarget) return;
|
|
1993
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1994
|
+
event.preventDefault();
|
|
1995
|
+
hub.openDetail(skill.name);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
2058
1998
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2059
1999
|
className: panel_module_css_default.row,
|
|
2000
|
+
role: "button",
|
|
2001
|
+
tabIndex: 0,
|
|
2002
|
+
"aria-label": tt("row.open", { name: skill.name }),
|
|
2060
2003
|
onClick: () => {
|
|
2061
2004
|
hub.openDetail(skill.name);
|
|
2062
2005
|
},
|
|
2006
|
+
onKeyDown,
|
|
2063
2007
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2064
2008
|
className: panel_module_css_default.rowMain,
|
|
2065
2009
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2187,6 +2131,123 @@ window.__ModuleLoader__.load({
|
|
|
2187
2131
|
}
|
|
2188
2132
|
//#endregion
|
|
2189
2133
|
//#region src/client/panel/SourcesView.tsx
|
|
2134
|
+
/** 项目级三级树:项目级 → 具体工作区(可折叠、可细分)→ .dsh/.agents。 */
|
|
2135
|
+
function ProjectTree(props) {
|
|
2136
|
+
const { hub } = props;
|
|
2137
|
+
const { sorted, sourceFilter, origins, collapsedGroups, subdividedProjects, toggleGroupCollapse, toggleSubdivide } = hub;
|
|
2138
|
+
const projectSkills = filterBySource(sorted, sourceFilter, origins).filter((skill) => isProjectSource(skill.source));
|
|
2139
|
+
if (projectSkills.length === 0) return null;
|
|
2140
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
2141
|
+
for (const skill of projectSkills) {
|
|
2142
|
+
const key = skill.workspace ?? skill.source;
|
|
2143
|
+
const entry = byProject.get(key);
|
|
2144
|
+
if (entry === void 0) byProject.set(key, {
|
|
2145
|
+
title: skill.workspaceTitle ?? skill.workspace ?? tt("groups.project"),
|
|
2146
|
+
skills: [skill]
|
|
2147
|
+
});
|
|
2148
|
+
else entry.skills.push(skill);
|
|
2149
|
+
}
|
|
2150
|
+
const topCollapsed = collapsedGroups.has("project");
|
|
2151
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2152
|
+
className: panel_module_css_default.section,
|
|
2153
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2154
|
+
className: panel_module_css_default.groupHead,
|
|
2155
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2156
|
+
type: "button",
|
|
2157
|
+
className: panel_module_css_default.disclosure,
|
|
2158
|
+
"aria-expanded": !topCollapsed,
|
|
2159
|
+
onClick: () => {
|
|
2160
|
+
toggleGroupCollapse("project");
|
|
2161
|
+
},
|
|
2162
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (topCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2163
|
+
className: panel_module_css_default.groupTitle,
|
|
2164
|
+
children: [
|
|
2165
|
+
tt("groups.project"),
|
|
2166
|
+
" · ",
|
|
2167
|
+
byProject.size
|
|
2168
|
+
]
|
|
2169
|
+
})]
|
|
2170
|
+
})
|
|
2171
|
+
}), !topCollapsed ? [...byProject.entries()].map(([key, proj]) => {
|
|
2172
|
+
const projKey = "project:" + key;
|
|
2173
|
+
const projCollapsed = collapsedGroups.has(projKey);
|
|
2174
|
+
const subdivided = subdividedProjects.has(key);
|
|
2175
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2176
|
+
className: panel_module_css_default.projectNest,
|
|
2177
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2178
|
+
className: panel_module_css_default.groupHead,
|
|
2179
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2180
|
+
type: "button",
|
|
2181
|
+
className: panel_module_css_default.disclosure,
|
|
2182
|
+
"aria-expanded": !projCollapsed,
|
|
2183
|
+
onClick: () => {
|
|
2184
|
+
toggleGroupCollapse(projKey);
|
|
2185
|
+
},
|
|
2186
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (projCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2187
|
+
className: panel_module_css_default.groupTitle,
|
|
2188
|
+
children: [
|
|
2189
|
+
proj.title,
|
|
2190
|
+
" · ",
|
|
2191
|
+
proj.skills.length,
|
|
2192
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
|
|
2193
|
+
members: proj.skills.map((skill) => skill.name),
|
|
2194
|
+
hub
|
|
2195
|
+
})
|
|
2196
|
+
]
|
|
2197
|
+
})]
|
|
2198
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2199
|
+
className: panel_module_css_default.groupOps,
|
|
2200
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2201
|
+
type: "button",
|
|
2202
|
+
className: panel_module_css_default.opBtn,
|
|
2203
|
+
onClick: (event) => {
|
|
2204
|
+
event.stopPropagation();
|
|
2205
|
+
toggleSubdivide(key);
|
|
2206
|
+
},
|
|
2207
|
+
children: subdivided ? tt("groups.merge") : tt("groups.subdivide")
|
|
2208
|
+
})
|
|
2209
|
+
})]
|
|
2210
|
+
}), !projCollapsed ? subdivided ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2211
|
+
className: panel_module_css_default.projectNest,
|
|
2212
|
+
children: ["project-dsh", "project-agents"].map((source) => {
|
|
2213
|
+
const list = proj.skills.filter((skill) => skill.source === source);
|
|
2214
|
+
if (list.length === 0) return null;
|
|
2215
|
+
const srcKey = projKey + ":" + source;
|
|
2216
|
+
const srcCollapsed = collapsedGroups.has(srcKey);
|
|
2217
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2218
|
+
className: panel_module_css_default.projectNest,
|
|
2219
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2220
|
+
className: panel_module_css_default.groupHead,
|
|
2221
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2222
|
+
type: "button",
|
|
2223
|
+
className: panel_module_css_default.disclosure,
|
|
2224
|
+
"aria-expanded": !srcCollapsed,
|
|
2225
|
+
onClick: () => {
|
|
2226
|
+
toggleGroupCollapse(srcKey);
|
|
2227
|
+
},
|
|
2228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (srcCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2229
|
+
className: panel_module_css_default.groupTitle,
|
|
2230
|
+
children: [
|
|
2231
|
+
tt("badge.source." + source),
|
|
2232
|
+
" · ",
|
|
2233
|
+
list.length
|
|
2234
|
+
]
|
|
2235
|
+
})]
|
|
2236
|
+
})
|
|
2237
|
+
}), !srcCollapsed ? list.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
2238
|
+
skill,
|
|
2239
|
+
hub
|
|
2240
|
+
}, skill.name)) : null]
|
|
2241
|
+
}, srcKey);
|
|
2242
|
+
})
|
|
2243
|
+
}) : proj.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
2244
|
+
skill,
|
|
2245
|
+
hub
|
|
2246
|
+
}, skill.name)) : null]
|
|
2247
|
+
}, projKey);
|
|
2248
|
+
}) : null]
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2190
2251
|
function SourcesView(props) {
|
|
2191
2252
|
const { hub } = props;
|
|
2192
2253
|
const { catalog, groupsState, skillView, sourceFilter, origins, sorted, normalized, collapsedGroups, viewNames, sourceCheck, actionNames, checkingSource, syncingSource, batchBusy, busyNames, toggleGroupCollapse, checkSources, requestSync, requestDelete, toggleGroup, enableDisabled } = hub;
|
|
@@ -2195,6 +2256,7 @@ window.__ModuleLoader__.load({
|
|
|
2195
2256
|
hub
|
|
2196
2257
|
}, skill.name)) });
|
|
2197
2258
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2259
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProjectTree, { hub }),
|
|
2198
2260
|
groupsState !== null && groupsState.collections.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2199
2261
|
className: panel_module_css_default.empty,
|
|
2200
2262
|
children: tt("groups.noCollections")
|
|
@@ -2295,7 +2357,7 @@ window.__ModuleLoader__.load({
|
|
|
2295
2357
|
}, "col:" + collection.name);
|
|
2296
2358
|
}),
|
|
2297
2359
|
(() => {
|
|
2298
|
-
const uncategorized = filterBySource(sorted, sourceFilter, origins).filter((skill) => origins[skill.name] === void 0);
|
|
2360
|
+
const uncategorized = filterBySource(sorted, sourceFilter, origins).filter((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
|
|
2299
2361
|
if (uncategorized.length === 0) return null;
|
|
2300
2362
|
const collapsed = collapsedGroups.has("uncategorized-source");
|
|
2301
2363
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -2658,6 +2720,12 @@ window.__ModuleLoader__.load({
|
|
|
2658
2720
|
}
|
|
2659
2721
|
//#endregion
|
|
2660
2722
|
//#region src/client/panel/MarketView.tsx
|
|
2723
|
+
/**
|
|
2724
|
+
* Market tab: one unified market list — built-in catalog entries (add
|
|
2725
|
+
* button while not yet added) and the user's market sources (full state
|
|
2726
|
+
* badges + actions once added, plus custom sources), with check-all and
|
|
2727
|
+
* update-all actions and the repo scan result with the importable checklist.
|
|
2728
|
+
*/
|
|
2661
2729
|
/** Compact byte size for repo preview rows. */
|
|
2662
2730
|
function formatBytes(bytes) {
|
|
2663
2731
|
if (bytes < 1024) return bytes + " B";
|
|
@@ -2669,54 +2737,140 @@ window.__ModuleLoader__.load({
|
|
|
2669
2737
|
const { marketState, marketCheck, sourceCheck, sourcesState, repoDiscoverState, scanningRepo, repoSelected, setRepoSelected, repoImporting, repoResult, newSourceName, setNewSourceName, syncingMarket, tagBusy, addSource, addMarketSource, removeMarketSource, scanRepo, checkMarket, checkSources, syncMarketSource, toggleRepoSelected, importRepo, updateAllDialog, setUpdateAllDialog, updateAll } = hub;
|
|
2670
2738
|
/** 有可更新技能的来源(全部更新按钮与确认列表共用)。 */
|
|
2671
2739
|
const updatableRepos = Object.entries(sourceCheck).filter(([, check]) => check.changed && check.updated.length > 0);
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2740
|
+
/** 一个已添加来源的完整行:状态徽章 + 操作按钮。 */
|
|
2741
|
+
const sourceRow = (record) => {
|
|
2742
|
+
const releaseCheck = marketCheck[record.repo];
|
|
2743
|
+
const skillCheck = sourceCheck[record.repo];
|
|
2744
|
+
const installedCount = sourcesState?.sources.find((source) => source.repo === record.repo)?.skills.length ?? 0;
|
|
2745
|
+
const scanning = scanningRepo === record.repo;
|
|
2746
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2747
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
2675
2748
|
children: [
|
|
2676
2749
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2677
|
-
className: panel_module_css_default.
|
|
2678
|
-
children:
|
|
2750
|
+
className: panel_module_css_default.rowMain,
|
|
2751
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2752
|
+
className: panel_module_css_default.rowName,
|
|
2753
|
+
children: [
|
|
2754
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2755
|
+
className: panel_module_css_default.sourceLink,
|
|
2756
|
+
href: "https://github.com/" + record.repo,
|
|
2757
|
+
target: "_blank",
|
|
2758
|
+
rel: "noreferrer",
|
|
2759
|
+
children: record.repo
|
|
2760
|
+
}),
|
|
2761
|
+
record.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2762
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
2763
|
+
children: record.ref
|
|
2764
|
+
}) : null,
|
|
2765
|
+
installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2766
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
2767
|
+
children: tt("market.installed", { count: installedCount })
|
|
2768
|
+
}) : null,
|
|
2769
|
+
skillCheck?.changed === true && skillCheck.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2770
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
2771
|
+
children: tt("market.updatable", { count: skillCheck.updated.length })
|
|
2772
|
+
}) : null,
|
|
2773
|
+
skillCheck !== void 0 && skillCheck.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2774
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
|
|
2775
|
+
children: tt("market.deletedUpstream", { count: skillCheck.deleted.length })
|
|
2776
|
+
}) : null,
|
|
2777
|
+
releaseCheck?.updateAvailable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2778
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
2779
|
+
children: releaseCheck.latestTag !== void 0 ? tt("market.newRelease", { version: releaseCheck.latestTag }) : tt("market.updated")
|
|
2780
|
+
}) : null
|
|
2781
|
+
]
|
|
2782
|
+
})
|
|
2679
2783
|
}),
|
|
2680
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2681
|
-
|
|
2682
|
-
|
|
2784
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2785
|
+
type: "button",
|
|
2786
|
+
className: panel_module_css_default.opBtn,
|
|
2787
|
+
disabled: scanning,
|
|
2788
|
+
onClick: () => {
|
|
2789
|
+
checkMarket();
|
|
2790
|
+
},
|
|
2791
|
+
children: tt("market.check")
|
|
2683
2792
|
}),
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
onClick: () => {
|
|
2712
|
-
addSource(entry.repo);
|
|
2713
|
-
},
|
|
2714
|
-
children: tt("market.add")
|
|
2715
|
-
})]
|
|
2716
|
-
}, entry.repo);
|
|
2793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2794
|
+
type: "button",
|
|
2795
|
+
className: panel_module_css_default.opBtn + (releaseCheck?.updateAvailable === true ? " " + panel_module_css_default.opDanger : ""),
|
|
2796
|
+
disabled: syncingMarket === record.repo,
|
|
2797
|
+
onClick: () => {
|
|
2798
|
+
syncMarketSource(record.repo);
|
|
2799
|
+
},
|
|
2800
|
+
children: syncingMarket === record.repo ? tt("market.syncing") : tt("market.sync")
|
|
2801
|
+
}),
|
|
2802
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2803
|
+
type: "button",
|
|
2804
|
+
className: panel_module_css_default.opBtn,
|
|
2805
|
+
disabled: scanning,
|
|
2806
|
+
onClick: () => {
|
|
2807
|
+
scanRepo(record.repo);
|
|
2808
|
+
},
|
|
2809
|
+
children: scanning ? tt("market.scanning") : tt("market.scan")
|
|
2810
|
+
}),
|
|
2811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2812
|
+
type: "button",
|
|
2813
|
+
className: panel_module_css_default.opBtn,
|
|
2814
|
+
disabled: tagBusy,
|
|
2815
|
+
title: tt("market.removeHint"),
|
|
2816
|
+
onClick: () => {
|
|
2817
|
+
removeMarketSource(record.repo);
|
|
2818
|
+
},
|
|
2819
|
+
children: tt("market.deleteSource")
|
|
2717
2820
|
})
|
|
2718
2821
|
]
|
|
2719
|
-
})
|
|
2822
|
+
});
|
|
2823
|
+
};
|
|
2824
|
+
const rows = [];
|
|
2825
|
+
for (const entry of MARKET_CATALOG) {
|
|
2826
|
+
const record = marketState.repos.find((item) => item.repo === entry.repo);
|
|
2827
|
+
if (record !== void 0) rows.push({
|
|
2828
|
+
key: entry.repo,
|
|
2829
|
+
element: sourceRow(record)
|
|
2830
|
+
});
|
|
2831
|
+
else {
|
|
2832
|
+
const busy = scanningRepo !== null || tagBusy;
|
|
2833
|
+
rows.push({
|
|
2834
|
+
key: entry.repo,
|
|
2835
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2836
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
2837
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2838
|
+
className: panel_module_css_default.rowMain,
|
|
2839
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2840
|
+
className: panel_module_css_default.rowName,
|
|
2841
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2842
|
+
className: panel_module_css_default.sourceLink,
|
|
2843
|
+
href: "https://github.com/" + entry.repo,
|
|
2844
|
+
target: "_blank",
|
|
2845
|
+
rel: "noreferrer",
|
|
2846
|
+
children: entry.repo
|
|
2847
|
+
})
|
|
2848
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2849
|
+
className: panel_module_css_default.rowDesc,
|
|
2850
|
+
children: tt(entry.descriptionKey)
|
|
2851
|
+
})]
|
|
2852
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2853
|
+
type: "button",
|
|
2854
|
+
className: panel_module_css_default.opBtn,
|
|
2855
|
+
disabled: busy,
|
|
2856
|
+
onClick: () => {
|
|
2857
|
+
addSource(entry.repo);
|
|
2858
|
+
},
|
|
2859
|
+
children: tt("market.add")
|
|
2860
|
+
})]
|
|
2861
|
+
})
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
for (const record of marketState.repos) {
|
|
2866
|
+
if (MARKET_CATALOG.some((entry) => entry.repo === record.repo)) continue;
|
|
2867
|
+
rows.push({
|
|
2868
|
+
key: record.repo,
|
|
2869
|
+
element: sourceRow(record)
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
const unaddedCatalog = MARKET_CATALOG.filter((entry) => !marketState.repos.some((record) => record.repo === entry.repo));
|
|
2873
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2720
2874
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2721
2875
|
className: panel_module_css_default.section,
|
|
2722
2876
|
children: [
|
|
@@ -2725,7 +2879,7 @@ window.__ModuleLoader__.load({
|
|
|
2725
2879
|
children: [
|
|
2726
2880
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2727
2881
|
className: panel_module_css_default.sectionTitleFill,
|
|
2728
|
-
children: tt("market.
|
|
2882
|
+
children: tt("market.title")
|
|
2729
2883
|
}),
|
|
2730
2884
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2731
2885
|
type: "button",
|
|
@@ -2747,10 +2901,6 @@ window.__ModuleLoader__.load({
|
|
|
2747
2901
|
})
|
|
2748
2902
|
]
|
|
2749
2903
|
}),
|
|
2750
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2751
|
-
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintPadded,
|
|
2752
|
-
children: tt("market.addHint")
|
|
2753
|
-
}),
|
|
2754
2904
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2755
2905
|
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsPadded,
|
|
2756
2906
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -2776,97 +2926,19 @@ window.__ModuleLoader__.load({
|
|
|
2776
2926
|
children: tt("market.addSource")
|
|
2777
2927
|
})]
|
|
2778
2928
|
}),
|
|
2929
|
+
unaddedCatalog.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2930
|
+
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintPadded,
|
|
2931
|
+
children: tt("market.catalogHint")
|
|
2932
|
+
}) : null,
|
|
2779
2933
|
marketState.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2780
2934
|
className: panel_module_css_default.empty,
|
|
2781
2935
|
children: tt("panel.loading")
|
|
2782
2936
|
}) : null,
|
|
2783
|
-
marketState.status
|
|
2937
|
+
marketState.status !== "loading" && rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2784
2938
|
className: panel_module_css_default.empty,
|
|
2785
2939
|
children: tt("market.noSources")
|
|
2786
2940
|
}) : null,
|
|
2787
|
-
marketState.
|
|
2788
|
-
const releaseCheck = marketCheck[record.repo];
|
|
2789
|
-
const skillCheck = sourceCheck[record.repo];
|
|
2790
|
-
const installedCount = sourcesState?.sources.find((source) => source.repo === record.repo)?.skills.length ?? 0;
|
|
2791
|
-
const scanning = scanningRepo === record.repo;
|
|
2792
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2793
|
-
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
2794
|
-
children: [
|
|
2795
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2796
|
-
className: panel_module_css_default.rowMain,
|
|
2797
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2798
|
-
className: panel_module_css_default.rowName,
|
|
2799
|
-
children: [
|
|
2800
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2801
|
-
className: panel_module_css_default.sourceLink,
|
|
2802
|
-
href: "https://github.com/" + record.repo,
|
|
2803
|
-
target: "_blank",
|
|
2804
|
-
rel: "noreferrer",
|
|
2805
|
-
children: record.repo
|
|
2806
|
-
}),
|
|
2807
|
-
record.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2808
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
2809
|
-
children: record.ref
|
|
2810
|
-
}) : null,
|
|
2811
|
-
installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2812
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
2813
|
-
children: tt("market.installed", { count: installedCount })
|
|
2814
|
-
}) : null,
|
|
2815
|
-
skillCheck?.changed === true && skillCheck.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2816
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
2817
|
-
children: tt("market.updatable", { count: skillCheck.updated.length })
|
|
2818
|
-
}) : null,
|
|
2819
|
-
skillCheck !== void 0 && skillCheck.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2820
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
|
|
2821
|
-
children: tt("market.deletedUpstream", { count: skillCheck.deleted.length })
|
|
2822
|
-
}) : null,
|
|
2823
|
-
releaseCheck?.updateAvailable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2824
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
2825
|
-
children: releaseCheck.latestTag !== void 0 ? tt("market.newRelease", { version: releaseCheck.latestTag }) : tt("market.updated")
|
|
2826
|
-
}) : null
|
|
2827
|
-
]
|
|
2828
|
-
})
|
|
2829
|
-
}),
|
|
2830
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2831
|
-
type: "button",
|
|
2832
|
-
className: panel_module_css_default.opBtn,
|
|
2833
|
-
disabled: scanning,
|
|
2834
|
-
onClick: () => {
|
|
2835
|
-
checkMarket();
|
|
2836
|
-
},
|
|
2837
|
-
children: tt("market.check")
|
|
2838
|
-
}),
|
|
2839
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2840
|
-
type: "button",
|
|
2841
|
-
className: panel_module_css_default.opBtn + (releaseCheck?.updateAvailable === true ? " " + panel_module_css_default.opDanger : ""),
|
|
2842
|
-
disabled: syncingMarket === record.repo,
|
|
2843
|
-
onClick: () => {
|
|
2844
|
-
syncMarketSource(record.repo);
|
|
2845
|
-
},
|
|
2846
|
-
children: syncingMarket === record.repo ? tt("market.syncing") : tt("market.sync")
|
|
2847
|
-
}),
|
|
2848
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2849
|
-
type: "button",
|
|
2850
|
-
className: panel_module_css_default.opBtn,
|
|
2851
|
-
disabled: scanning,
|
|
2852
|
-
onClick: () => {
|
|
2853
|
-
scanRepo(record.repo);
|
|
2854
|
-
},
|
|
2855
|
-
children: scanning ? tt("market.scanning") : tt("market.scan")
|
|
2856
|
-
}),
|
|
2857
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2858
|
-
type: "button",
|
|
2859
|
-
className: panel_module_css_default.opBtn,
|
|
2860
|
-
disabled: tagBusy,
|
|
2861
|
-
title: tt("market.removeHint"),
|
|
2862
|
-
onClick: () => {
|
|
2863
|
-
removeMarketSource(record.repo);
|
|
2864
|
-
},
|
|
2865
|
-
children: tt("market.deleteSource")
|
|
2866
|
-
})
|
|
2867
|
-
]
|
|
2868
|
-
}, record.repo);
|
|
2869
|
-
})
|
|
2941
|
+
marketState.status !== "loading" ? rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: row.element }, row.key)) : null
|
|
2870
2942
|
]
|
|
2871
2943
|
}),
|
|
2872
2944
|
repoDiscoverState.status === "scanning" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -3039,6 +3111,8 @@ window.__ModuleLoader__.load({
|
|
|
3039
3111
|
const [repoImporting, setRepoImporting] = (0, react.useState)(false);
|
|
3040
3112
|
const [repoResult, setRepoResult] = (0, react.useState)(null);
|
|
3041
3113
|
const [search, setSearch] = (0, react.useState)("");
|
|
3114
|
+
/** 工作区(项目)路径;空 = 只看用户级技能。 */
|
|
3115
|
+
const [workspace, setWorkspace] = (0, react.useState)("");
|
|
3042
3116
|
const [detail, setDetail] = (0, react.useState)(null);
|
|
3043
3117
|
const [detailLoading, setDetailLoading] = (0, react.useState)(false);
|
|
3044
3118
|
const [busyNames, setBusyNames] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
@@ -3083,8 +3157,10 @@ window.__ModuleLoader__.load({
|
|
|
3083
3157
|
const [newTagName, setNewTagName] = (0, react.useState)("");
|
|
3084
3158
|
const [tagBusy, setTagBusy] = (0, react.useState)(false);
|
|
3085
3159
|
const [editSearch, setEditSearch] = (0, react.useState)("");
|
|
3086
|
-
/** 分组视图里收起的分组(key 为 tag:<id> 或
|
|
3160
|
+
/** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project 树键)。 */
|
|
3087
3161
|
const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3162
|
+
/** 项目级三级树里已细分(按 .dsh/.agents)的项目键。 */
|
|
3163
|
+
const [subdividedProjects, setSubdividedProjects] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3088
3164
|
const [showLegend, setShowLegend] = (0, react.useState)(false);
|
|
3089
3165
|
const toggleGroupCollapse = (0, react.useCallback)((key) => {
|
|
3090
3166
|
setCollapsedGroups((previous) => {
|
|
@@ -3094,9 +3170,17 @@ window.__ModuleLoader__.load({
|
|
|
3094
3170
|
return next;
|
|
3095
3171
|
});
|
|
3096
3172
|
}, []);
|
|
3173
|
+
const toggleSubdivide = (0, react.useCallback)((key) => {
|
|
3174
|
+
setSubdividedProjects((previous) => {
|
|
3175
|
+
const next = new Set(previous);
|
|
3176
|
+
if (next.has(key)) next.delete(key);
|
|
3177
|
+
else next.add(key);
|
|
3178
|
+
return next;
|
|
3179
|
+
});
|
|
3180
|
+
}, []);
|
|
3097
3181
|
const load = (0, react.useCallback)(async () => {
|
|
3098
3182
|
try {
|
|
3099
|
-
const next = await api.catalog();
|
|
3183
|
+
const next = await api.catalog(workspace !== "" ? { cwd: workspace } : void 0);
|
|
3100
3184
|
setCatalog(next);
|
|
3101
3185
|
setLoadError(null);
|
|
3102
3186
|
} catch (error) {
|
|
@@ -3104,7 +3188,7 @@ window.__ModuleLoader__.load({
|
|
|
3104
3188
|
} finally {
|
|
3105
3189
|
setLoading(false);
|
|
3106
3190
|
}
|
|
3107
|
-
}, [api]);
|
|
3191
|
+
}, [api, workspace]);
|
|
3108
3192
|
const loadUses = (0, react.useCallback)(async () => {
|
|
3109
3193
|
try {
|
|
3110
3194
|
const result = await api.stats();
|
|
@@ -3208,13 +3292,13 @@ window.__ModuleLoader__.load({
|
|
|
3208
3292
|
setDetailLoading(true);
|
|
3209
3293
|
setLoadError(null);
|
|
3210
3294
|
try {
|
|
3211
|
-
setDetail(await api.skill(name));
|
|
3295
|
+
setDetail(await api.skill(name, workspace !== "" ? { cwd: workspace } : void 0));
|
|
3212
3296
|
} catch (error) {
|
|
3213
3297
|
setLoadError(errorMessage(error));
|
|
3214
3298
|
} finally {
|
|
3215
3299
|
setDetailLoading(false);
|
|
3216
3300
|
}
|
|
3217
|
-
}, [api]);
|
|
3301
|
+
}, [api, workspace]);
|
|
3218
3302
|
const toggle = (0, react.useCallback)(async (skill, enabled) => {
|
|
3219
3303
|
setBusyNames((previous) => new Set(previous).add(skill.name));
|
|
3220
3304
|
setLoadError(null);
|
|
@@ -3802,7 +3886,7 @@ window.__ModuleLoader__.load({
|
|
|
3802
3886
|
const sourceOptions = (0, react.useMemo)(() => {
|
|
3803
3887
|
const skills = catalog?.skills ?? [];
|
|
3804
3888
|
const repos = [...new Set(skills.map((skill) => origins[skill.name]).filter((repo) => repo !== void 0))].sort();
|
|
3805
|
-
const hasPrivate = skills.some((skill) => origins[skill.name] === void 0);
|
|
3889
|
+
const hasPrivate = skills.some((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
|
|
3806
3890
|
return [...repos, ...hasPrivate ? [PRIVATE_SOURCE] : []];
|
|
3807
3891
|
}, [catalog, origins]);
|
|
3808
3892
|
const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) => normalized.length === 0 || skill.name.toLocaleLowerCase().includes(normalized) || skill.description.toLocaleLowerCase().includes(normalized)), [catalog, normalized]);
|
|
@@ -3818,6 +3902,7 @@ window.__ModuleLoader__.load({
|
|
|
3818
3902
|
repoImporting,
|
|
3819
3903
|
repoResult,
|
|
3820
3904
|
search,
|
|
3905
|
+
workspace,
|
|
3821
3906
|
detail,
|
|
3822
3907
|
detailLoading,
|
|
3823
3908
|
busyNames,
|
|
@@ -3859,6 +3944,7 @@ window.__ModuleLoader__.load({
|
|
|
3859
3944
|
tagBusy,
|
|
3860
3945
|
editSearch,
|
|
3861
3946
|
collapsedGroups,
|
|
3947
|
+
subdividedProjects,
|
|
3862
3948
|
showLegend,
|
|
3863
3949
|
actionNames,
|
|
3864
3950
|
viewNames,
|
|
@@ -3874,6 +3960,7 @@ window.__ModuleLoader__.load({
|
|
|
3874
3960
|
setLoadError,
|
|
3875
3961
|
setSuccessBanner,
|
|
3876
3962
|
setSearch,
|
|
3963
|
+
setWorkspace,
|
|
3877
3964
|
setDetail,
|
|
3878
3965
|
setShowForm,
|
|
3879
3966
|
setFormName,
|
|
@@ -3900,6 +3987,7 @@ window.__ModuleLoader__.load({
|
|
|
3900
3987
|
setEditSearch,
|
|
3901
3988
|
setShowLegend,
|
|
3902
3989
|
toggleGroupCollapse,
|
|
3990
|
+
toggleSubdivide,
|
|
3903
3991
|
checkUpdate,
|
|
3904
3992
|
loadMarket,
|
|
3905
3993
|
openDetail,
|
|
@@ -3935,9 +4023,24 @@ window.__ModuleLoader__.load({
|
|
|
3935
4023
|
}
|
|
3936
4024
|
//#endregion
|
|
3937
4025
|
//#region src/client/panel/SkillHubPanel.tsx
|
|
4026
|
+
/**
|
|
4027
|
+
* The skill hub panel: catalog grouped by tags + source collections, search
|
|
4028
|
+
* and filter in one row, per-group tri-state switches with conflict dialogs,
|
|
4029
|
+
* upstream source tracking (check / sync / follow upstream deletion into a
|
|
4030
|
+
* restorable trash), market sources, disabled re-enable,
|
|
4031
|
+
* detail inspection, and the new-skill scaffold form.
|
|
4032
|
+
*
|
|
4033
|
+
* Thin shell: state and flows live in useSkillHub, the tab contents live in
|
|
4034
|
+
* SourcesView / ScenesView / MarketView, and the dialog family lives in
|
|
4035
|
+
* dialogs.tsx. This component owns only the shared chrome (header, banners,
|
|
4036
|
+
* filter bar, shared sections) and the view routing — and holds no hooks,
|
|
4037
|
+
* so the detail / tag-editor early returns are safe.
|
|
4038
|
+
*/
|
|
3938
4039
|
function SkillHubPanel(props) {
|
|
3939
4040
|
const hub = useSkillHub(props.api);
|
|
3940
|
-
|
|
4041
|
+
/** 工作区输入草稿:回车才应用,避免每次按键都触发目录重拉。 */
|
|
4042
|
+
const [workspaceDraft, setWorkspaceDraft] = (0, react.useState)("");
|
|
4043
|
+
const { catalog, loading, loadError, successBanner, updateState, detail, detailLoading, showForm, formName, formDesc, formRoot, formBusy, formMessage, hubConfig, tab, skillView, sourceFilter, sortKey, search, workspace, setWorkspace, sourcesState, tagBusy, busyNames, normalized, origins, sourceOptions, filtered, conflictDialog, confirmDialog, deleteSkillDialog, confirmClearTrash, branchChoice, branchBusy, marketSyncDialog, syncBusy, editingTag, editName, membersDraft, editSearch, uses, groupsState, sourceCheck, checkingSource, syncingSource, showLegend, setLoadError, setSuccessBanner, setDetail, setShowForm, setFormName, setFormDesc, setFormRoot, setFormMessage, setTab, setSkillView, setSourceFilter, setSortKey, setSearch, setConflictDialog, setConfirmDialog, setDeleteSkillDialog, setConfirmClearTrash, setBranchChoice, setMarketSyncDialog, setEditingTag, setEditName, setMembersDraft, setEditSearch, setShowLegend, checkUpdate, loadMarket, checkSources, requestSync, requestDelete, restoreTrash, clearTrash, runDeleteSkill, runConfirmed, resolveConflict, confirmBranchChoice, confirmMarketSync, create, saveTag, deleteTag, enableDisabled } = hub;
|
|
3941
4044
|
if (detail !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDetailView, {
|
|
3942
4045
|
detail,
|
|
3943
4046
|
hubConfig,
|
|
@@ -4081,6 +4184,30 @@ window.__ModuleLoader__.load({
|
|
|
4081
4184
|
})
|
|
4082
4185
|
]
|
|
4083
4186
|
}),
|
|
4187
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4188
|
+
className: panel_module_css_default.workspaceBox,
|
|
4189
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4190
|
+
className: panel_module_css_default.search + " " + panel_module_css_default.workspaceInput,
|
|
4191
|
+
value: workspaceDraft,
|
|
4192
|
+
placeholder: workspace !== "" ? workspace : tt("panel.workspacePlaceholder"),
|
|
4193
|
+
title: tt("panel.workspaceHint"),
|
|
4194
|
+
onChange: (event) => {
|
|
4195
|
+
setWorkspaceDraft(event.target.value);
|
|
4196
|
+
},
|
|
4197
|
+
onKeyDown: (event) => {
|
|
4198
|
+
if (event.key === "Enter") setWorkspace(event.target.value.trim());
|
|
4199
|
+
}
|
|
4200
|
+
}), workspace !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4201
|
+
type: "button",
|
|
4202
|
+
className: panel_module_css_default.opBtn,
|
|
4203
|
+
title: tt("panel.workspaceClear"),
|
|
4204
|
+
onClick: () => {
|
|
4205
|
+
setWorkspace("");
|
|
4206
|
+
setWorkspaceDraft("");
|
|
4207
|
+
},
|
|
4208
|
+
children: "✕"
|
|
4209
|
+
}) : null]
|
|
4210
|
+
}),
|
|
4084
4211
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4085
4212
|
type: "button",
|
|
4086
4213
|
className: panel_module_css_default.legendToggle + (showLegend ? " " + panel_module_css_default.legendToggleActive : ""),
|
|
@@ -4473,8 +4600,20 @@ window.__ModuleLoader__.load({
|
|
|
4473
4600
|
//#region src/client/index.tsx
|
|
4474
4601
|
/** Locale namespace this plugin owns. */
|
|
4475
4602
|
const NS = "dsh-skill-hub";
|
|
4476
|
-
/**
|
|
4477
|
-
|
|
4603
|
+
/**
|
|
4604
|
+
* Required services (fiber inject waiting — the runtime must be up first).
|
|
4605
|
+
* `connection`/`remote` are the settings transport's own prerequisites
|
|
4606
|
+
* (`ctx.settingsScope.bind` resolves them on the caller's fiber), and
|
|
4607
|
+
* `settingsScope` is the namespace-scope binder itself; mirror the official
|
|
4608
|
+
* settings-plugins inject list.
|
|
4609
|
+
*/
|
|
4610
|
+
const inject = [
|
|
4611
|
+
"slots",
|
|
4612
|
+
"locale",
|
|
4613
|
+
"connection",
|
|
4614
|
+
"remote",
|
|
4615
|
+
"settingsScope"
|
|
4616
|
+
];
|
|
4478
4617
|
/**
|
|
4479
4618
|
* Mount the settings card and the skill hub section.
|
|
4480
4619
|
* @param ctx - client root context (slots, locale).
|
|
@@ -4486,11 +4625,10 @@ window.__ModuleLoader__.load({
|
|
|
4486
4625
|
}), "dsh-skill-hub: dictionaries");
|
|
4487
4626
|
const t = ctx.locale.bind(NS);
|
|
4488
4627
|
const api = new SkillHubApi();
|
|
4489
|
-
const settingsCard = new SkillHubSettingsCardController(
|
|
4628
|
+
const settingsCard = new SkillHubSettingsCardController(ctx.settingsScope.bind({ namespace: NS }));
|
|
4490
4629
|
ctx.effect(() => ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
4491
4630
|
name: "settings.plugin.item",
|
|
4492
|
-
|
|
4493
|
-
order: 115,
|
|
4631
|
+
key: NS,
|
|
4494
4632
|
locale: NS,
|
|
4495
4633
|
inject: () => settingsCard.inject()
|
|
4496
4634
|
}, SkillHubSettingsCard)), "dsh-skill-hub: settings card");
|