import { MAX_COMMON, MAX_SECTION } from "../constants.ts"; import { normalizeSearch } from "../identity.ts"; import { sortItems } from "../state/usage.ts"; import type { IndexedItem, ResourceIndex } from "../types.ts"; export function escapeMarkdownInline(value: string | undefined): string { return (value ?? "").replace(/([\\`*_{}\[\]()#+\-.!|>])/g, "\\$1"); } function itemTitle(item: IndexedItem): string { return escapeMarkdownInline(item.displayName ?? item.name); } function formatItem(item: IndexedItem): string { const meta = item.sourceLabel ? [escapeMarkdownInline(item.sourceLabel)] : []; if (item.scope && item.scope !== "builtin") meta.push(escapeMarkdownInline(item.scope === "project" ? "项目" : item.scope === "user" ? "全局" : item.scope)); if ((item.useCount ?? 0) > 0) meta.push(`使用 ${item.useCount} 次`); if (item.pinned) meta.push("已固定"); const suffix = meta.length > 0 ? ` · ${meta.join(" · ")}` : ""; const desc = item.description ? ` — ${escapeMarkdownInline(item.description)}` : ""; return `- **${itemTitle(item)}**${desc}${suffix}`; } function selectTop(items: IndexedItem[], count: number): IndexedItem[] { return [...items].sort(sortItems).slice(0, count); } function renderSection(title: string, items: IndexedItem[], emptyText: string): string { if (items.length === 0) return [`## ${title}`, `- ${emptyText}`].join("\n"); return [`## ${title}`, items.map(formatItem).join("\n")].join("\n"); } export function filterIndex(index: ResourceIndex, query?: string): IndexedItem[] { const normalizedQuery = normalizeSearch(query); return [...index.commands, ...index.tools, ...index.mcp, ...index.packages].filter((item) => { if (!normalizedQuery) return true; return normalizeSearch([item.name, item.displayName, item.description, item.sourceLabel, item.packageName, item.sourceRef, item.kind].join(" ")).includes(normalizedQuery); }); } export function buildMarkdown(index: ResourceIndex, updatedAt: number | undefined, query?: string): string { const normalizedQuery = normalizeSearch(query); const allVisible = filterIndex(index, query); const commonCommands = selectTop(allVisible.filter((item) => item.kind === "command"), MAX_COMMON); const commonTools = selectTop(allVisible.filter((item) => item.kind === "tool"), MAX_COMMON); const commonMcp = selectTop(allVisible.filter((item) => item.kind === "mcp"), MAX_COMMON); const commonPackages = selectTop(allVisible.filter((item) => item.kind === "package"), MAX_COMMON); const commandItems = selectTop(allVisible.filter((item) => item.kind === "command"), MAX_SECTION); const toolItems = selectTop(allVisible.filter((item) => item.kind === "tool"), MAX_SECTION); const mcpItems = selectTop(allVisible.filter((item) => item.kind === "mcp"), MAX_SECTION); const packageItems = selectTop(allVisible.filter((item) => item.kind === "package"), MAX_SECTION); const gatewayItems = mcpItems.filter((item) => item.name === "mcp"); const serverItems = mcpItems.filter((item) => item.name !== "mcp"); const updated = updatedAt ? new Date(updatedAt).toLocaleString() : new Date().toLocaleString(); const title = normalizedQuery ? `动态帮助 · 搜索:${escapeMarkdownInline(query?.trim())}` : "动态帮助"; return [ `# ${title}`, `_更新时间:${updated}_`, "", `> 已加载:${index.commands.length} 条命令 · ${index.tools.length} 个工具 · ${index.mcp.length} 个 MCP 服务器 · ${index.packages.length} 个包`, "", renderSection("常用命令", commonCommands, "暂无"), "", renderSection("常用工具", commonTools, "暂无"), "", renderSection("常用 MCP 服务器", commonMcp, "暂无"), "", renderSection("常用包", commonPackages, "暂无"), "", "## 命令", commandItems.length > 0 ? commandItems.map(formatItem).join("\n") : "- 暂无", "", "## 工具", toolItems.length > 0 ? toolItems.map(formatItem).join("\n") : "- 暂无", "", "## MCP 服务器", "### 网关", gatewayItems.length > 0 ? gatewayItems.map(formatItem).join("\n") : "- 暂无", "", "### 服务器", serverItems.length > 0 ? serverItems.map(formatItem).join("\n") : "- 暂无", "", "## 包", packageItems.length > 0 ? packageItems.map(formatItem).join("\n") : "- 暂无", "", "## 操作", "- `/help search <词>` 搜索当前索引", "- `/help pin <词>` 固定匹配项", "- `/help unpin <词>` 取消固定", "- `/help refresh` 重新扫描并刷新使用统计", ].join("\n"); }