@assistant-ui/mcp-docs-server 0.1.36 → 0.1.39

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.
Files changed (246) hide show
  1. package/.docs/organized/code-examples/waterfall.md +8 -9
  2. package/.docs/organized/code-examples/with-a2a.md +16 -11
  3. package/.docs/organized/code-examples/with-ag-ui.md +16 -11
  4. package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +32 -21
  5. package/.docs/organized/code-examples/with-artifacts.md +17 -10
  6. package/.docs/organized/code-examples/with-assistant-transport.md +15 -8
  7. package/.docs/organized/code-examples/with-browser-extension.md +15 -8
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +17 -10
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +10 -9
  10. package/.docs/organized/code-examples/with-cloud.md +18 -13
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +17 -10
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +20 -13
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +20 -13
  14. package/.docs/organized/code-examples/with-eve.md +16 -9
  15. package/.docs/organized/code-examples/with-expo.md +26 -23
  16. package/.docs/organized/code-examples/with-external-store.md +16 -11
  17. package/.docs/organized/code-examples/with-ffmpeg.md +18 -13
  18. package/.docs/organized/code-examples/with-generative-ui.md +20 -15
  19. package/.docs/organized/code-examples/with-google-adk.md +16 -9
  20. package/.docs/organized/code-examples/with-heat-graph.md +8 -9
  21. package/.docs/organized/code-examples/with-image-generation.md +17 -10
  22. package/.docs/organized/code-examples/with-interactables.md +19 -15
  23. package/.docs/organized/code-examples/with-langchain.md +17 -10
  24. package/.docs/organized/code-examples/with-langgraph.md +17 -10
  25. package/.docs/organized/code-examples/with-livekit.md +21 -14
  26. package/.docs/organized/code-examples/with-mcp.md +40 -18
  27. package/.docs/organized/code-examples/with-opencode.md +22 -17
  28. package/.docs/organized/code-examples/with-pi.md +47 -12
  29. package/.docs/organized/code-examples/with-react-hook-form.md +19 -14
  30. package/.docs/organized/code-examples/with-react-ink-web.md +6 -6
  31. package/.docs/organized/code-examples/with-react-ink.md +2 -2
  32. package/.docs/organized/code-examples/with-react-router.md +20 -15
  33. package/.docs/organized/code-examples/with-resumable-stream.md +19 -12
  34. package/.docs/organized/code-examples/with-store.md +8 -9
  35. package/.docs/organized/code-examples/with-tanstack.md +17 -11
  36. package/.docs/organized/code-examples/with-tap-runtime.md +16 -11
  37. package/.docs/organized/code-examples/with-virtualized-thread.md +17 -12
  38. package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
  39. package/.docs/raw/docs/(docs)/cli.mdx +18 -2
  40. package/.docs/raw/docs/(docs)/installation.mdx +15 -1
  41. package/.docs/raw/docs/(docs)/rtl.mdx +2 -4
  42. package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +56 -0
  43. package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +86 -0
  44. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +19 -1
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +42 -0
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +53 -2
  47. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +81 -0
  48. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +86 -0
  49. package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +62 -0
  50. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
  51. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +4 -2
  52. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
  53. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
  54. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +29 -29
  55. package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +1 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +3 -0
  57. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +2 -0
  58. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -2
  59. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +12 -9
  60. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +4 -0
  61. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +7 -1
  62. package/.docs/raw/docs/cloud/langgraph.mdx +4 -2
  63. package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
  64. package/.docs/raw/docs/guides/dictation.mdx +185 -257
  65. package/.docs/raw/docs/guides/index.mdx +10 -0
  66. package/.docs/raw/docs/guides/mentions.mdx +31 -3
  67. package/.docs/raw/docs/guides/resumable-streams.mdx +12 -1
  68. package/.docs/raw/docs/guides/speech.mdx +47 -29
  69. package/.docs/raw/docs/guides/suggestions.mdx +70 -1
  70. package/.docs/raw/docs/guides/voice.mdx +197 -267
  71. package/.docs/raw/docs/ink/primitives.mdx +35 -1
  72. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +13 -7
  73. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +3 -3
  74. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +1 -1
  75. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +3 -3
  76. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  77. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  78. package/.docs/raw/docs/integrations/index.mdx +1 -1
  79. package/.docs/raw/docs/integrations/observability/helicone.mdx +1 -1
  80. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  81. package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
  82. package/.docs/raw/docs/migrations/index.mdx +50 -0
  83. package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +2 -2
  84. package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
  85. package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
  86. package/.docs/raw/docs/primitives/composer.mdx +16 -0
  87. package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
  88. package/.docs/raw/docs/primitives/thread-list.mdx +30 -6
  89. package/.docs/raw/docs/react-native/primitives.mdx +23 -0
  90. package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +1 -1
  91. package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
  92. package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +1 -1
  93. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +13 -2
  94. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +13 -4
  95. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +11 -11
  96. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
  97. package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +8 -6
  98. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +717 -0
  99. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +1 -1
  100. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +1 -1
  101. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
  102. package/.docs/raw/docs/runtimes/custom/external-store.mdx +1 -1
  103. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +2 -0
  104. package/.docs/raw/docs/runtimes/eve/overview.mdx +1 -1
  105. package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +1 -1
  106. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  107. package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +181 -0
  108. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +1 -1
  109. package/.docs/raw/docs/tools/backend.mdx +6 -3
  110. package/.docs/raw/docs/tools/defining-tools.mdx +12 -1
  111. package/.docs/raw/docs/tools/generative-ui.mdx +95 -0
  112. package/.docs/raw/docs/tools/mcp-apps.mdx +37 -10
  113. package/.docs/raw/docs/tools/mcp.mdx +105 -3
  114. package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
  115. package/.docs/raw/docs/tools/user-managed-mcp.mdx +41 -4
  116. package/.docs/raw/docs/ui/accordion.mdx +16 -10
  117. package/.docs/raw/docs/ui/assistant-modal.mdx +8 -4
  118. package/.docs/raw/docs/ui/attachment.mdx +5 -1
  119. package/.docs/raw/docs/ui/badge.mdx +23 -12
  120. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +80 -0
  121. package/.docs/raw/docs/ui/model-selector.mdx +69 -6
  122. package/.docs/raw/docs/ui/select.mdx +22 -14
  123. package/.docs/raw/docs/ui/sources.mdx +1 -1
  124. package/.docs/raw/docs/ui/tabs.mdx +25 -14
  125. package/.docs/raw/docs/utilities/heat-graph.mdx +2 -2
  126. package/README.md +1 -1
  127. package/dist/constants.d.ts +2 -1
  128. package/dist/constants.d.ts.map +1 -1
  129. package/dist/constants.js +2 -1
  130. package/dist/constants.js.map +1 -1
  131. package/dist/index.d.ts +0 -1
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +51 -0
  134. package/dist/index.js.map +1 -1
  135. package/dist/prepare-docs/code-examples.d.ts.map +1 -1
  136. package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
  137. package/dist/prepare-docs/copy-raw.js +0 -4
  138. package/dist/prepare-docs/copy-raw.js.map +1 -1
  139. package/dist/prompts/xulux-playground.d.ts +12 -0
  140. package/dist/prompts/xulux-playground.d.ts.map +1 -0
  141. package/dist/prompts/xulux-playground.js +33 -0
  142. package/dist/prompts/xulux-playground.js.map +1 -0
  143. package/dist/tools/docs.d.ts +2 -4
  144. package/dist/tools/docs.d.ts.map +1 -1
  145. package/dist/tools/docs.js +38 -10
  146. package/dist/tools/docs.js.map +1 -1
  147. package/dist/tools/examples.d.ts +5 -5
  148. package/dist/tools/examples.d.ts.map +1 -1
  149. package/dist/tools/examples.js +10 -7
  150. package/dist/tools/examples.js.map +1 -1
  151. package/dist/tools/resources.d.ts +6 -0
  152. package/dist/tools/resources.d.ts.map +1 -0
  153. package/dist/tools/resources.js +74 -0
  154. package/dist/tools/resources.js.map +1 -0
  155. package/dist/tools/search.d.ts +30 -0
  156. package/dist/tools/search.d.ts.map +1 -0
  157. package/dist/tools/search.js +39 -0
  158. package/dist/tools/search.js.map +1 -0
  159. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  160. package/dist/tools/tests/test-setup.js +7 -1
  161. package/dist/tools/tests/test-setup.js.map +1 -1
  162. package/dist/tools/xulux-templates.d.ts +72 -0
  163. package/dist/tools/xulux-templates.d.ts.map +1 -0
  164. package/dist/tools/xulux-templates.js +82 -0
  165. package/dist/tools/xulux-templates.js.map +1 -0
  166. package/dist/utils/cache.d.ts +5 -0
  167. package/dist/utils/cache.d.ts.map +1 -0
  168. package/dist/utils/cache.js +18 -0
  169. package/dist/utils/cache.js.map +1 -0
  170. package/dist/utils/logger.d.ts.map +1 -1
  171. package/dist/utils/mcp-format.d.ts +1 -0
  172. package/dist/utils/mcp-format.d.ts.map +1 -1
  173. package/dist/utils/mcp-format.js +7 -4
  174. package/dist/utils/mcp-format.js.map +1 -1
  175. package/dist/utils/mdx.d.ts +2 -1
  176. package/dist/utils/mdx.d.ts.map +1 -1
  177. package/dist/utils/mdx.js +19 -2
  178. package/dist/utils/mdx.js.map +1 -1
  179. package/dist/utils/paths.d.ts +2 -1
  180. package/dist/utils/paths.d.ts.map +1 -1
  181. package/dist/utils/paths.js +20 -1
  182. package/dist/utils/paths.js.map +1 -1
  183. package/dist/utils/search.d.ts +10 -0
  184. package/dist/utils/search.d.ts.map +1 -0
  185. package/dist/utils/search.js +97 -0
  186. package/dist/utils/search.js.map +1 -0
  187. package/dist/utils/security.d.ts.map +1 -1
  188. package/dist/xulux/catalog-client.d.ts +14 -0
  189. package/dist/xulux/catalog-client.d.ts.map +1 -0
  190. package/dist/xulux/catalog-client.js +67 -0
  191. package/dist/xulux/catalog-client.js.map +1 -0
  192. package/dist/xulux/fallback-catalog.d.ts +7 -0
  193. package/dist/xulux/fallback-catalog.d.ts.map +1 -0
  194. package/dist/xulux/fallback-catalog.js +47 -0
  195. package/dist/xulux/fallback-catalog.js.map +1 -0
  196. package/dist/xulux/fetch-sandbox.d.ts +5 -0
  197. package/dist/xulux/fetch-sandbox.d.ts.map +1 -0
  198. package/dist/xulux/fetch-sandbox.js +40 -0
  199. package/dist/xulux/fetch-sandbox.js.map +1 -0
  200. package/dist/xulux/template-service.d.ts +84 -0
  201. package/dist/xulux/template-service.d.ts.map +1 -0
  202. package/dist/xulux/template-service.js +223 -0
  203. package/dist/xulux/template-service.js.map +1 -0
  204. package/dist/xulux/types.d.ts +55 -0
  205. package/dist/xulux/types.d.ts.map +1 -0
  206. package/dist/xulux/types.js +6 -0
  207. package/dist/xulux/types.js.map +1 -0
  208. package/package.json +5 -5
  209. package/src/constants.ts +2 -0
  210. package/src/index.ts +67 -0
  211. package/src/prepare-docs/copy-raw.ts +0 -5
  212. package/src/prompts/xulux-playground.ts +36 -0
  213. package/src/tools/docs.ts +52 -4
  214. package/src/tools/examples.ts +18 -11
  215. package/src/tools/resources.ts +114 -0
  216. package/src/tools/search.ts +46 -0
  217. package/src/tools/tests/completions.test.ts +46 -0
  218. package/src/tools/tests/directory-size-cap.test.ts +50 -0
  219. package/src/tools/tests/docs.test.ts +20 -0
  220. package/src/tools/tests/examples.test.ts +5 -5
  221. package/src/tools/tests/listings-cache.test.ts +19 -0
  222. package/src/tools/tests/mcp-protocol.test.ts +92 -1
  223. package/src/tools/tests/resources.test.ts +102 -0
  224. package/src/tools/tests/search.test.ts +37 -0
  225. package/src/tools/tests/test-setup.ts +10 -0
  226. package/src/tools/tests/xulux-templates.test.ts +262 -0
  227. package/src/tools/xulux-templates.ts +141 -0
  228. package/src/utils/cache.ts +20 -0
  229. package/src/utils/mcp-format.ts +8 -6
  230. package/src/utils/mdx.ts +21 -1
  231. package/src/utils/paths.ts +25 -0
  232. package/src/utils/search.ts +131 -0
  233. package/src/utils/tests/cache.test.ts +51 -0
  234. package/src/utils/tests/mcp-format.test.ts +22 -0
  235. package/src/utils/tests/security.test.ts +1 -1
  236. package/src/xulux/catalog-client.ts +105 -0
  237. package/src/xulux/fallback-catalog.ts +63 -0
  238. package/src/xulux/fetch-sandbox.ts +56 -0
  239. package/src/xulux/template-service.ts +406 -0
  240. package/src/xulux/types.ts +60 -0
  241. package/.docs/raw/blog/2024-07-29-hello/index.mdx +0 -64
  242. package/.docs/raw/blog/2024-09-11/index.mdx +0 -10
  243. package/.docs/raw/blog/2024-12-15/index.mdx +0 -10
  244. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +0 -127
  245. package/.docs/raw/blog/2026-03-launch-week/index.mdx +0 -258
  246. package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +0 -464
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xulux-templates.js","names":[],"sources":["../../src/tools/xulux-templates.ts"],"sourcesContent":["import { z } from \"zod/v3\";\nimport { logger } from \"../utils/logger.js\";\nimport { formatMCPResponse } from \"../utils/mcp-format.js\";\nimport { getXuluxCatalog } from \"../xulux/catalog-client.js\";\nimport {\n createTemplatePreview,\n getTemplateDetails,\n listTemplates,\n} from \"../xulux/template-service.js\";\n\nconst emptyInputSchema = z.object({});\n\nconst detailsInputSchema = z.object({\n templateId: z.string().describe(\"The template id from assistantUITemplates\"),\n versionId: z\n .string()\n .optional()\n .describe(\n \"Optional version id. When provided, exampleConfig reflects that version's resolved defaults.\",\n ),\n});\n\nconst previewInputSchema = z.object({\n templateId: z.string().describe(\"The template id from assistantUITemplates\"),\n versionId: z\n .string()\n .optional()\n .describe(\"Version id to use. Uses the template default if omitted.\"),\n config: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n \"Customization config for the preview. Must contain only the top-level keys: hostUi, assistant, and brandTheme. \" +\n \"Use the schemas from assistantUITemplateDetails.configRoots as the source of truth for each root. \" +\n \"Do not pass any other root keys.\",\n ),\n});\n\nfunction withDegradedNote<T extends Record<string, unknown>>(\n payload: T,\n degraded: boolean,\n degradedReason?: string,\n): T & { catalogDegraded?: true; catalogDegradedReason?: string } {\n if (!degraded) return payload;\n return {\n ...payload,\n catalogDegraded: true,\n ...(degradedReason ? { catalogDegradedReason: degradedReason } : {}),\n };\n}\n\nexport const xuluxTemplatesListTool = {\n name: \"assistantUITemplates\",\n description:\n \"List the available assistant-ui hosted app templates and fixed demos with their features, customizable surfaces, and versions. \" +\n \"Call this first for any assistant-ui app-building request. Use the features and customizable fields to decide which template fits. \" +\n \"If customizable is empty, the entry is a fixed demo that should be used as-is rather than configured. \" +\n \"Call assistantUITemplateDetails on the chosen template before requesting a preview.\",\n parameters: emptyInputSchema.shape,\n execute: async (_args: z.infer<typeof emptyInputSchema>) => {\n logger.info(\"Listing assistant-ui templates\");\n try {\n const { catalog, degraded, degradedReason } = await getXuluxCatalog();\n return formatMCPResponse(\n withDegradedNote(listTemplates(catalog), degraded, degradedReason),\n );\n } catch (error) {\n logger.error(\"Failed to list assistant-ui templates\", error);\n return formatMCPResponse({\n error: \"Failed to list assistant-ui templates\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n },\n};\n\nexport const xuluxTemplateDetailsTool = {\n name: \"assistantUITemplateDetails\",\n description:\n \"Get the full authoring surface for a specific assistant-ui hosted template. \" +\n \"Returns configRoots schemas (with types, defaults, and enums), rules, built-in tool input/outputShape, renderer contracts, and an exampleConfig. \" +\n \"Fixed demos return no configRoots; use those as-is without config. \" +\n \"Use this before calling assistantUITemplatePreview to understand exactly what config to write. \" +\n \"If assistantUITemplatePreview returns validationWarnings, call this again and cross-reference configRoots to correct the config.\",\n parameters: detailsInputSchema.shape,\n execute: async (args: z.infer<typeof detailsInputSchema>) => {\n logger.info(\n `Getting assistant-ui template details for: ${args.templateId}`,\n );\n try {\n const { catalog, degraded, degradedReason } = await getXuluxCatalog();\n const details = await getTemplateDetails(catalog, args);\n return formatMCPResponse(\n withDegradedNote(\n details as unknown as Record<string, unknown>,\n degraded,\n degradedReason,\n ),\n );\n } catch (error) {\n logger.error(\"Failed to get assistant-ui template details\", error);\n return formatMCPResponse({\n error: \"Failed to get assistant-ui template details\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n },\n};\n\nexport const xuluxTemplatePreviewTool = {\n name: \"assistantUITemplatePreview\",\n description:\n \"Return preview and download URLs for a selected assistant-ui hosted template. \" +\n \"The previewUrl is a hosted app URL; show it to the user first, or open it with an available browser/in-app-browser tool if your client provides one. \" +\n \"If you pass config, a preview session is created on the hosted template sandbox and the returned URLs reflect that configuration. \" +\n \"Do not pass config for fixed demos that have no configRoots in assistantUITemplateDetails. \" +\n \"This tool only returns URLs.\",\n parameters: previewInputSchema.shape,\n execute: async (args: z.infer<typeof previewInputSchema>) => {\n logger.info(\n `Creating assistant-ui template preview for: ${args.templateId}`,\n );\n try {\n const { catalog, degraded, degradedReason } = await getXuluxCatalog();\n const result = await createTemplatePreview(catalog, args);\n return formatMCPResponse(\n withDegradedNote(\n result as unknown as Record<string, unknown>,\n degraded,\n degradedReason,\n ),\n );\n } catch (error) {\n logger.error(\"Failed to create assistant-ui template preview\", error);\n return formatMCPResponse({\n error: \"Failed to create assistant-ui template preview\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n },\n};\n"],"mappings":";;;;;;AAUA,MAAM,mBAAmB,EAAE,OAAO,CAAC,CAAC;AAEpC,MAAM,qBAAqB,EAAE,OAAO;CAClC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS,2CAA2C;CAC3E,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,8FACF;AACJ,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;CAClC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS,2CAA2C;CAC3E,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SAAS,0DAA0D;CACtE,QAAQ,EACL,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAC/B,SAAS,CAAC,CACV,SACC,mPAGF;AACJ,CAAC;AAED,SAAS,iBACP,SACA,UACA,gBACgE;CAChE,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO;EACL,GAAG;EACH,iBAAiB;EACjB,GAAI,iBAAiB,EAAE,uBAAuB,eAAe,IAAI,CAAC;CACpE;AACF;AAEA,MAAa,yBAAyB;CACpC,MAAM;CACN,aACE;CAIF,YAAY,iBAAiB;CAC7B,SAAS,OAAO,UAA4C;EAC1D,OAAO,KAAK,gCAAgC;EAC5C,IAAI;GACF,MAAM,EAAE,SAAS,UAAU,mBAAmB,MAAM,gBAAgB;GACpE,OAAO,kBACL,iBAAiB,cAAc,OAAO,GAAG,UAAU,cAAc,CACnE;EACF,SAAS,OAAO;GACd,OAAO,MAAM,yCAAyC,KAAK;GAC3D,OAAO,kBAAkB;IACvB,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAChE,CAAC;EACH;CACF;AACF;AAEA,MAAa,2BAA2B;CACtC,MAAM;CACN,aACE;CAKF,YAAY,mBAAmB;CAC/B,SAAS,OAAO,SAA6C;EAC3D,OAAO,KACL,8CAA8C,KAAK,YACrD;EACA,IAAI;GACF,MAAM,EAAE,SAAS,UAAU,mBAAmB,MAAM,gBAAgB;GAEpE,OAAO,kBACL,iBACE,MAHkB,mBAAmB,SAAS,IAAI,GAIlD,UACA,cACF,CACF;EACF,SAAS,OAAO;GACd,OAAO,MAAM,+CAA+C,KAAK;GACjE,OAAO,kBAAkB;IACvB,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAChE,CAAC;EACH;CACF;AACF;AAEA,MAAa,2BAA2B;CACtC,MAAM;CACN,aACE;CAKF,YAAY,mBAAmB;CAC/B,SAAS,OAAO,SAA6C;EAC3D,OAAO,KACL,+CAA+C,KAAK,YACtD;EACA,IAAI;GACF,MAAM,EAAE,SAAS,UAAU,mBAAmB,MAAM,gBAAgB;GAEpE,OAAO,kBACL,iBACE,MAHiB,sBAAsB,SAAS,IAAI,GAIpD,UACA,cACF,CACF;EACF,SAAS,OAAO;GACd,OAAO,MAAM,kDAAkD,KAAK;GACpE,OAAO,kBAAkB;IACvB,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAChE,CAAC;EACH;CACF;AACF"}
@@ -0,0 +1,5 @@
1
+ //#region src/utils/cache.d.ts
2
+ declare function cacheListing(scan: () => Promise<string[]>): () => Promise<string[]>;
3
+ //#endregion
4
+ export { cacheListing };
5
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","names":[],"sources":["../../src/utils/cache.ts"],"mappings":";iBAAgB,aACd,YAAY,0BACL"}
@@ -0,0 +1,18 @@
1
+ //#region src/utils/cache.ts
2
+ function cacheListing(scan) {
3
+ let cache = null;
4
+ return () => {
5
+ if (!cache) cache = scan().then((result) => {
6
+ if (result.length === 0) cache = null;
7
+ return result;
8
+ }, (error) => {
9
+ cache = null;
10
+ throw error;
11
+ });
12
+ return cache;
13
+ };
14
+ }
15
+ //#endregion
16
+ export { cacheListing };
17
+
18
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","names":[],"sources":["../../src/utils/cache.ts"],"sourcesContent":["export function cacheListing(\n scan: () => Promise<string[]>,\n): () => Promise<string[]> {\n let cache: Promise<string[]> | null = null;\n return () => {\n if (!cache) {\n cache = scan().then(\n (result) => {\n if (result.length === 0) cache = null;\n return result;\n },\n (error) => {\n cache = null;\n throw error;\n },\n );\n }\n return cache;\n };\n}\n"],"mappings":";AAAA,SAAgB,aACd,MACyB;CACzB,IAAI,QAAkC;CACtC,aAAa;EACX,IAAI,CAAC,OACH,QAAQ,KAAK,CAAC,CAAC,MACZ,WAAW;GACV,IAAI,OAAO,WAAW,GAAG,QAAQ;GACjC,OAAO;EACT,IACC,UAAU;GACT,QAAQ;GACR,MAAM;EACR,CACF;EAEF,OAAO;CACT;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","names":[],"sources":["../../src/utils/logger.ts"],"mappings":";cAEa,MAAA;8BACY,IAAA;6BAKD,IAAA;8BAKC,IAAA;6BAGD,IAAA;AAAA"}
1
+ {"version":3,"file":"logger.d.ts","names":[],"sources":["../../src/utils/logger.ts"],"mappings":";cAEa;8BACY;6BAKD;8BAKC;6BAGD"}
@@ -4,6 +4,7 @@ declare function formatMCPResponse(data: any): {
4
4
  type: "text";
5
5
  text: string;
6
6
  }>;
7
+ structuredContent?: Record<string, unknown>;
7
8
  };
8
9
  //#endregion
9
10
  export { formatMCPResponse };
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-format.d.ts","names":[],"sources":["../../src/utils/mcp-format.ts"],"mappings":";iBAAgB,iBAAA,CAAkB,IAAA;EAChC,OAAA,EAAS,KAAK;IAAG,IAAA;IAAc,IAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"mcp-format.d.ts","names":[],"sources":["../../src/utils/mcp-format.ts"],"mappings":";iBAAgB,kBAAkB;EAChC,SAAS;IAAQ;IAAc;;EAC/B,oBAAoB"}
@@ -1,9 +1,12 @@
1
1
  //#region src/utils/mcp-format.ts
2
2
  function formatMCPResponse(data) {
3
- return { content: [{
4
- type: "text",
5
- text: typeof data === "string" ? data : JSON.stringify(data, null, 2)
6
- }] };
3
+ return {
4
+ content: [{
5
+ type: "text",
6
+ text: typeof data === "string" ? data : JSON.stringify(data, null, 2)
7
+ }],
8
+ ...typeof data === "object" && data !== null && !Array.isArray(data) && { structuredContent: data }
9
+ };
7
10
  }
8
11
  //#endregion
9
12
  export { formatMCPResponse };
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-format.js","names":[],"sources":["../../src/utils/mcp-format.ts"],"sourcesContent":["export function formatMCPResponse(data: any): {\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n content: [\n {\n type: \"text\",\n text: typeof data === \"string\" ? data : JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n"],"mappings":";AAAA,SAAgB,kBAAkB,MAEhC;CACA,OAAO,EACL,SAAS,CACP;EACE,MAAM;EACN,MAAM,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;CACtE,CACF,EACF;AACF"}
1
+ {"version":3,"file":"mcp-format.js","names":[],"sources":["../../src/utils/mcp-format.ts"],"sourcesContent":["export function formatMCPResponse(data: any): {\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent?: Record<string, unknown>;\n} {\n const text = typeof data === \"string\" ? data : JSON.stringify(data, null, 2);\n const isStructured =\n typeof data === \"object\" && data !== null && !Array.isArray(data);\n return {\n content: [{ type: \"text\", text }],\n ...(isStructured && {\n structuredContent: data as Record<string, unknown>,\n }),\n };\n}\n"],"mappings":";AAAA,SAAgB,kBAAkB,MAGhC;CAIA,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAJf,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;EAI1C,CAAC;EAChC,GAHA,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI,KAG5C,EAClB,mBAAmB,KACrB;CACF;AACF"}
@@ -5,7 +5,8 @@ interface MDXContent {
5
5
  excerpt?: string;
6
6
  }
7
7
  declare function readMDXFile(filePath: string): Promise<MDXContent | null>;
8
+ declare function readMDXFileSafe(filePath: string): Promise<MDXContent | null>;
8
9
  declare function formatMDXContent(mdxContent: MDXContent): string;
9
10
  //#endregion
10
- export { formatMDXContent, readMDXFile };
11
+ export { formatMDXContent, readMDXFile, readMDXFileSafe };
11
12
  //# sourceMappingURL=mdx.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mdx.d.ts","names":[],"sources":["../../src/utils/mdx.ts"],"mappings":";UAIU,UAAA;EACR,OAAA;EACA,WAAA,EAAa,MAAM;EACnB,OAAA;AAAA;AAAA,iBAGoB,WAAA,CACpB,QAAA,WACC,OAAO,CAAC,UAAA;AAAA,iBAsBK,gBAAA,CAAiB,UAAsB,EAAV,UAAU"}
1
+ {"version":3,"file":"mdx.d.ts","names":[],"sources":["../../src/utils/mdx.ts"],"mappings":";UAKU;EACR;EACA,aAAa;EACb;;iBAGoB,YACpB,mBACC,QAAQ;iBAsBW,gBACpB,mBACC,QAAQ;iBAiBK,iBAAiB,YAAY"}
package/dist/utils/mdx.js CHANGED
@@ -1,5 +1,6 @@
1
+ import "../constants.js";
1
2
  import { logger } from "./logger.js";
2
- import { readFile } from "node:fs/promises";
3
+ import { lstat, readFile } from "node:fs/promises";
3
4
  import matter from "gray-matter";
4
5
  //#region src/utils/mdx.ts
5
6
  async function readMDXFile(filePath) {
@@ -17,11 +18,27 @@ async function readMDXFile(filePath) {
17
18
  return null;
18
19
  }
19
20
  }
21
+ async function readMDXFileSafe(filePath) {
22
+ try {
23
+ const stats = await lstat(filePath);
24
+ if (stats.isSymbolicLink()) {
25
+ logger.warn(`Attempted to read symlink: ${filePath}`);
26
+ return null;
27
+ }
28
+ if (stats.size > 10485760) {
29
+ logger.warn(`File size exceeds limit: ${filePath} (${stats.size} bytes)`);
30
+ return null;
31
+ }
32
+ } catch {
33
+ return null;
34
+ }
35
+ return readMDXFile(filePath);
36
+ }
20
37
  function formatMDXContent(mdxContent) {
21
38
  const { content, frontmatter } = mdxContent;
22
39
  return matter.stringify(content, frontmatter);
23
40
  }
24
41
  //#endregion
25
- export { formatMDXContent, readMDXFile };
42
+ export { formatMDXContent, readMDXFile, readMDXFileSafe };
26
43
 
27
44
  //# sourceMappingURL=mdx.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mdx.js","names":[],"sources":["../../src/utils/mdx.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport matter from \"gray-matter\";\nimport { logger } from \"./logger.js\";\n\ninterface MDXContent {\n content: string;\n frontmatter: Record<string, any>;\n excerpt?: string;\n}\n\nexport async function readMDXFile(\n filePath: string,\n): Promise<MDXContent | null> {\n try {\n const fileContent = await readFile(filePath, \"utf-8\");\n const { content, data } = matter(fileContent);\n\n const excerptMatch = content.match(/^(.+?)(?:\\n\\n|$)/);\n const excerpt =\n excerptMatch?.[1] !== undefined\n ? excerptMatch[1].replace(/^#+ /, \"\")\n : undefined;\n\n return {\n content,\n frontmatter: data,\n ...(excerpt !== undefined && { excerpt }),\n };\n } catch (error) {\n logger.error(`Failed to read MDX file: ${filePath}`, error);\n return null;\n }\n}\n\nexport function formatMDXContent(mdxContent: MDXContent): string {\n const { content, frontmatter } = mdxContent;\n\n return matter.stringify(content, frontmatter);\n}\n"],"mappings":";;;;AAUA,eAAsB,YACpB,UAC4B;CAC5B,IAAI;EAEF,MAAM,EAAE,SAAS,SAAS,OAAO,MADP,SAAS,UAAU,OAAO,CACR;EAE5C,MAAM,eAAe,QAAQ,MAAM,kBAAkB;EACrD,MAAM,UACJ,eAAe,OAAO,KAAA,IAClB,aAAa,EAAE,CAAC,QAAQ,QAAQ,EAAE,IAClC,KAAA;EAEN,OAAO;GACL;GACA,aAAa;GACb,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;EACzC;CACF,SAAS,OAAO;EACd,OAAO,MAAM,4BAA4B,YAAY,KAAK;EAC1D,OAAO;CACT;AACF;AAEA,SAAgB,iBAAiB,YAAgC;CAC/D,MAAM,EAAE,SAAS,gBAAgB;CAEjC,OAAO,OAAO,UAAU,SAAS,WAAW;AAC9C"}
1
+ {"version":3,"file":"mdx.js","names":[],"sources":["../../src/utils/mdx.ts"],"sourcesContent":["import { readFile, lstat } from \"node:fs/promises\";\nimport matter from \"gray-matter\";\nimport { MAX_FILE_SIZE } from \"../constants.js\";\nimport { logger } from \"./logger.js\";\n\ninterface MDXContent {\n content: string;\n frontmatter: Record<string, any>;\n excerpt?: string;\n}\n\nexport async function readMDXFile(\n filePath: string,\n): Promise<MDXContent | null> {\n try {\n const fileContent = await readFile(filePath, \"utf-8\");\n const { content, data } = matter(fileContent);\n\n const excerptMatch = content.match(/^(.+?)(?:\\n\\n|$)/);\n const excerpt =\n excerptMatch?.[1] !== undefined\n ? excerptMatch[1].replace(/^#+ /, \"\")\n : undefined;\n\n return {\n content,\n frontmatter: data,\n ...(excerpt !== undefined && { excerpt }),\n };\n } catch (error) {\n logger.error(`Failed to read MDX file: ${filePath}`, error);\n return null;\n }\n}\n\nexport async function readMDXFileSafe(\n filePath: string,\n): Promise<MDXContent | null> {\n try {\n const stats = await lstat(filePath);\n if (stats.isSymbolicLink()) {\n logger.warn(`Attempted to read symlink: ${filePath}`);\n return null;\n }\n if (stats.size > MAX_FILE_SIZE) {\n logger.warn(`File size exceeds limit: ${filePath} (${stats.size} bytes)`);\n return null;\n }\n } catch {\n return null;\n }\n return readMDXFile(filePath);\n}\n\nexport function formatMDXContent(mdxContent: MDXContent): string {\n const { content, frontmatter } = mdxContent;\n\n return matter.stringify(content, frontmatter);\n}\n"],"mappings":";;;;;AAWA,eAAsB,YACpB,UAC4B;CAC5B,IAAI;EAEF,MAAM,EAAE,SAAS,SAAS,OAAO,MADP,SAAS,UAAU,OAAO,CACR;EAE5C,MAAM,eAAe,QAAQ,MAAM,kBAAkB;EACrD,MAAM,UACJ,eAAe,OAAO,KAAA,IAClB,aAAa,EAAE,CAAC,QAAQ,QAAQ,EAAE,IAClC,KAAA;EAEN,OAAO;GACL;GACA,aAAa;GACb,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;EACzC;CACF,SAAS,OAAO;EACd,OAAO,MAAM,4BAA4B,YAAY,KAAK;EAC1D,OAAO;CACT;AACF;AAEA,eAAsB,gBACpB,UAC4B;CAC5B,IAAI;EACF,MAAM,QAAQ,MAAM,MAAM,QAAQ;EAClC,IAAI,MAAM,eAAe,GAAG;GAC1B,OAAO,KAAK,8BAA8B,UAAU;GACpD,OAAO;EACT;EACA,IAAI,MAAM,OAAA,UAAsB;GAC9B,OAAO,KAAK,4BAA4B,SAAS,IAAI,MAAM,KAAK,QAAQ;GACxE,OAAO;EACT;CACF,QAAQ;EACN,OAAO;CACT;CACA,OAAO,YAAY,QAAQ;AAC7B;AAEA,SAAgB,iBAAiB,YAAgC;CAC/D,MAAM,EAAE,SAAS,gBAAgB;CAEjC,OAAO,OAAO,UAAU,SAAS,WAAW;AAC9C"}
@@ -4,8 +4,9 @@ declare function listDirContents(dirPath: string): Promise<{
4
4
  files: string[];
5
5
  }>;
6
6
  declare function getAvailablePaths(): Promise<string[]>;
7
+ declare const getAvailableDocFiles: () => Promise<string[]>;
7
8
  declare function findNearestPaths(requestedPath: string, availablePaths: string[]): string[];
8
9
  declare function pathExists(path: string): Promise<boolean>;
9
10
  //#endregion
10
- export { findNearestPaths, getAvailablePaths, listDirContents, pathExists };
11
+ export { findNearestPaths, getAvailableDocFiles, getAvailablePaths, listDirContents, pathExists };
11
12
  //# sourceMappingURL=paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","names":[],"sources":["../../src/utils/paths.ts"],"mappings":";iBAYsB,eAAA,CAAgB,OAAA,WAAkB,OAAO;EAC7D,WAAA;EACA,KAAA;AAAA;AAAA,iBA4BoB,iBAAA,IAAqB,OAAO;AAAA,iBA0BlC,gBAAA,CACd,aAAA,UACA,cAAwB;AAAA,iBAoCJ,UAAA,CAAW,IAAA,WAAe,OAAO"}
1
+ {"version":3,"file":"paths.d.ts","names":[],"sources":["../../src/utils/paths.ts"],"mappings":";iBAasB,gBAAgB,kBAAkB;EACtD;EACA;;iBA4BoB,qBAAqB;cA0B9B,4BAAoB;iBAwBjB,iBACd,uBACA;iBAoCoB,WAAW,eAAe"}
@@ -1,5 +1,6 @@
1
1
  import { DOCS_PATH, MDX_EXTENSION } from "../constants.js";
2
2
  import { logger } from "./logger.js";
3
+ import { cacheListing } from "./cache.js";
3
4
  import { extname, join } from "node:path";
4
5
  import { readdir, stat } from "node:fs/promises";
5
6
  //#region src/utils/paths.ts
@@ -42,6 +43,24 @@ async function getAvailablePaths() {
42
43
  await scanDirectory(DOCS_PATH);
43
44
  return paths.sort();
44
45
  }
46
+ const getAvailableDocFiles = cacheListing(scanDocFiles);
47
+ async function scanDocFiles() {
48
+ const files = [];
49
+ async function scanDirectory(dir, prefix = "") {
50
+ const { directories, files: dirFiles } = await listDirContents(dir);
51
+ for (const file of dirFiles) {
52
+ if (extname(file) !== ".mdx") continue;
53
+ const name = file.replace(MDX_EXTENSION, "");
54
+ files.push(prefix ? `${prefix}/${name}` : name);
55
+ }
56
+ for (const subdir of directories) {
57
+ const newPrefix = prefix ? `${prefix}/${subdir}` : subdir;
58
+ await scanDirectory(join(dir, subdir), newPrefix);
59
+ }
60
+ }
61
+ await scanDirectory(DOCS_PATH);
62
+ return files.sort();
63
+ }
45
64
  function findNearestPaths(requestedPath, availablePaths) {
46
65
  const normalizedRequest = requestedPath.toLowerCase().replace(/[^a-z0-9]/g, "");
47
66
  return availablePaths.map((path) => {
@@ -69,6 +88,6 @@ async function pathExists(path) {
69
88
  }
70
89
  }
71
90
  //#endregion
72
- export { findNearestPaths, getAvailablePaths, listDirContents, pathExists };
91
+ export { findNearestPaths, getAvailableDocFiles, getAvailablePaths, listDirContents, pathExists };
73
92
 
74
93
  //# sourceMappingURL=paths.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","names":[],"sources":["../../src/utils/paths.ts"],"sourcesContent":["import { readdir, stat } from \"node:fs/promises\";\nimport { join, extname } from \"node:path\";\nimport { DOCS_PATH, MDX_EXTENSION, MD_EXTENSION } from \"../constants.js\";\nimport { logger } from \"./logger.js\";\n\nconst SIMILARITY_THRESHOLDS = {\n EXACT_MATCH: 1,\n CONTAINS_MATCH: 0.8,\n PARTIAL_MATCH: 0.5,\n MIN_SUGGESTION: 0.3,\n} as const;\n\nexport async function listDirContents(dirPath: string): Promise<{\n directories: string[];\n files: string[];\n}> {\n try {\n const items = await readdir(dirPath, { withFileTypes: true });\n\n const directories = items\n .filter((item) => item.isDirectory())\n .map((item) => item.name)\n .filter((name) => !name.startsWith(\".\"))\n .sort();\n\n const files = items\n .filter(\n (item) =>\n item.isFile() &&\n (extname(item.name) === MDX_EXTENSION ||\n extname(item.name) === MD_EXTENSION),\n )\n .map((item) => item.name)\n .sort();\n\n return { directories, files };\n } catch (error) {\n logger.error(`Failed to list directory contents: ${dirPath}`, error);\n return { directories: [], files: [] };\n }\n}\n\nexport async function getAvailablePaths(): Promise<string[]> {\n const paths: string[] = [];\n\n async function scanDirectory(\n dir: string,\n prefix: string = \"\",\n ): Promise<void> {\n const { directories, files } = await listDirContents(dir);\n\n for (const file of files) {\n const name = file.replace(MDX_EXTENSION, \"\").replace(MD_EXTENSION, \"\");\n paths.push(prefix ? `${prefix}/${name}` : name);\n }\n\n for (const subdir of directories) {\n const subdirPath = join(dir, subdir);\n const newPrefix = prefix ? `${prefix}/${subdir}` : subdir;\n paths.push(newPrefix);\n await scanDirectory(subdirPath, newPrefix);\n }\n }\n\n await scanDirectory(DOCS_PATH);\n return paths.sort();\n}\n\nexport function findNearestPaths(\n requestedPath: string,\n availablePaths: string[],\n): string[] {\n const normalizedRequest = requestedPath\n .toLowerCase()\n .replace(/[^a-z0-9]/g, \"\");\n\n const scored = availablePaths.map((path) => {\n const normalizedPath = path.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n\n if (normalizedPath.includes(normalizedRequest)) {\n return { path, score: SIMILARITY_THRESHOLDS.EXACT_MATCH };\n }\n\n if (normalizedRequest.includes(normalizedPath)) {\n return { path, score: SIMILARITY_THRESHOLDS.CONTAINS_MATCH };\n }\n\n const overlap = [...normalizedRequest].filter((char) =>\n normalizedPath.includes(char),\n ).length;\n\n return {\n path,\n score:\n (overlap / normalizedRequest.length) *\n SIMILARITY_THRESHOLDS.PARTIAL_MATCH,\n };\n });\n\n return scored\n .filter((item) => item.score > SIMILARITY_THRESHOLDS.MIN_SUGGESTION)\n .sort((a, b) => b.score - a.score)\n .slice(0, 3)\n .map((item) => item.path);\n}\n\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;AAKA,MAAM,wBAAwB;CAC5B,aAAa;CACb,gBAAgB;CAChB,eAAe;CACf,gBAAgB;AAClB;AAEA,eAAsB,gBAAgB,SAGnC;CACD,IAAI;EACF,MAAM,QAAQ,MAAM,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;EAkB5D,OAAO;GAAE,aAhBW,MACjB,QAAQ,SAAS,KAAK,YAAY,CAAC,CAAC,CACpC,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,QAAQ,SAAS,CAAC,KAAK,WAAW,GAAG,CAAC,CAAC,CACvC,KAYgB;GAAG,OAVR,MACX,QACE,SACC,KAAK,OAAO,MACX,QAAQ,KAAK,IAAI,MAAA,UAChB,QAAQ,KAAK,IAAI,MAAA,MACvB,CAAC,CACA,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAEuB;EAAE;CAC9B,SAAS,OAAO;EACd,OAAO,MAAM,sCAAsC,WAAW,KAAK;EACnE,OAAO;GAAE,aAAa,CAAC;GAAG,OAAO,CAAC;EAAE;CACtC;AACF;AAEA,eAAsB,oBAAuC;CAC3D,MAAM,QAAkB,CAAC;CAEzB,eAAe,cACb,KACA,SAAiB,IACF;EACf,MAAM,EAAE,aAAa,UAAU,MAAM,gBAAgB,GAAG;EAExD,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,OAAO,KAAK,QAAQ,eAAe,EAAE,CAAC,CAAC,QAAA,OAAsB,EAAE;GACrE,MAAM,KAAK,SAAS,GAAG,OAAO,GAAG,SAAS,IAAI;EAChD;EAEA,KAAK,MAAM,UAAU,aAAa;GAChC,MAAM,aAAa,KAAK,KAAK,MAAM;GACnC,MAAM,YAAY,SAAS,GAAG,OAAO,GAAG,WAAW;GACnD,MAAM,KAAK,SAAS;GACpB,MAAM,cAAc,YAAY,SAAS;EAC3C;CACF;CAEA,MAAM,cAAc,SAAS;CAC7B,OAAO,MAAM,KAAK;AACpB;AAEA,SAAgB,iBACd,eACA,gBACU;CACV,MAAM,oBAAoB,cACvB,YAAY,CAAC,CACb,QAAQ,cAAc,EAAE;CAyB3B,OAvBe,eAAe,KAAK,SAAS;EAC1C,MAAM,iBAAiB,KAAK,YAAY,CAAC,CAAC,QAAQ,cAAc,EAAE;EAElE,IAAI,eAAe,SAAS,iBAAiB,GAC3C,OAAO;GAAE;GAAM,OAAO,sBAAsB;EAAY;EAG1D,IAAI,kBAAkB,SAAS,cAAc,GAC3C,OAAO;GAAE;GAAM,OAAO,sBAAsB;EAAe;EAO7D,OAAO;GACL;GACA,OANc,CAAC,GAAG,iBAAiB,CAAC,CAAC,QAAQ,SAC7C,eAAe,SAAS,IAAI,CAC9B,CAAC,CAAC,SAKa,kBAAkB,SAC7B,sBAAsB;EAC1B;CACF,CAEY,CAAC,CACV,QAAQ,SAAS,KAAK,QAAQ,sBAAsB,cAAc,CAAC,CACnE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CACjC,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,SAAS,KAAK,IAAI;AAC5B;AAEA,eAAsB,WAAW,MAAgC;CAC/D,IAAI;EACF,MAAM,KAAK,IAAI;EACf,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"paths.js","names":[],"sources":["../../src/utils/paths.ts"],"sourcesContent":["import { readdir, stat } from \"node:fs/promises\";\nimport { join, extname } from \"node:path\";\nimport { DOCS_PATH, MDX_EXTENSION, MD_EXTENSION } from \"../constants.js\";\nimport { cacheListing } from \"./cache.js\";\nimport { logger } from \"./logger.js\";\n\nconst SIMILARITY_THRESHOLDS = {\n EXACT_MATCH: 1,\n CONTAINS_MATCH: 0.8,\n PARTIAL_MATCH: 0.5,\n MIN_SUGGESTION: 0.3,\n} as const;\n\nexport async function listDirContents(dirPath: string): Promise<{\n directories: string[];\n files: string[];\n}> {\n try {\n const items = await readdir(dirPath, { withFileTypes: true });\n\n const directories = items\n .filter((item) => item.isDirectory())\n .map((item) => item.name)\n .filter((name) => !name.startsWith(\".\"))\n .sort();\n\n const files = items\n .filter(\n (item) =>\n item.isFile() &&\n (extname(item.name) === MDX_EXTENSION ||\n extname(item.name) === MD_EXTENSION),\n )\n .map((item) => item.name)\n .sort();\n\n return { directories, files };\n } catch (error) {\n logger.error(`Failed to list directory contents: ${dirPath}`, error);\n return { directories: [], files: [] };\n }\n}\n\nexport async function getAvailablePaths(): Promise<string[]> {\n const paths: string[] = [];\n\n async function scanDirectory(\n dir: string,\n prefix: string = \"\",\n ): Promise<void> {\n const { directories, files } = await listDirContents(dir);\n\n for (const file of files) {\n const name = file.replace(MDX_EXTENSION, \"\").replace(MD_EXTENSION, \"\");\n paths.push(prefix ? `${prefix}/${name}` : name);\n }\n\n for (const subdir of directories) {\n const subdirPath = join(dir, subdir);\n const newPrefix = prefix ? `${prefix}/${subdir}` : subdir;\n paths.push(newPrefix);\n await scanDirectory(subdirPath, newPrefix);\n }\n }\n\n await scanDirectory(DOCS_PATH);\n return paths.sort();\n}\n\nexport const getAvailableDocFiles = cacheListing(scanDocFiles);\n\nasync function scanDocFiles(): Promise<string[]> {\n const files: string[] = [];\n\n async function scanDirectory(dir: string, prefix = \"\"): Promise<void> {\n const { directories, files: dirFiles } = await listDirContents(dir);\n\n for (const file of dirFiles) {\n if (extname(file) !== MDX_EXTENSION) continue;\n const name = file.replace(MDX_EXTENSION, \"\");\n files.push(prefix ? `${prefix}/${name}` : name);\n }\n\n for (const subdir of directories) {\n const newPrefix = prefix ? `${prefix}/${subdir}` : subdir;\n await scanDirectory(join(dir, subdir), newPrefix);\n }\n }\n\n await scanDirectory(DOCS_PATH);\n return files.sort();\n}\n\nexport function findNearestPaths(\n requestedPath: string,\n availablePaths: string[],\n): string[] {\n const normalizedRequest = requestedPath\n .toLowerCase()\n .replace(/[^a-z0-9]/g, \"\");\n\n const scored = availablePaths.map((path) => {\n const normalizedPath = path.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n\n if (normalizedPath.includes(normalizedRequest)) {\n return { path, score: SIMILARITY_THRESHOLDS.EXACT_MATCH };\n }\n\n if (normalizedRequest.includes(normalizedPath)) {\n return { path, score: SIMILARITY_THRESHOLDS.CONTAINS_MATCH };\n }\n\n const overlap = [...normalizedRequest].filter((char) =>\n normalizedPath.includes(char),\n ).length;\n\n return {\n path,\n score:\n (overlap / normalizedRequest.length) *\n SIMILARITY_THRESHOLDS.PARTIAL_MATCH,\n };\n });\n\n return scored\n .filter((item) => item.score > SIMILARITY_THRESHOLDS.MIN_SUGGESTION)\n .sort((a, b) => b.score - a.score)\n .slice(0, 3)\n .map((item) => item.path);\n}\n\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ //#region src/utils/search.d.ts
2
+ interface SearchResult {
3
+ path: string;
4
+ title: string;
5
+ snippet: string;
6
+ }
7
+ declare function searchDocs(query: string, limit?: number): Promise<SearchResult[]>;
8
+ //#endregion
9
+ export { SearchResult, searchDocs };
10
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","names":[],"sources":["../../src/utils/search.ts"],"mappings":";UAkBiB;EACf;EACA;EACA;;iBA0FoB,WACpB,eACA,iBACC,QAAQ"}
@@ -0,0 +1,97 @@
1
+ import { DOCS_PATH, MDX_EXTENSION } from "../constants.js";
2
+ import { getAvailablePaths, pathExists } from "./paths.js";
3
+ import { readMDXFile } from "./mdx.js";
4
+ import { join } from "node:path";
5
+ //#region src/utils/search.ts
6
+ const SCORE_WEIGHTS = {
7
+ FULL_QUERY: {
8
+ title: 10,
9
+ path: 8,
10
+ description: 5
11
+ },
12
+ TOKEN: {
13
+ title: 3,
14
+ path: 2,
15
+ description: 1,
16
+ content: .5
17
+ }
18
+ };
19
+ let indexPromise = null;
20
+ function deriveTitle(path, frontmatter) {
21
+ const fmTitle = frontmatter["title"];
22
+ if (typeof fmTitle === "string" && fmTitle.trim()) return fmTitle;
23
+ return path.split("/").pop() ?? path;
24
+ }
25
+ async function buildIndex() {
26
+ const paths = await getAvailablePaths();
27
+ return (await Promise.all(paths.map(async (path) => {
28
+ const filePath = join(DOCS_PATH, `${path}${MDX_EXTENSION}`);
29
+ if (!await pathExists(filePath)) return null;
30
+ const mdx = await readMDXFile(filePath);
31
+ if (!mdx) return null;
32
+ const description = mdx.frontmatter["description"];
33
+ return {
34
+ path,
35
+ title: deriveTitle(path, mdx.frontmatter),
36
+ description: typeof description === "string" ? description : "",
37
+ content: mdx.content,
38
+ excerpt: mdx.excerpt ?? ""
39
+ };
40
+ }))).filter((entry) => entry !== null);
41
+ }
42
+ function getIndex() {
43
+ if (!indexPromise) indexPromise = buildIndex().catch((error) => {
44
+ indexPromise = null;
45
+ throw error;
46
+ });
47
+ return indexPromise;
48
+ }
49
+ function makeSnippet(content, tokens, excerpt) {
50
+ const lower = content.toLowerCase();
51
+ for (const token of tokens) {
52
+ const idx = lower.indexOf(token);
53
+ if (idx >= 0) {
54
+ const start = Math.max(0, idx - 60);
55
+ const end = Math.min(content.length, idx + token.length + 100);
56
+ const slice = content.slice(start, end).replace(/\s+/g, " ").trim();
57
+ return `${start > 0 ? "…" : ""}${slice}${end < content.length ? "…" : ""}`;
58
+ }
59
+ }
60
+ if (excerpt) return excerpt.slice(0, 160);
61
+ const firstLine = content.split("\n").find((line) => line.trim());
62
+ return firstLine ? firstLine.replace(/^#+\s*/, "").slice(0, 160) : "";
63
+ }
64
+ function scoreEntry(entry, query, tokens) {
65
+ const title = entry.title.toLowerCase();
66
+ const path = entry.path.toLowerCase();
67
+ const description = entry.description.toLowerCase();
68
+ const content = entry.content.toLowerCase();
69
+ let score = 0;
70
+ if (title.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.title;
71
+ if (path.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.path;
72
+ if (description.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.description;
73
+ for (const token of tokens) {
74
+ if (title.includes(token)) score += SCORE_WEIGHTS.TOKEN.title;
75
+ if (path.includes(token)) score += SCORE_WEIGHTS.TOKEN.path;
76
+ if (description.includes(token)) score += SCORE_WEIGHTS.TOKEN.description;
77
+ if (content.includes(token)) score += SCORE_WEIGHTS.TOKEN.content;
78
+ }
79
+ return score;
80
+ }
81
+ async function searchDocs(query, limit = 10) {
82
+ const normalized = query.trim().toLowerCase();
83
+ if (!normalized) return [];
84
+ const tokens = normalized.split(/\s+/).filter(Boolean);
85
+ return (await getIndex()).map((entry) => ({
86
+ entry,
87
+ score: scoreEntry(entry, normalized, tokens)
88
+ })).filter((scored) => scored.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map(({ entry }) => ({
89
+ path: entry.path,
90
+ title: entry.title,
91
+ snippet: makeSnippet(entry.content, tokens, entry.excerpt)
92
+ }));
93
+ }
94
+ //#endregion
95
+ export { searchDocs };
96
+
97
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../src/utils/search.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { DOCS_PATH, MDX_EXTENSION } from \"../constants.js\";\nimport { getAvailablePaths, pathExists } from \"./paths.js\";\nimport { readMDXFile } from \"./mdx.js\";\n\nconst SCORE_WEIGHTS = {\n FULL_QUERY: { title: 10, path: 8, description: 5 },\n TOKEN: { title: 3, path: 2, description: 1, content: 0.5 },\n} as const;\n\ninterface IndexEntry {\n path: string;\n title: string;\n description: string;\n content: string;\n excerpt: string;\n}\n\nexport interface SearchResult {\n path: string;\n title: string;\n snippet: string;\n}\n\nlet indexPromise: Promise<IndexEntry[]> | null = null;\n\nfunction deriveTitle(\n path: string,\n frontmatter: Record<string, unknown>,\n): string {\n const fmTitle = frontmatter[\"title\"];\n if (typeof fmTitle === \"string\" && fmTitle.trim()) return fmTitle;\n return path.split(\"/\").pop() ?? path;\n}\n\nasync function buildIndex(): Promise<IndexEntry[]> {\n const paths = await getAvailablePaths();\n const entries = await Promise.all(\n paths.map(async (path): Promise<IndexEntry | null> => {\n const filePath = join(DOCS_PATH, `${path}${MDX_EXTENSION}`);\n if (!(await pathExists(filePath))) return null;\n const mdx = await readMDXFile(filePath);\n if (!mdx) return null;\n const description = mdx.frontmatter[\"description\"];\n return {\n path,\n title: deriveTitle(path, mdx.frontmatter),\n description: typeof description === \"string\" ? description : \"\",\n content: mdx.content,\n excerpt: mdx.excerpt ?? \"\",\n };\n }),\n );\n return entries.filter((entry): entry is IndexEntry => entry !== null);\n}\n\nfunction getIndex(): Promise<IndexEntry[]> {\n if (!indexPromise) {\n indexPromise = buildIndex().catch((error) => {\n indexPromise = null;\n throw error;\n });\n }\n return indexPromise;\n}\n\nfunction makeSnippet(\n content: string,\n tokens: string[],\n excerpt: string,\n): string {\n const lower = content.toLowerCase();\n for (const token of tokens) {\n const idx = lower.indexOf(token);\n if (idx >= 0) {\n const start = Math.max(0, idx - 60);\n const end = Math.min(content.length, idx + token.length + 100);\n const slice = content.slice(start, end).replace(/\\s+/g, \" \").trim();\n return `${start > 0 ? \"…\" : \"\"}${slice}${end < content.length ? \"…\" : \"\"}`;\n }\n }\n if (excerpt) return excerpt.slice(0, 160);\n const firstLine = content.split(\"\\n\").find((line) => line.trim());\n return firstLine ? firstLine.replace(/^#+\\s*/, \"\").slice(0, 160) : \"\";\n}\n\nfunction scoreEntry(\n entry: IndexEntry,\n query: string,\n tokens: string[],\n): number {\n const title = entry.title.toLowerCase();\n const path = entry.path.toLowerCase();\n const description = entry.description.toLowerCase();\n const content = entry.content.toLowerCase();\n\n let score = 0;\n if (title.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.title;\n if (path.includes(query)) score += SCORE_WEIGHTS.FULL_QUERY.path;\n if (description.includes(query))\n score += SCORE_WEIGHTS.FULL_QUERY.description;\n\n for (const token of tokens) {\n if (title.includes(token)) score += SCORE_WEIGHTS.TOKEN.title;\n if (path.includes(token)) score += SCORE_WEIGHTS.TOKEN.path;\n if (description.includes(token)) score += SCORE_WEIGHTS.TOKEN.description;\n if (content.includes(token)) score += SCORE_WEIGHTS.TOKEN.content;\n }\n return score;\n}\n\nexport async function searchDocs(\n query: string,\n limit = 10,\n): Promise<SearchResult[]> {\n const normalized = query.trim().toLowerCase();\n if (!normalized) return [];\n const tokens = normalized.split(/\\s+/).filter(Boolean);\n\n const index = await getIndex();\n return index\n .map((entry) => ({ entry, score: scoreEntry(entry, normalized, tokens) }))\n .filter((scored) => scored.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map(({ entry }) => ({\n path: entry.path,\n title: entry.title,\n snippet: makeSnippet(entry.content, tokens, entry.excerpt),\n }));\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","names":[],"sources":["../../src/utils/security.ts"],"mappings":";iBAEgB,YAAA,CAAa,QAAgB"}
1
+ {"version":3,"file":"security.d.ts","names":[],"sources":["../../src/utils/security.ts"],"mappings":";iBAEgB,aAAa"}
@@ -0,0 +1,14 @@
1
+ import { XuluxCatalogResult } from "./types.js";
2
+ //#region src/xulux/catalog-client.d.ts
3
+ declare const DEFAULT_CATALOG_URL = "https://www.assistant-ui.com/api/xulux/mcp-catalog";
4
+ declare function getCatalogUrl(): string;
5
+ declare function clearCatalogCache(): void;
6
+ /**
7
+ * Returns the assistant-ui template catalog. Uses an in-memory five-minute cache, and falls
8
+ * back to a minimal bundled catalog (marked degraded) when the live fetch
9
+ * fails and no cached copy exists.
10
+ */
11
+ declare function getXuluxCatalog(): Promise<XuluxCatalogResult>;
12
+ //#endregion
13
+ export { DEFAULT_CATALOG_URL, clearCatalogCache, getCatalogUrl, getXuluxCatalog };
14
+ //# sourceMappingURL=catalog-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-client.d.ts","names":[],"sources":["../../src/xulux/catalog-client.ts"],"mappings":";;cAIa;iBAKG;iBA0CA;;;;;;iBAoBM,mBAAmB,QAAQ"}
@@ -0,0 +1,67 @@
1
+ import { logger } from "../utils/logger.js";
2
+ import { FALLBACK_CATALOG, FALLBACK_NOTE } from "./fallback-catalog.js";
3
+ //#region src/xulux/catalog-client.ts
4
+ const DEFAULT_CATALOG_URL = "https://www.assistant-ui.com/api/xulux/mcp-catalog";
5
+ const CATALOG_TTL_MS = 300 * 1e3;
6
+ function getCatalogUrl() {
7
+ return process.env.XULUX_CATALOG_URL?.trim() || "https://www.assistant-ui.com/api/xulux/mcp-catalog";
8
+ }
9
+ function validateCatalog(data) {
10
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Catalog response is not a JSON object.");
11
+ const catalog = data;
12
+ if (catalog.version !== 1) throw new Error(`Unsupported catalog version: ${String(catalog.version)}. Expected 1.`);
13
+ if (!Array.isArray(catalog.templates)) throw new Error("Catalog response has no templates array.");
14
+ for (const template of catalog.templates) if (!template || typeof template !== "object" || typeof template.id !== "string" || typeof template.templateId !== "string" || template.kind !== "template" && template.kind !== "example") throw new Error("Catalog response contains a malformed template entry (missing id/templateId/kind).");
15
+ return catalog;
16
+ }
17
+ let cache = null;
18
+ function clearCatalogCache() {
19
+ cache = null;
20
+ }
21
+ async function fetchCatalog(url) {
22
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
23
+ if (!res.ok) throw new Error(`Catalog fetch failed: HTTP ${res.status}`);
24
+ return validateCatalog(await res.json());
25
+ }
26
+ /**
27
+ * Returns the assistant-ui template catalog. Uses an in-memory five-minute cache, and falls
28
+ * back to a minimal bundled catalog (marked degraded) when the live fetch
29
+ * fails and no cached copy exists.
30
+ */
31
+ async function getXuluxCatalog() {
32
+ const url = getCatalogUrl();
33
+ const now = Date.now();
34
+ if (cache && cache.url === url && now - cache.fetchedAt < CATALOG_TTL_MS) return {
35
+ catalog: cache.catalog,
36
+ degraded: false
37
+ };
38
+ try {
39
+ const catalog = await fetchCatalog(url);
40
+ cache = {
41
+ catalog,
42
+ fetchedAt: now,
43
+ url
44
+ };
45
+ return {
46
+ catalog,
47
+ degraded: false
48
+ };
49
+ } catch (error) {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ logger.warn(`assistant-ui template catalog fetch failed (${url}): ${message}`);
52
+ if (cache && cache.url === url) return {
53
+ catalog: cache.catalog,
54
+ degraded: true,
55
+ degradedReason: `Live catalog refresh failed (${message}); using previously fetched catalog.`
56
+ };
57
+ return {
58
+ catalog: FALLBACK_CATALOG,
59
+ degraded: true,
60
+ degradedReason: `${message} ${FALLBACK_NOTE}`
61
+ };
62
+ }
63
+ }
64
+ //#endregion
65
+ export { DEFAULT_CATALOG_URL, clearCatalogCache, getCatalogUrl, getXuluxCatalog };
66
+
67
+ //# sourceMappingURL=catalog-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-client.js","names":[],"sources":["../../src/xulux/catalog-client.ts"],"sourcesContent":["import { logger } from \"../utils/logger.js\";\nimport { FALLBACK_CATALOG, FALLBACK_NOTE } from \"./fallback-catalog.js\";\nimport type { XuluxCatalog, XuluxCatalogResult } from \"./types.js\";\n\nexport const DEFAULT_CATALOG_URL =\n \"https://www.assistant-ui.com/api/xulux/mcp-catalog\";\n\nconst CATALOG_TTL_MS = 5 * 60 * 1000;\n\nexport function getCatalogUrl(): string {\n const override = process.env.XULUX_CATALOG_URL?.trim();\n return override || DEFAULT_CATALOG_URL;\n}\n\nfunction validateCatalog(data: unknown): XuluxCatalog {\n if (!data || typeof data !== \"object\" || Array.isArray(data)) {\n throw new Error(\"Catalog response is not a JSON object.\");\n }\n const catalog = data as XuluxCatalog;\n if (catalog.version !== 1) {\n throw new Error(\n `Unsupported catalog version: ${String(catalog.version)}. Expected 1.`,\n );\n }\n if (!Array.isArray(catalog.templates)) {\n throw new Error(\"Catalog response has no templates array.\");\n }\n for (const template of catalog.templates) {\n if (\n !template ||\n typeof template !== \"object\" ||\n typeof template.id !== \"string\" ||\n typeof template.templateId !== \"string\" ||\n (template.kind !== \"template\" && template.kind !== \"example\")\n ) {\n throw new Error(\n \"Catalog response contains a malformed template entry (missing id/templateId/kind).\",\n );\n }\n }\n return catalog;\n}\n\ninterface CacheEntry {\n catalog: XuluxCatalog;\n fetchedAt: number;\n url: string;\n}\n\nlet cache: CacheEntry | null = null;\n\nexport function clearCatalogCache(): void {\n cache = null;\n}\n\nasync function fetchCatalog(url: string): Promise<XuluxCatalog> {\n const res = await fetch(url, {\n headers: { Accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`Catalog fetch failed: HTTP ${res.status}`);\n }\n const data: unknown = await res.json();\n return validateCatalog(data);\n}\n\n/**\n * Returns the assistant-ui template catalog. Uses an in-memory five-minute cache, and falls\n * back to a minimal bundled catalog (marked degraded) when the live fetch\n * fails and no cached copy exists.\n */\nexport async function getXuluxCatalog(): Promise<XuluxCatalogResult> {\n const url = getCatalogUrl();\n const now = Date.now();\n\n if (cache && cache.url === url && now - cache.fetchedAt < CATALOG_TTL_MS) {\n return { catalog: cache.catalog, degraded: false };\n }\n\n try {\n const catalog = await fetchCatalog(url);\n cache = { catalog, fetchedAt: now, url };\n return { catalog, degraded: false };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.warn(\n `assistant-ui template catalog fetch failed (${url}): ${message}`,\n );\n\n // Prefer a stale cached copy over the minimal fallback.\n if (cache && cache.url === url) {\n return {\n catalog: cache.catalog,\n degraded: true,\n degradedReason: `Live catalog refresh failed (${message}); using previously fetched catalog.`,\n };\n }\n\n return {\n catalog: FALLBACK_CATALOG,\n degraded: true,\n degradedReason: `${message} ${FALLBACK_NOTE}`,\n };\n }\n}\n"],"mappings":";;;AAIA,MAAa,sBACX;AAEF,MAAM,iBAAiB,MAAS;AAEhC,SAAgB,gBAAwB;CAEtC,OADiB,QAAQ,IAAI,mBAAmB,KAAK,KAAA;AAEvD;AAEA,SAAS,gBAAgB,MAA6B;CACpD,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACzD,MAAM,IAAI,MAAM,wCAAwC;CAE1D,MAAM,UAAU;CAChB,IAAI,QAAQ,YAAY,GACtB,MAAM,IAAI,MACR,gCAAgC,OAAO,QAAQ,OAAO,EAAE,cAC1D;CAEF,IAAI,CAAC,MAAM,QAAQ,QAAQ,SAAS,GAClC,MAAM,IAAI,MAAM,0CAA0C;CAE5D,KAAK,MAAM,YAAY,QAAQ,WAC7B,IACE,CAAC,YACD,OAAO,aAAa,YACpB,OAAO,SAAS,OAAO,YACvB,OAAO,SAAS,eAAe,YAC9B,SAAS,SAAS,cAAc,SAAS,SAAS,WAEnD,MAAM,IAAI,MACR,oFACF;CAGJ,OAAO;AACT;AAQA,IAAI,QAA2B;AAE/B,SAAgB,oBAA0B;CACxC,QAAQ;AACV;AAEA,eAAe,aAAa,KAAoC;CAC9D,MAAM,MAAM,MAAM,MAAM,KAAK,EAC3B,SAAS,EAAE,QAAQ,mBAAmB,EACxC,CAAC;CACD,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,8BAA8B,IAAI,QAAQ;CAG5D,OAAO,gBAAgB,MADK,IAAI,KAAK,CACV;AAC7B;;;;;;AAOA,eAAsB,kBAA+C;CACnE,MAAM,MAAM,cAAc;CAC1B,MAAM,MAAM,KAAK,IAAI;CAErB,IAAI,SAAS,MAAM,QAAQ,OAAO,MAAM,MAAM,YAAY,gBACxD,OAAO;EAAE,SAAS,MAAM;EAAS,UAAU;CAAM;CAGnD,IAAI;EACF,MAAM,UAAU,MAAM,aAAa,GAAG;EACtC,QAAQ;GAAE;GAAS,WAAW;GAAK;EAAI;EACvC,OAAO;GAAE;GAAS,UAAU;EAAM;CACpC,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,OAAO,KACL,+CAA+C,IAAI,KAAK,SAC1D;EAGA,IAAI,SAAS,MAAM,QAAQ,KACzB,OAAO;GACL,SAAS,MAAM;GACf,UAAU;GACV,gBAAgB,gCAAgC,QAAQ;EAC1D;EAGF,OAAO;GACL,SAAS;GACT,UAAU;GACV,gBAAgB,GAAG,QAAQ,GAAG;EAChC;CACF;AACF"}
@@ -0,0 +1,7 @@
1
+ import { XuluxCatalog } from "./types.js";
2
+ //#region src/xulux/fallback-catalog.d.ts
3
+ declare const FALLBACK_NOTE: string;
4
+ declare const FALLBACK_CATALOG: XuluxCatalog;
5
+ //#endregion
6
+ export { FALLBACK_CATALOG, FALLBACK_NOTE };
7
+ //# sourceMappingURL=fallback-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback-catalog.d.ts","names":[],"sources":["../../src/xulux/fallback-catalog.ts"],"mappings":";;cAWa;cAiBA,kBAAkB"}
@@ -0,0 +1,47 @@
1
+ import "./types.js";
2
+ //#region src/xulux/fallback-catalog.ts
3
+ const FALLBACK_NOTE = "The live assistant-ui template catalog could not be fetched, so this is minimal bundled fallback data. Template details, previews, and downloads require live catalog access. Check network access to the docs site or set XULUX_CATALOG_URL to a reachable catalog endpoint.";
4
+ const fallbackTemplate = (overrides) => ({
5
+ templateId: overrides.id,
6
+ versionId: null,
7
+ assistantPlacement: "unavailable in fallback mode",
8
+ features: [],
9
+ customizable: [],
10
+ versions: [],
11
+ ...overrides
12
+ });
13
+ const FALLBACK_CATALOG = {
14
+ version: 1,
15
+ generatedAt: "fallback",
16
+ docsOrigin: "https://www.assistant-ui.com",
17
+ templates: [
18
+ fallbackTemplate({
19
+ id: "base-assistant-ui",
20
+ kind: "template",
21
+ name: "Configurable Base Assistant UI",
22
+ summary: "Hosted configurable assistant-ui Base chat template with threads, composer, suggestions, and no-key demo flows."
23
+ }),
24
+ fallbackTemplate({
25
+ id: "webpage-assistant",
26
+ kind: "template",
27
+ name: "Webpage with Assistant",
28
+ summary: "Docs/website layout with a sidebar or modal assistant grounded in configurable pages."
29
+ }),
30
+ fallbackTemplate({
31
+ id: "product-page-assistant",
32
+ kind: "template",
33
+ name: "Product Page with Floating Assistant",
34
+ summary: "Mock product dashboard with a floating modal support assistant and analyze-then-handoff flow."
35
+ }),
36
+ fallbackTemplate({
37
+ id: "expo-react-native",
38
+ kind: "example",
39
+ name: "Expo React Native Assistant",
40
+ summary: "Mobile AI chat app built with Expo and assistant-ui React Native primitives."
41
+ })
42
+ ]
43
+ };
44
+ //#endregion
45
+ export { FALLBACK_CATALOG, FALLBACK_NOTE };
46
+
47
+ //# sourceMappingURL=fallback-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback-catalog.js","names":[],"sources":["../../src/xulux/fallback-catalog.ts"],"sourcesContent":["import {\n XULUX_MCP_CATALOG_VERSION,\n type XuluxCatalog,\n type XuluxCatalogTemplate,\n} from \"./types.js\";\n\n// Minimal bundled fallback catalog for degraded mode. It supports discovery\n// (ids + names) only. Full authoring details and preview/download URLs\n// require the live catalog endpoint. Do NOT grow this into a copy of the\n// full template knowledge base — the docs endpoint owns freshness.\n\nexport const FALLBACK_NOTE =\n \"The live assistant-ui template catalog could not be fetched, so this is minimal bundled fallback data. \" +\n \"Template details, previews, and downloads require live catalog access. \" +\n \"Check network access to the docs site or set XULUX_CATALOG_URL to a reachable catalog endpoint.\";\n\nconst fallbackTemplate = (\n overrides: Pick<XuluxCatalogTemplate, \"id\" | \"kind\" | \"name\" | \"summary\">,\n): XuluxCatalogTemplate => ({\n templateId: overrides.id,\n versionId: null,\n assistantPlacement: \"unavailable in fallback mode\",\n features: [],\n customizable: [],\n versions: [],\n ...overrides,\n});\n\nexport const FALLBACK_CATALOG: XuluxCatalog = {\n version: XULUX_MCP_CATALOG_VERSION,\n generatedAt: \"fallback\",\n docsOrigin: \"https://www.assistant-ui.com\",\n templates: [\n fallbackTemplate({\n id: \"base-assistant-ui\",\n kind: \"template\",\n name: \"Configurable Base Assistant UI\",\n summary:\n \"Hosted configurable assistant-ui Base chat template with threads, composer, suggestions, and no-key demo flows.\",\n }),\n fallbackTemplate({\n id: \"webpage-assistant\",\n kind: \"template\",\n name: \"Webpage with Assistant\",\n summary:\n \"Docs/website layout with a sidebar or modal assistant grounded in configurable pages.\",\n }),\n fallbackTemplate({\n id: \"product-page-assistant\",\n kind: \"template\",\n name: \"Product Page with Floating Assistant\",\n summary:\n \"Mock product dashboard with a floating modal support assistant and analyze-then-handoff flow.\",\n }),\n fallbackTemplate({\n id: \"expo-react-native\",\n kind: \"example\",\n name: \"Expo React Native Assistant\",\n summary:\n \"Mobile AI chat app built with Expo and assistant-ui React Native primitives.\",\n }),\n ],\n};\n"],"mappings":";;AAWA,MAAa,gBACX;AAIF,MAAM,oBACJ,eAC0B;CAC1B,YAAY,UAAU;CACtB,WAAW;CACX,oBAAoB;CACpB,UAAU,CAAC;CACX,cAAc,CAAC;CACf,UAAU,CAAC;CACX,GAAG;AACL;AAEA,MAAa,mBAAiC;CAC5C,SAAA;CACA,aAAa;CACb,YAAY;CACZ,WAAW;EACT,iBAAiB;GACf,IAAI;GACJ,MAAM;GACN,MAAM;GACN,SACE;EACJ,CAAC;EACD,iBAAiB;GACf,IAAI;GACJ,MAAM;GACN,MAAM;GACN,SACE;EACJ,CAAC;EACD,iBAAiB;GACf,IAAI;GACJ,MAAM;GACN,MAAM;GACN,SACE;EACJ,CAAC;EACD,iBAAiB;GACf,IAAI;GACJ,MAAM;GACN,MAAM;GACN,SACE;EACJ,CAAC;CACH;AACF"}
@@ -0,0 +1,5 @@
1
+ //#region src/xulux/fetch-sandbox.d.ts
2
+ declare function fetchSandboxResource(url: string | URL, init?: RequestInit): Promise<Response>;
3
+ //#endregion
4
+ export { fetchSandboxResource };
5
+ //# sourceMappingURL=fetch-sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-sandbox.d.ts","names":[],"sources":["../../src/xulux/fetch-sandbox.ts"],"mappings":";iBAgCsB,qBACpB,cAAc,KACd,OAAO,cACN,QAAQ"}
@@ -0,0 +1,40 @@
1
+ //#region src/xulux/fetch-sandbox.ts
2
+ const SANDBOX_FETCH_HEADERS = {
3
+ Accept: "application/json, application/zip, application/octet-stream, */*",
4
+ "User-Agent": "curl/8.7.1"
5
+ };
6
+ const MAX_ATTEMPTS = 3;
7
+ const RETRY_DELAY_MS = 300;
8
+ function isRetryableFetchError(error) {
9
+ if (!(error instanceof Error)) return false;
10
+ const code = error.cause?.code ?? error.message;
11
+ return code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ECONNREFUSED" || code === "fetch failed";
12
+ }
13
+ function sleep(ms) {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ }
16
+ function mergeHeaders(headers) {
17
+ const merged = new Headers(SANDBOX_FETCH_HEADERS);
18
+ if (!headers) return merged;
19
+ new Headers(headers).forEach((value, key) => merged.set(key, value));
20
+ return merged;
21
+ }
22
+ async function fetchSandboxResource(url, init) {
23
+ let lastError;
24
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) try {
25
+ return await fetch(url, {
26
+ ...init,
27
+ cache: "no-store",
28
+ headers: mergeHeaders(init?.headers)
29
+ });
30
+ } catch (error) {
31
+ lastError = error;
32
+ if (!isRetryableFetchError(error) || attempt === MAX_ATTEMPTS) throw error;
33
+ await sleep(RETRY_DELAY_MS * attempt);
34
+ }
35
+ throw lastError;
36
+ }
37
+ //#endregion
38
+ export { fetchSandboxResource };
39
+
40
+ //# sourceMappingURL=fetch-sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-sandbox.js","names":[],"sources":["../../src/xulux/fetch-sandbox.ts"],"sourcesContent":["const SANDBOX_FETCH_HEADERS = {\n Accept: \"application/json, application/zip, application/octet-stream, */*\",\n // Blaxel preview hosts intermittently reset Node's default fetch path.\n \"User-Agent\": \"curl/8.7.1\",\n};\n\nconst MAX_ATTEMPTS = 3;\nconst RETRY_DELAY_MS = 300;\n\nfunction isRetryableFetchError(error: unknown) {\n if (!(error instanceof Error)) return false;\n const cause = error.cause as { code?: string } | undefined;\n const code = cause?.code ?? error.message;\n return (\n code === \"ECONNRESET\" ||\n code === \"ETIMEDOUT\" ||\n code === \"ECONNREFUSED\" ||\n code === \"fetch failed\"\n );\n}\n\nfunction sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction mergeHeaders(headers?: HeadersInit): Headers {\n const merged = new Headers(SANDBOX_FETCH_HEADERS);\n if (!headers) return merged;\n new Headers(headers).forEach((value, key) => merged.set(key, value));\n return merged;\n}\n\nexport async function fetchSandboxResource(\n url: string | URL,\n init?: RequestInit,\n): Promise<Response> {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {\n try {\n return await fetch(url, {\n ...init,\n cache: \"no-store\",\n headers: mergeHeaders(init?.headers),\n });\n } catch (error) {\n lastError = error;\n if (!isRetryableFetchError(error) || attempt === MAX_ATTEMPTS) {\n throw error;\n }\n await sleep(RETRY_DELAY_MS * attempt);\n }\n }\n\n throw lastError;\n}\n"],"mappings":";AAAA,MAAM,wBAAwB;CAC5B,QAAQ;CAER,cAAc;AAChB;AAEA,MAAM,eAAe;AACrB,MAAM,iBAAiB;AAEvB,SAAS,sBAAsB,OAAgB;CAC7C,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CAEtC,MAAM,OADQ,MAAM,OACA,QAAQ,MAAM;CAClC,OACE,SAAS,gBACT,SAAS,eACT,SAAS,kBACT,SAAS;AAEb;AAEA,SAAS,MAAM,IAAY;CACzB,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAEA,SAAS,aAAa,SAAgC;CACpD,MAAM,SAAS,IAAI,QAAQ,qBAAqB;CAChD,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,OAAO,CAAC,CAAC,SAAS,OAAO,QAAQ,OAAO,IAAI,KAAK,KAAK,CAAC;CACnE,OAAO;AACT;AAEA,eAAsB,qBACpB,KACA,MACmB;CACnB,IAAI;CAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,cAAc,WAAW,GACxD,IAAI;EACF,OAAO,MAAM,MAAM,KAAK;GACtB,GAAG;GACH,OAAO;GACP,SAAS,aAAa,MAAM,OAAO;EACrC,CAAC;CACH,SAAS,OAAO;EACd,YAAY;EACZ,IAAI,CAAC,sBAAsB,KAAK,KAAK,YAAY,cAC/C,MAAM;EAER,MAAM,MAAM,iBAAiB,OAAO;CACtC;CAGF,MAAM;AACR"}