@linxin666/dsh-client-ui-community-plugins 0.1.19 → 0.2.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/lib/client.js CHANGED
@@ -151,7 +151,7 @@ window.__ModuleLoader__.load({
151
151
  children: props.t("settings.readOnly")
152
152
  }) : null,
153
153
  props.children,
154
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
154
+ props.hideFooter === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
155
155
  className: settings_card_module_css_default.footer,
156
156
  children: [
157
157
  state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
@@ -491,6 +491,23 @@ window.__ModuleLoader__.load({
491
491
  };
492
492
  //#endregion
493
493
  //#region src/client/generated/community.ts
494
+ /**
495
+ * AUTO-GENERATED by scripts/community-index — do not edit by hand.
496
+ * Community plugin index for the settings page: every entry links to the
497
+ * contributor's own repository. This repository never vendors the listed
498
+ * plugins — it only points users at them.
499
+ * Regenerate with `node scripts/community-index`.
500
+ */
501
+ /** Stable category ids rendered as marketplace-style filter pills (the runtime guard imports this). */
502
+ const COMMUNITY_CATEGORIES = [
503
+ "ui",
504
+ "agent",
505
+ "tools",
506
+ "knowledge",
507
+ "integration",
508
+ "security",
509
+ "utility"
510
+ ];
494
511
  /** Every community plugin, in community.json order. */
495
512
  const COMMUNITY_PLUGINS = [
496
513
  {
@@ -500,7 +517,8 @@ window.__ModuleLoader__.load({
500
517
  "author": "omdsh-dev",
501
518
  "repo": "https://github.com/omdsh-dev/dsh-data-agent",
502
519
  "description": "为 DSH 定义专用 Data Agent 预设,让 AI 帮你查询、更新、分析数据。",
503
- "descriptionEn": "Defines a dedicated Data Agent preset for DSH so the AI can query, update and analyze data."
520
+ "descriptionEn": "Defines a dedicated Data Agent preset for DSH so the AI can query, update and analyze data.",
521
+ "category": "agent"
504
522
  },
505
523
  {
506
524
  "id": "dsh-tui",
@@ -509,7 +527,8 @@ window.__ModuleLoader__.load({
509
527
  "author": "ccch1mneyyy",
510
528
  "repo": "https://github.com/ccch1mneyyy/dsh-TUI",
511
529
  "description": "Claude Code 风格全屏交互终端插件:像素鲸鱼顶栏、实时工作状态行、思考流式展开、双击 Esc 回滚、上下文进度条与 TPS 仪表。",
512
- "descriptionEn": "A Claude Code style fullscreen interactive terminal plugin: pixel-whale header, live working-state line, streaming reasoning expansion, double-Esc rollback, context progress bar and TPS gauges."
530
+ "descriptionEn": "A Claude Code style fullscreen interactive terminal plugin: pixel-whale header, live working-state line, streaming reasoning expansion, double-Esc rollback, context progress bar and TPS gauges.",
531
+ "category": "ui"
513
532
  },
514
533
  {
515
534
  "id": "dsh-tianshu-tui",
@@ -518,7 +537,8 @@ window.__ModuleLoader__.load({
518
537
  "author": "huiliyi37",
519
538
  "repo": "https://github.com/huiliyi37/dsh-tianshu-tui",
520
539
  "description": "基于官方 DeepSeek Harness 的交互式终端 UI 插件,在官方基础上增加 TDD 与证据门等工作流。",
521
- "descriptionEn": "An interactive terminal UI plugin for DeepSeek Harness that adds TDD and evidence-gate workflows on top of the official base."
540
+ "descriptionEn": "An interactive terminal UI plugin for DeepSeek Harness that adds TDD and evidence-gate workflows on top of the official base.",
541
+ "category": "ui"
522
542
  },
523
543
  {
524
544
  "id": "dsh-chat-summary",
@@ -528,7 +548,8 @@ window.__ModuleLoader__.load({
528
548
  "repo": "https://github.com/v833/dsh-chat-summary",
529
549
  "description": "总结当前对话并导出为 Markdown / DOCX / PDF,可选 LLM 智能总结(用户自配 API Key)。",
530
550
  "descriptionEn": "Summarize the current conversation and export it as Markdown / DOCX / PDF, with optional LLM summarization using your own API key.",
531
- "npm": "@linxin666/dsh-client-ui-chat-summary"
551
+ "npm": "@linxin666/dsh-client-ui-chat-summary",
552
+ "category": "knowledge"
532
553
  },
533
554
  {
534
555
  "id": "dsh-builtin-toggles",
@@ -538,7 +559,8 @@ window.__ModuleLoader__.load({
538
559
  "repo": "https://github.com/Starfie1d1272/dsh-builtin-toggles",
539
560
  "description": "Evidence-backed 内置 capability Inspector:展示 DSH Web built-in capability 的 provenance、compatibility 与 structural drift;仅对 9 个经过审阅的 UI leaves 提供 fail-closed 开关。",
540
561
  "descriptionEn": "Evidence-backed built-in capability Inspector: surfaces provenance, compatibility and structural drift of DSH Web built-ins, with fail-closed toggles for only the nine reviewed UI leaves.",
541
- "npm": "dsh-builtin-toggles"
562
+ "npm": "dsh-builtin-toggles",
563
+ "category": "tools"
542
564
  },
543
565
  {
544
566
  "id": "dsh-pilot",
@@ -547,7 +569,8 @@ window.__ModuleLoader__.load({
547
569
  "author": "guo6x",
548
570
  "repo": "https://github.com/guo6x/dsh-pilot",
549
571
  "description": "给 agent 一双会开车的手:零依赖 CDP 浏览器操控(8 个 pilot_* 工具:导航/点击/输入/按键/JS/截图)+ Web GUI 可拖拽驾驶舱面板,无需 Playwright、无需 API key。",
550
- "descriptionEn": "Give your agent hands: zero-dependency CDP browser control (8 pilot_* tools: navigate/click/type/keys/eval/screenshot) plus a draggable cockpit panel in the Web GUI - no Playwright, no API key."
572
+ "descriptionEn": "Give your agent hands: zero-dependency CDP browser control (8 pilot_* tools: navigate/click/type/keys/eval/screenshot) plus a draggable cockpit panel in the Web GUI - no Playwright, no API key.",
573
+ "category": "tools"
551
574
  },
552
575
  {
553
576
  "id": "dsh-housekeeper",
@@ -556,7 +579,8 @@ window.__ModuleLoader__.load({
556
579
  "author": "guo6x",
557
580
  "repo": "https://github.com/guo6x/dsh-housekeeper",
558
581
  "description": "管住 agent 的脏手:工具链台账(node/pnpm/git/gh/ffmpeg 等自动探测)、缓存与临时目录扫描 + 白名单安全一键清理、机器规则 AGENTS.md 查看编辑,全在设置面板完成。",
559
- "descriptionEn": "Keep your agent's hands clean: toolchain inventory, scratch/cache scan with whitelist-guarded one-click cleanup, and the machine rules file (AGENTS.md) view/edit - all in the settings panel."
582
+ "descriptionEn": "Keep your agent's hands clean: toolchain inventory, scratch/cache scan with whitelist-guarded one-click cleanup, and the machine rules file (AGENTS.md) view/edit - all in the settings panel.",
583
+ "category": "utility"
560
584
  },
561
585
  {
562
586
  "id": "dsh-deepread",
@@ -566,7 +590,8 @@ window.__ModuleLoader__.load({
566
590
  "repo": "https://github.com/xiehuan123/dsh-deepread",
567
591
  "description": "五种模式精读插件(quick / deep / map / feynman / book),支持公众号链接与文件输入、批量对比、预算预检与后台任务进度透明,导出 md / mm / html,Web UI 提供工具结果卡片与精读面板。",
568
592
  "descriptionEn": "A five-mode deep reading plugin (quick / deep / map / feynman / book) for links and files, with batch comparison, budget preflight, transparent background-job progress, md / mm / html exports, and Web UI tool-result cards plus a reading panel.",
569
- "npm": "dsh-deepread"
593
+ "npm": "dsh-deepread",
594
+ "category": "knowledge"
570
595
  },
571
596
  {
572
597
  "id": "dsh-mnemon",
@@ -576,7 +601,8 @@ window.__ModuleLoader__.load({
576
601
  "repo": "https://github.com/omdsh-dev/dsh-mnemon",
577
602
  "description": "与 Mnemon CLI 集成的跨 Agent、本地优先持久记忆插件:用户画像 / 工作记忆 / 项目档案与长期 Memory Spaces,支持导入导出。",
578
603
  "descriptionEn": "A cross-agent, local-first persistent memory plugin integrating the Mnemon CLI: profiles, working memory, project documents and long-term Memory Spaces, with import and export.",
579
- "npm": "dsh-mnemon"
604
+ "npm": "dsh-mnemon",
605
+ "category": "knowledge"
580
606
  },
581
607
  {
582
608
  "id": "dsh-plugin-hub",
@@ -585,7 +611,8 @@ window.__ModuleLoader__.load({
585
611
  "author": "Noob-stupid",
586
612
  "repo": "https://github.com/Noob-stupid/dsh-plugin-hub",
587
613
  "description": "插件管理面板:已安装插件一键启用/停用,内置 GitHub dsh-plugin 插件市场(官方/聚合识别、子包浏览、一键安装与本地 AI 兜底修复、删除卸载)。",
588
- "descriptionEn": "Plugin management panel: one-click enable/disable, a GitHub dsh-plugin marketplace with official/aggregate detection, subpackage browsing, one-click installs with local-AI fallback repair, and uninstall."
614
+ "descriptionEn": "Plugin management panel: one-click enable/disable, a GitHub dsh-plugin marketplace with official/aggregate detection, subpackage browsing, one-click installs with local-AI fallback repair, and uninstall.",
615
+ "category": "tools"
589
616
  },
590
617
  {
591
618
  "id": "dsh-genui",
@@ -594,7 +621,8 @@ window.__ModuleLoader__.load({
594
621
  "author": "omdsh-dev",
595
622
  "repo": "https://github.com/omdsh-dev/dsh-genui",
596
623
  "description": "给模型输出配交互式 UI:助手回复内联渲染 dsh-ui fence(布局、图表、表单、Mermaid、3D),支持流式渲染与面板停靠,组件交互可回传模型。",
597
- "descriptionEn": "Interactive UI inside assistant replies via the dsh-ui fence: layouts, charts, forms, Mermaid and 3D with streaming rendering, panel docking and actions that loop back to the model."
624
+ "descriptionEn": "Interactive UI inside assistant replies via the dsh-ui fence: layouts, charts, forms, Mermaid and 3D with streaming rendering, panel docking and actions that loop back to the model.",
625
+ "category": "ui"
598
626
  },
599
627
  {
600
628
  "id": "dsh-annotation",
@@ -603,7 +631,8 @@ window.__ModuleLoader__.load({
603
631
  "author": "omdsh-dev",
604
632
  "repo": "https://github.com/omdsh-dev/dsh-annotation",
605
633
  "description": "选中助手文字即可批注,回车随消息发送;自己的气泡只显示问题与「批注 ×N」标签,模型按 Annotation N 逐条对照回复(悬浮芯片)。",
606
- "descriptionEn": "Select text in an assistant reply to annotate it; annotations are sent with your next message, hidden from your own bubble behind an Annotations xN chip, and the model replies per Annotation N with hoverable chips."
634
+ "descriptionEn": "Select text in an assistant reply to annotate it; annotations are sent with your next message, hidden from your own bubble behind an Annotations xN chip, and the model replies per Annotation N with hoverable chips.",
635
+ "category": "ui"
607
636
  },
608
637
  {
609
638
  "id": "deepseek-harness-auth",
@@ -613,7 +642,8 @@ window.__ModuleLoader__.load({
613
642
  "repo": "https://github.com/taichuy/deepseek-harness-auth",
614
643
  "description": "为 DSH Web 公网部署提供登录认证前置代理,支持账号密码、验证码、失败锁定和 IP/CIDR 白名单。",
615
644
  "descriptionEn": "An authentication proxy for public DSH Web deployments with password login, captcha, failed-attempt locking, and IP/CIDR allowlists.",
616
- "npm": "deepseek-harness-auth"
645
+ "npm": "deepseek-harness-auth",
646
+ "category": "security"
617
647
  },
618
648
  {
619
649
  "id": "dsh-cloud-sync",
@@ -623,11 +653,80 @@ window.__ModuleLoader__.load({
623
653
  "repo": "https://github.com/dickpy/dsh-cloud-sync",
624
654
  "description": "支持 WebDAV、S3、阿里云 OSS、腾讯云 COS 与 MinIO 的 DSH 云同步插件,可同步 profile、插件配置及本地插件源码归档。",
625
655
  "descriptionEn": "DSH cloud sync for WebDAV, S3, Alibaba Cloud OSS, Tencent Cloud COS and MinIO, syncing profiles, plugin settings and local plugin source archives.",
626
- "npm": "@dickpy/dsh-cloud-sync"
656
+ "npm": "@dickpy/dsh-cloud-sync",
657
+ "category": "integration"
658
+ },
659
+ {
660
+ "id": "dsh-auto-memory",
661
+ "name": "自动记忆",
662
+ "nameEn": "Auto Memory",
663
+ "author": "Aik358",
664
+ "repo": "https://github.com/Aik358/dsh-auto-memory",
665
+ "description": "三层自动记忆插件:用户级/项目笔记/每日日志自动注入与检索、每轮自动沉淀、AI 时段问候、智能检索、日历与工作区思维导图,支持继承 WorkBuddy/CodeBuddy/Claude Code/Codex 等其他 AI 工具的记忆。",
666
+ "descriptionEn": "Three-layer auto memory for DSH: user-level/project-notes/daily-logs with automatic injection and retrieval, per-turn auto-consolidation, AI greetings, smart search, calendar, workspace mind map, and inheritance from WorkBuddy/CodeBuddy/Claude Code/Codex.",
667
+ "npm": "@a9i5k4/dsh-auto-memory",
668
+ "category": "knowledge"
669
+ },
670
+ {
671
+ "id": "dsh-memoir",
672
+ "name": "项目记忆 Memoir",
673
+ "nameEn": "Project Memory Memoir",
674
+ "author": "Qinling-Melon-Farmers",
675
+ "repo": "https://github.com/Qinling-Melon-Farmers/dsh-memoir",
676
+ "description": "项目持久化记忆与会话经验沉淀:memoir_record / memoir_read 工具(BM25 相关度排序检索)、每轮工作自动蒸馏、记忆诊断面板与跨项目全局检索,纯本地零外部依赖。",
677
+ "descriptionEn": "Project persistent memory and session-lessons distillation: memoir_record / memoir_read tools with BM25-ranked retrieval, per-turn auto-distill, a memory diagnostics panel and cross-project search - fully local, no external dependency.",
678
+ "category": "knowledge"
679
+ },
680
+ {
681
+ "id": "dsh-worktime-board",
682
+ "name": "牛马修仙看板",
683
+ "nameEn": "Worktime Cultivation Board",
684
+ "author": "spacexun2",
685
+ "repo": "https://github.com/spacexun2/dsh-worktime-board",
686
+ "description": "DeepSeek Harness 工时统计 × 修仙养成:日/周/月/学年四档汇总 agent 活跃时长、token、输入与工具调用,十二重境界(炼气→宇宙洪荒)量化进度,纯本地存储。",
687
+ "descriptionEn": "Worktime statistics meets cultivation: daily / weekly / monthly / yearly agent activity, token, input and tool-call summaries with a twelve-realm progression (Qi Refining to Cosmic Chaos), fully local.",
688
+ "npm": "dsh-worktime-board",
689
+ "category": "utility"
690
+ },
691
+ {
692
+ "id": "dsh-skill-explorer",
693
+ "name": "技能中心(Skill Explorer)",
694
+ "nameEn": "Skill Explorer",
695
+ "author": "wingsky-1",
696
+ "repo": "https://github.com/wingsky-1/dsh-skill-explorer",
697
+ "description": "DSH 技能中心:按来源分级浏览已加载 skill(系统内置 / 项目 / 用户 / 自定义 / 运行时),支持启用/禁用、创建与删除(移入 .trash)。",
698
+ "descriptionEn": "DSH skill center: browse loaded skills grouped by source (bundled / project / user / custom / runtime), with enable/disable, create and delete (move to .trash).",
699
+ "npm": "@linxin666/dsh-client-ui-skill-explorer",
700
+ "category": "tools"
701
+ },
702
+ {
703
+ "id": "dsh-dream-skin",
704
+ "name": "dsh-dream-skin",
705
+ "nameEn": "dsh-dream-skin",
706
+ "author": "RevolutionLA",
707
+ "repo": "https://github.com/RevolutionLA/dsh-dream-skin",
708
+ "description": "DSH Web 一键换肤插件:8 套原创主题、背景壁纸(透明度/模糊/渐变/URL)、每用户强调色、主题包导入导出与分享链接、收藏与随机,纯原生 token 系统。",
709
+ "descriptionEn": "One-command skinning for DSH Web: 8 original themes, wallpaper (opacity/blur/gradient/URL), per-user accent, shareable theme packs with favorites and surprise-me. Purely native token system.",
710
+ "category": "ui"
627
711
  }
628
712
  ];
629
713
  //#endregion
630
714
  //#region src/client/community-guard.ts
715
+ /**
716
+ * Runtime guard for the generated community index entries. The index is
717
+ * build-time generated data, but the card renders whatever the module
718
+ * carries; a hand-written narrowing keeps one malformed entry from breaking
719
+ * the whole list at render time.
720
+ */
721
+ /** Category ids the card knows how to label; others are treated as uncategorized. */
722
+ const KNOWN_CATEGORIES = COMMUNITY_CATEGORIES;
723
+ /**
724
+ * The install command is pasted into a shell, so repo/npm must be free of
725
+ * shell metacharacters: repo is a plain https URL of path-safe characters,
726
+ * npm a standard (optionally scoped) package name.
727
+ */
728
+ const REPO_SAFE_RE = /^https:\/\/[A-Za-z0-9._~\/-]+$/;
729
+ const NPM_SAFE_RE = /^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
631
730
  /** True when the value is a well-formed community plugin entry. */
632
731
  function isCommunityPluginEntry(value) {
633
732
  if (typeof value !== "object" || value === null) return false;
@@ -635,15 +734,16 @@ window.__ModuleLoader__.load({
635
734
  if (typeof entry.id !== "string" || entry.id === "") return false;
636
735
  if (typeof entry.name !== "string" || typeof entry.nameEn !== "string") return false;
637
736
  if (typeof entry.author !== "string" || entry.author === "") return false;
638
- if (typeof entry.repo !== "string" || !entry.repo.startsWith("https://")) return false;
737
+ if (typeof entry.repo !== "string" || !REPO_SAFE_RE.test(entry.repo)) return false;
639
738
  if (entry.description !== void 0 && typeof entry.description !== "string") return false;
640
739
  if (entry.descriptionEn !== void 0 && typeof entry.descriptionEn !== "string") return false;
641
- if (entry.npm !== void 0 && typeof entry.npm !== "string") return false;
740
+ if (entry.npm !== void 0 && (typeof entry.npm !== "string" || !NPM_SAFE_RE.test(entry.npm))) return false;
741
+ if (entry.category !== void 0 && (typeof entry.category !== "string" || !KNOWN_CATEGORIES.includes(entry.category))) return false;
642
742
  return true;
643
743
  }
644
744
  //#endregion
645
745
  //#region \0dsh-css:packages/dsh-community-plugins/src/client/community.module.css.mjs
646
- const css = ".z0J_8a_sectionList{margin:0;padding:0;list-style:none}.z0J_8a_entries{flex-direction:column;gap:8px;margin:8px 0 0;padding:0;list-style:none;display:flex}.z0J_8a_entry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex-direction:column;gap:4px;padding:10px 12px;display:flex}.z0J_8a_entryHead{justify-content:space-between;align-items:baseline;gap:8px;min-width:0;display:flex}.z0J_8a_entryName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.z0J_8a_entryAuthor{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:none;font-size:12px;overflow:hidden}.z0J_8a_entryDescription,.z0J_8a_entryDescriptionEn{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.4}.z0J_8a_entryDescriptionEn{color:var(--dsw-alias-label-tertiary);font-size:12px}.z0J_8a_entryLinks{flex-wrap:wrap;align-items:center;gap:10px;margin-top:2px;display:flex}.z0J_8a_entryLink{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));font-size:13px;text-decoration:underline}.z0J_8a_entryLink:hover{text-decoration:none}.z0J_8a_entryNpm{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.z0J_8a_entryInstall{flex-wrap:wrap;align-items:center;gap:8px;margin-top:2px;display:flex}.z0J_8a_entryCommand{min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);overflow-wrap:anywhere;border-radius:4px;flex:1;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}.z0J_8a_copyButton{color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:4px;flex:none;padding:2px 10px;font-size:12px;line-height:1.5}.z0J_8a_copyButton:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.z0J_8a_installNote{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:12px;line-height:1.4}.z0J_8a_empty{color:var(--dsw-alias-label-tertiary);margin:0;padding:8px 2px;font-size:13px}.z0J_8a_off{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:13px}.z0J_8a_notice{color:var(--dsw-alias-label-tertiary);margin:10px 2px 0;font-size:12px;line-height:1.4}";
746
+ const css = ".z0J_8a_sectionList{margin:0;padding:0;list-style:none}.z0J_8a_market{flex-direction:column;gap:10px;margin-top:12px;display:flex}.z0J_8a_toolbar{display:flex}.z0J_8a_search{min-width:0;font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;flex:1;padding:6px 10px;font-size:13px;line-height:1.5}.z0J_8a_search::placeholder{color:var(--dsw-alias-label-tertiary)}.z0J_8a_search:focus{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.z0J_8a_search:disabled{opacity:.5}.z0J_8a_filters{flex-wrap:wrap;gap:6px;display:flex}.z0J_8a_pill{font:inherit;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:999px;padding:2px 10px;font-size:12px;line-height:1.6}.z0J_8a_pill:hover:enabled{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_pill:disabled{opacity:.5;cursor:default}.z0J_8a_pillActive{color:var(--dsw-alias-bg-layer-3);background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);font-weight:600}.z0J_8a_pillActive:hover:enabled{color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary)}.z0J_8a_resultCount{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.4}.z0J_8a_grid{grid-template-columns:repeat(auto-fill,minmax(256px,1fr));gap:8px;margin:0;padding:0;list-style:none;display:grid}.z0J_8a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;min-width:0;padding:12px;display:flex}.z0J_8a_card:hover{border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_cardHead{justify-content:space-between;align-items:baseline;gap:8px;min-width:0;display:flex}.z0J_8a_cardName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.z0J_8a_badge{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:1.6}.z0J_8a_badgePublished{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));border-color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill))}.z0J_8a_cardMeta{min-width:0;color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;margin:0;font-size:12px;line-height:1.4;display:flex}.z0J_8a_cardCategory{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.z0J_8a_cardDot{flex:none}.z0J_8a_cardAuthor{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.z0J_8a_cardDescription{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;margin:0;font-size:13px;line-height:1.45;display:-webkit-box;overflow:hidden}.z0J_8a_cardFooter{flex-direction:column;gap:6px;margin-top:auto;padding-top:6px;display:flex}.z0J_8a_cardTop{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.z0J_8a_cardLink{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));font-size:13px;text-decoration:none}.z0J_8a_cardLink:hover{text-decoration:underline}.z0J_8a_cardCommand{box-sizing:border-box;width:100%;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;text-overflow:ellipsis;border-radius:4px;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow:hidden}.z0J_8a_installButton{font:inherit;color:var(--dsw-alias-bg-layer-3);background:var(--dsw-alias-button-info-fill);border:1px solid var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border-radius:6px;flex:none;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5}.z0J_8a_installButton:hover:enabled{opacity:.88}.z0J_8a_installButton:disabled{opacity:.5;cursor:default}.z0J_8a_installButtonCopied{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:0 0}.z0J_8a_installNote{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:12px;line-height:1.4}.z0J_8a_empty{color:var(--dsw-alias-label-tertiary);margin:0;padding:8px 2px;font-size:13px}.z0J_8a_off{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:13px}.z0J_8a_notice{color:var(--dsw-alias-label-tertiary);margin:10px 2px 0;font-size:12px;line-height:1.4}";
647
747
  const tagId = "@linxin666/dsh-client-ui-community-plugins/community.module.css";
648
748
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
649
749
  const tag = document.createElement("style");
@@ -653,33 +753,45 @@ window.__ModuleLoader__.load({
653
753
  document.head.appendChild(tag);
654
754
  }
655
755
  var community_module_css_default = {
656
- "copyButton": "z0J_8a_copyButton",
756
+ "badge": "z0J_8a_badge",
757
+ "badgePublished": "z0J_8a_badgePublished",
758
+ "card": "z0J_8a_card",
759
+ "cardAuthor": "z0J_8a_cardAuthor",
760
+ "cardCategory": "z0J_8a_cardCategory",
761
+ "cardCommand": "z0J_8a_cardCommand",
762
+ "cardDescription": "z0J_8a_cardDescription",
763
+ "cardDot": "z0J_8a_cardDot",
764
+ "cardFooter": "z0J_8a_cardFooter",
765
+ "cardHead": "z0J_8a_cardHead",
766
+ "cardLink": "z0J_8a_cardLink",
767
+ "cardMeta": "z0J_8a_cardMeta",
768
+ "cardName": "z0J_8a_cardName",
769
+ "cardTop": "z0J_8a_cardTop",
657
770
  "empty": "z0J_8a_empty",
658
- "entries": "z0J_8a_entries",
659
- "entry": "z0J_8a_entry",
660
- "entryAuthor": "z0J_8a_entryAuthor",
661
- "entryCommand": "z0J_8a_entryCommand",
662
- "entryDescription": "z0J_8a_entryDescription",
663
- "entryDescriptionEn": "z0J_8a_entryDescriptionEn",
664
- "entryHead": "z0J_8a_entryHead",
665
- "entryInstall": "z0J_8a_entryInstall",
666
- "entryLink": "z0J_8a_entryLink",
667
- "entryLinks": "z0J_8a_entryLinks",
668
- "entryName": "z0J_8a_entryName",
669
- "entryNpm": "z0J_8a_entryNpm",
771
+ "filters": "z0J_8a_filters",
772
+ "grid": "z0J_8a_grid",
773
+ "installButton": "z0J_8a_installButton",
774
+ "installButtonCopied": "z0J_8a_installButtonCopied",
670
775
  "installNote": "z0J_8a_installNote",
776
+ "market": "z0J_8a_market",
671
777
  "notice": "z0J_8a_notice",
672
778
  "off": "z0J_8a_off",
673
- "sectionList": "z0J_8a_sectionList"
779
+ "pill": "z0J_8a_pill",
780
+ "pillActive": "z0J_8a_pillActive",
781
+ "resultCount": "z0J_8a_resultCount",
782
+ "search": "z0J_8a_search",
783
+ "sectionList": "z0J_8a_sectionList",
784
+ "toolbar": "z0J_8a_toolbar"
674
785
  };
675
786
  //#endregion
676
787
  //#region src/client/CommunityPluginsCard.tsx
677
788
  /**
678
789
  * The community plugin index card: a first-level settings section that is
679
- * always open (a static header with the index list directly visible). Its own
680
- * enable switch (backed by the community-plugins settings namespace) gates
681
- * the entry list; the list itself points at contributors' own repositories
682
- * this package only indexes them, it never vendors their code.
790
+ * always open. Its own enable switch (backed by the community-plugins
791
+ * settings namespace) gates the list, which is presented marketplace-style:
792
+ * a search box, category filter pills with counts, and a two-column card grid
793
+ * whose entries link to contributors' own repositories this package only
794
+ * indexes them, it never vendors their code.
683
795
  */
684
796
  /** Bridges the community-plugins scope onto the card's staged form. */
685
797
  var CommunityPluginsCardController = class {
@@ -719,6 +831,25 @@ window.__ModuleLoader__.load({
719
831
  return `dsh plugin --profile web add ${entry.npm ?? entry.repo}`;
720
832
  }
721
833
  /**
834
+ * Localized entry copy read through the card's `t`. Falls back to the raw
835
+ * entry field when the key is missing — the dictionary only carries the
836
+ * generated registry, so injected test entries land on the fallback.
837
+ */
838
+ function entryCopy(t, key, fallback) {
839
+ const value = t(key);
840
+ return value === key ? fallback : value;
841
+ }
842
+ /** Maps a category id onto its locale dictionary key (kept in the generated union). */
843
+ const CATEGORY_KEY = {
844
+ ui: "category.ui",
845
+ agent: "category.agent",
846
+ tools: "category.tools",
847
+ knowledge: "category.knowledge",
848
+ integration: "category.integration",
849
+ security: "category.security",
850
+ utility: "category.utility"
851
+ };
852
+ /**
722
853
  * Render the community plugin index card.
723
854
  * @param props - locale copy, the card snapshot, its form actions, and the
724
855
  * (default-generated) entry list.
@@ -727,7 +858,9 @@ window.__ModuleLoader__.load({
727
858
  function CommunityPluginsCard(props) {
728
859
  const { t } = props;
729
860
  const state = props.useCommunityPluginsCard((snapshot) => snapshot);
730
- const plugins = (props.plugins ?? COMMUNITY_PLUGINS).filter(isCommunityPluginEntry);
861
+ const plugins = (0, react.useMemo)(() => (props.plugins ?? COMMUNITY_PLUGINS).filter(isCommunityPluginEntry), [props.plugins]);
862
+ const [query, setQuery] = (0, react.useState)("");
863
+ const [category, setCategory] = (0, react.useState)(null);
731
864
  const [copiedId, setCopiedId] = (0, react.useState)(null);
732
865
  const copyCommand = (id, command) => {
733
866
  const mark = () => {
@@ -759,6 +892,33 @@ window.__ModuleLoader__.load({
759
892
  invalidLabel: t("settings.invalidNumber"),
760
893
  disabled
761
894
  };
895
+ const categoryCounts = (0, react.useMemo)(() => {
896
+ const counts = /* @__PURE__ */ new Map();
897
+ for (const plugin of plugins) if (plugin.category) counts.set(plugin.category, (counts.get(plugin.category) ?? 0) + 1);
898
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
899
+ }, [plugins]);
900
+ const visiblePlugins = (0, react.useMemo)(() => {
901
+ const needle = query.trim().toLowerCase();
902
+ return plugins.filter((plugin) => {
903
+ if (category !== null && plugin.category !== category) return false;
904
+ if (!needle) return true;
905
+ const categoryLabel = plugin.category ? t(CATEGORY_KEY[plugin.category]) : "";
906
+ return [
907
+ plugin.name,
908
+ plugin.nameEn,
909
+ plugin.author,
910
+ plugin.description,
911
+ plugin.descriptionEn,
912
+ plugin.npm ?? "",
913
+ categoryLabel
914
+ ].join(" ").toLowerCase().includes(needle);
915
+ });
916
+ }, [
917
+ plugins,
918
+ query,
919
+ category,
920
+ t
921
+ ]);
762
922
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PluginSettingsCard, {
763
923
  t,
764
924
  titleKey: "settings.title",
@@ -784,68 +944,147 @@ window.__ModuleLoader__.load({
784
944
  props.resetField("enabled");
785
945
  }
786
946
  }),
787
- visible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
788
- className: community_module_css_default.entries,
789
- children: plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
790
- className: community_module_css_default.empty,
791
- role: "status",
792
- children: t("empty")
793
- }) : plugins.map((plugin) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
794
- className: community_module_css_default.entry,
795
- children: [
796
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
797
- className: community_module_css_default.entryHead,
798
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
799
- className: community_module_css_default.entryName,
800
- title: plugin.name,
801
- children: plugin.name
802
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
803
- className: community_module_css_default.entryAuthor,
804
- title: plugin.author,
947
+ visible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
948
+ className: community_module_css_default.market,
949
+ children: [
950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
951
+ className: community_module_css_default.toolbar,
952
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
953
+ id: "settings-community-search",
954
+ className: community_module_css_default.search,
955
+ type: "search",
956
+ placeholder: t("search.placeholder"),
957
+ "aria-label": t("search.label"),
958
+ value: query,
959
+ disabled,
960
+ onChange: (event) => {
961
+ setQuery(event.target.value);
962
+ }
963
+ })
964
+ }),
965
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
966
+ className: community_module_css_default.filters,
967
+ role: "group",
968
+ "aria-label": t("filter.all"),
969
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
970
+ type: "button",
971
+ className: category === null ? `${community_module_css_default.pill} ${community_module_css_default.pillActive}` : community_module_css_default.pill,
972
+ "aria-pressed": category === null,
973
+ disabled,
974
+ onClick: () => {
975
+ setCategory(null);
976
+ },
977
+ children: [
978
+ t("filter.all"),
979
+ " ",
980
+ plugins.length
981
+ ]
982
+ }), categoryCounts.map(([id, count]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
983
+ type: "button",
984
+ className: category === id ? `${community_module_css_default.pill} ${community_module_css_default.pillActive}` : community_module_css_default.pill,
985
+ "aria-pressed": category === id,
986
+ disabled,
987
+ onClick: () => {
988
+ setCategory(category === id ? null : id);
989
+ },
990
+ children: [
991
+ t(CATEGORY_KEY[id]),
992
+ " ",
993
+ count
994
+ ]
995
+ }, id))]
996
+ }),
997
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
998
+ className: community_module_css_default.resultCount,
999
+ role: "status",
1000
+ children: t("result.count", {
1001
+ shown: visiblePlugins.length,
1002
+ total: plugins.length
1003
+ })
1004
+ }),
1005
+ plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1006
+ className: community_module_css_default.empty,
1007
+ role: "status",
1008
+ children: t("empty")
1009
+ }) : visiblePlugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1010
+ className: community_module_css_default.empty,
1011
+ role: "status",
1012
+ children: t("noMatch")
1013
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1014
+ className: community_module_css_default.grid,
1015
+ children: visiblePlugins.map((plugin) => {
1016
+ const command = installCommand(plugin);
1017
+ const copied = copiedId === plugin.id;
1018
+ const name = entryCopy(t, `name.${plugin.id}`, plugin.nameEn || plugin.name);
1019
+ const description = entryCopy(t, `desc.${plugin.id}`, plugin.descriptionEn ?? plugin.description ?? "");
1020
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1021
+ className: community_module_css_default.card,
805
1022
  children: [
806
- t("author"),
807
- ": ",
808
- plugin.author
1023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1024
+ className: community_module_css_default.cardHead,
1025
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1026
+ className: community_module_css_default.cardName,
1027
+ title: name,
1028
+ children: name
1029
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1030
+ className: plugin.npm ? `${community_module_css_default.badge} ${community_module_css_default.badgePublished}` : community_module_css_default.badge,
1031
+ title: plugin.npm ?? plugin.repo,
1032
+ children: plugin.npm ? t("badge.published") : t("badge.source")
1033
+ })]
1034
+ }),
1035
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1036
+ className: community_module_css_default.cardMeta,
1037
+ children: [
1038
+ plugin.category ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1039
+ className: community_module_css_default.cardCategory,
1040
+ children: t(CATEGORY_KEY[plugin.category])
1041
+ }) : null,
1042
+ plugin.category ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1043
+ className: community_module_css_default.cardDot,
1044
+ "aria-hidden": "true",
1045
+ children: "·"
1046
+ }) : null,
1047
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1048
+ className: community_module_css_default.cardAuthor,
1049
+ title: plugin.author,
1050
+ children: plugin.author
1051
+ })
1052
+ ]
1053
+ }),
1054
+ description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1055
+ className: community_module_css_default.cardDescription,
1056
+ children: description
1057
+ }) : null,
1058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1059
+ className: community_module_css_default.cardFooter,
1060
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1061
+ className: community_module_css_default.cardTop,
1062
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1063
+ className: community_module_css_default.cardLink,
1064
+ href: plugin.repo,
1065
+ target: "_blank",
1066
+ rel: "noreferrer",
1067
+ children: t("repository")
1068
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1069
+ type: "button",
1070
+ className: copied ? `${community_module_css_default.installButton} ${community_module_css_default.installButtonCopied}` : community_module_css_default.installButton,
1071
+ title: command,
1072
+ onClick: () => {
1073
+ copyCommand(plugin.id, command);
1074
+ },
1075
+ children: copied ? t("copied") : t("install")
1076
+ })]
1077
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1078
+ className: community_module_css_default.cardCommand,
1079
+ title: command,
1080
+ children: command
1081
+ })]
1082
+ })
809
1083
  ]
810
- })]
811
- }),
812
- plugin.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
813
- className: community_module_css_default.entryDescription,
814
- children: plugin.description
815
- }) : null,
816
- plugin.descriptionEn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
817
- className: community_module_css_default.entryDescriptionEn,
818
- children: plugin.descriptionEn
819
- }) : null,
820
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
821
- className: community_module_css_default.entryLinks,
822
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
823
- className: community_module_css_default.entryLink,
824
- href: plugin.repo,
825
- target: "_blank",
826
- rel: "noreferrer",
827
- children: t("repository")
828
- }), plugin.npm ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
829
- className: community_module_css_default.entryNpm,
830
- children: plugin.npm
831
- }) : null]
832
- }),
833
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
834
- className: community_module_css_default.entryInstall,
835
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
836
- className: community_module_css_default.entryCommand,
837
- children: installCommand(plugin)
838
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
839
- type: "button",
840
- className: community_module_css_default.copyButton,
841
- onClick: () => {
842
- copyCommand(plugin.id, installCommand(plugin));
843
- },
844
- children: copiedId === plugin.id ? t("copied") : t("copy")
845
- })]
1084
+ }, plugin.id);
846
1085
  })
847
- ]
848
- }, plugin.id))
1086
+ })
1087
+ ]
849
1088
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
850
1089
  className: community_module_css_default.off,
851
1090
  role: "status",
@@ -883,10 +1122,14 @@ window.__ModuleLoader__.load({
883
1122
  //#endregion
884
1123
  //#region src/client/locales.ts
885
1124
  /**
886
- * The community-plugins locale dictionaries for the index card.
1125
+ * The community-plugins locale dictionaries for the index card. Static copy
1126
+ * for the card chrome plus one localized name/description pair per index
1127
+ * entry (`name.<id>` / `desc.<id>`), so the card localizes data-driven text
1128
+ * through the same `t` the rest of the settings UI uses — the document
1129
+ * `<html lang>` attribute is not a reliable locale signal in the GUI.
887
1130
  */
888
- /** Simplified Chinese dictionary (the key-set source of truth). */
889
- const zh = {
1131
+ /** Simplified Chinese copy for the card chrome (the key-set source of truth). */
1132
+ const STATIC_ZH = {
890
1133
  "settings.title": "社区插件",
891
1134
  "settings.description": "社区贡献者开发与维护的插件,链接指向作者自己的仓库。",
892
1135
  "settings.enabled": "启用社区插件索引",
@@ -910,13 +1153,28 @@ window.__ModuleLoader__.load({
910
1153
  "repository": "仓库",
911
1154
  "copy": "复制",
912
1155
  "copied": "已复制",
1156
+ "install": "复制安装命令",
1157
+ "noMatch": "没有匹配的社区插件。",
1158
+ "search.placeholder": "搜索名称、作者或简介",
1159
+ "search.label": "搜索社区插件",
1160
+ "filter.all": "全部",
1161
+ "result.count": "显示 {shown} / {total} 个",
1162
+ "badge.published": "npm 已发布",
1163
+ "badge.source": "仓库安装",
1164
+ "category.ui": "界面与体验",
1165
+ "category.agent": "Agent 与自动化",
1166
+ "category.tools": "开发者工具",
1167
+ "category.knowledge": "记忆与知识",
1168
+ "category.integration": "集成与分享",
1169
+ "category.security": "安全与运维",
1170
+ "category.utility": "实用工具",
913
1171
  "installHint": "索引只登记、不安装代码。要运行某个插件,复制它的安装命令到终端执行;安装后,插件自带的开关与配置(若有)会出现在插件配置里。",
914
1172
  "empty": "暂无社区插件登记。",
915
1173
  "off": "社区插件索引已关闭。",
916
1174
  "notice": "条目由贡献者自行登记,与 dsh-web-ui 的发布内容无关;使用前请自行评估。"
917
1175
  };
918
- /** English dictionary, checked complete against the zh key set. */
919
- const en = {
1176
+ /** English copy for the card chrome, checked complete against the zh key set. */
1177
+ const STATIC_EN = {
920
1178
  "settings.title": "Community Plugins",
921
1179
  "settings.description": "Plugins developed and maintained by community contributors, linking to each author's own repository.",
922
1180
  "settings.enabled": "Enable the community plugin index",
@@ -940,11 +1198,39 @@ window.__ModuleLoader__.load({
940
1198
  "repository": "Repository",
941
1199
  "copy": "Copy",
942
1200
  "copied": "Copied",
1201
+ "install": "Copy install command",
1202
+ "noMatch": "No matching community plugin found.",
1203
+ "search.placeholder": "Search name, author or description",
1204
+ "search.label": "Search community plugins",
1205
+ "filter.all": "All",
1206
+ "result.count": "Showing {shown} / {total}",
1207
+ "badge.published": "Published on npm",
1208
+ "badge.source": "Install from repo",
1209
+ "category.ui": "UI & Experience",
1210
+ "category.agent": "Agents & Automation",
1211
+ "category.tools": "Developer Tools",
1212
+ "category.knowledge": "Memory & Knowledge",
1213
+ "category.integration": "Integration & Sharing",
1214
+ "category.security": "Security & Ops",
1215
+ "category.utility": "Utilities",
943
1216
  "installHint": "The index only registers entries, it never installs code. To run a plugin, copy its install command into a terminal; once installed, the plugin provides its own switch and config (if any) in the plugin configuration section.",
944
1217
  "empty": "No community plugins registered yet.",
945
1218
  "off": "The community plugin index is turned off.",
946
1219
  "notice": "Entries are contributed by their authors and are separate from dsh-web-ui releases; evaluate before use."
947
1220
  };
1221
+ /** Build the runtime dictionary: static copy plus one localized name/description pair per index entry. */
1222
+ function build(base, lang) {
1223
+ const dict = { ...base };
1224
+ for (const entry of COMMUNITY_PLUGINS) {
1225
+ dict[`name.${entry.id}`] = lang === "zh" ? entry.name : entry.nameEn;
1226
+ dict[`desc.${entry.id}`] = lang === "zh" ? entry.description ?? entry.descriptionEn ?? "" : entry.descriptionEn ?? entry.description ?? "";
1227
+ }
1228
+ return dict;
1229
+ }
1230
+ /** Simplified Chinese dictionary for the index card (chrome + per-entry copy). */
1231
+ const zh = build(STATIC_ZH, "zh");
1232
+ /** English dictionary, built from the same key set. */
1233
+ const en = build(STATIC_EN, "en");
948
1234
  //#endregion
949
1235
  //#region src/client/index.ts
950
1236
  /** Settings namespace the card's enable switch edits (the Host plugin registers it). */