livemark 0.25.0 → 0.27.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.
Files changed (427) hide show
  1. package/README.md +3 -2
  2. package/build/actions/config/define.d.ts +12 -0
  3. package/build/actions/config/define.unit.js +8 -1
  4. package/build/actions/escape/copy.d.ts +11 -0
  5. package/build/actions/escape/copy.js +29 -0
  6. package/build/actions/escape/copy.unit.d.ts +1 -0
  7. package/build/actions/escape/copy.unit.js +37 -0
  8. package/build/actions/escape/list.d.ts +12 -0
  9. package/build/actions/escape/list.js +27 -0
  10. package/build/actions/escape/list.unit.d.ts +1 -0
  11. package/build/actions/escape/list.unit.js +36 -0
  12. package/build/actions/target/build.d.ts +9 -26
  13. package/build/actions/target/build.js +50 -71
  14. package/build/actions/target/build.unit.d.ts +1 -0
  15. package/build/actions/target/build.unit.js +76 -0
  16. package/build/actions/target/clear.d.ts +4 -0
  17. package/build/actions/target/clear.js +14 -0
  18. package/build/actions/target/clear.unit.d.ts +1 -0
  19. package/build/actions/target/clear.unit.js +23 -0
  20. package/build/commands/build.js +1 -2
  21. package/build/commands/clear.d.ts +5 -0
  22. package/build/commands/clear.js +15 -0
  23. package/build/commands/escape.d.ts +4 -3
  24. package/build/commands/escape.js +45 -48
  25. package/build/commands/preview.js +1 -2
  26. package/build/commands/start.js +1 -2
  27. package/build/components/Abbr.d.ts +1 -1
  28. package/build/components/Abbr.js +2 -4
  29. package/build/components/AnsiCode.d.ts +1 -1
  30. package/build/components/AnsiCode.js +2 -2
  31. package/build/components/Article.d.ts +2 -1
  32. package/build/components/Article.js +7 -14
  33. package/build/components/BackToTop.d.ts +1 -1
  34. package/build/components/Banner.d.ts +2 -2
  35. package/build/components/Banner.js +7 -3
  36. package/build/components/BlogIndex.d.ts +1 -1
  37. package/build/components/BlogIndex.js +2 -11
  38. package/build/components/Breadcrumbs.d.ts +1 -1
  39. package/build/components/Breadcrumbs.js +2 -3
  40. package/build/components/Callout.d.ts +1 -1
  41. package/build/components/Callout.js +5 -9
  42. package/build/components/Card.d.ts +1 -1
  43. package/build/components/Card.js +2 -3
  44. package/build/components/Cards.d.ts +1 -1
  45. package/build/components/ChangelogIndex.d.ts +1 -1
  46. package/build/components/ChangelogIndex.js +2 -11
  47. package/build/components/ChoiceList.d.ts +15 -0
  48. package/build/components/ChoiceList.js +13 -0
  49. package/build/components/CodeBlock.d.ts +1 -1
  50. package/build/components/CodeBlock.js +3 -16
  51. package/build/components/CodeTabs.d.ts +1 -1
  52. package/build/components/CodeTabs.js +2 -3
  53. package/build/components/Column.d.ts +1 -1
  54. package/build/components/Columns.d.ts +1 -1
  55. package/build/components/Content.d.ts +1 -1
  56. package/build/components/ContentTab.d.ts +1 -1
  57. package/build/components/ContentTabs.d.ts +1 -1
  58. package/build/components/ContentTabs.js +2 -3
  59. package/build/components/DefaultCatchBoundary.d.ts +1 -1
  60. package/build/components/DefaultCatchBoundary.js +5 -7
  61. package/build/components/DefinitionList.d.ts +3 -3
  62. package/build/components/Details.d.ts +1 -1
  63. package/build/components/Details.js +2 -5
  64. package/build/components/FileTree.d.ts +1 -1
  65. package/build/components/FileTree.js +3 -5
  66. package/build/components/Footer.d.ts +1 -1
  67. package/build/components/Footer.js +2 -5
  68. package/build/components/FootnoteRef.d.ts +1 -1
  69. package/build/components/FootnoteRef.js +2 -4
  70. package/build/components/Header.d.ts +6 -0
  71. package/build/components/Header.js +26 -0
  72. package/build/components/HeaderMenu.d.ts +2 -0
  73. package/build/components/HeaderMenu.js +27 -0
  74. package/build/components/HeaderNav.d.ts +6 -0
  75. package/build/components/HeaderNav.js +74 -0
  76. package/build/components/HeaderSearch.d.ts +4 -0
  77. package/build/components/HeaderSearch.js +33 -0
  78. package/build/components/Heading.d.ts +6 -6
  79. package/build/components/InlineBadge.d.ts +2 -2
  80. package/build/components/InlineBadge.js +1 -1
  81. package/build/components/InlineIcon.d.ts +1 -1
  82. package/build/components/InlineToc.d.ts +1 -1
  83. package/build/components/InlineToc.js +2 -4
  84. package/build/components/Layout.d.ts +2 -1
  85. package/build/components/Layout.js +5 -58
  86. package/build/components/LinkButton.d.ts +2 -2
  87. package/build/components/LinkButton.js +1 -1
  88. package/build/components/MenuSheet.d.ts +22 -0
  89. package/build/components/MenuSheet.js +27 -0
  90. package/build/components/Mermaid.d.ts +1 -1
  91. package/build/components/MermaidRenderer.d.ts +1 -1
  92. package/build/components/MermaidRenderer.js +20 -8
  93. package/build/components/NotFound.d.ts +1 -1
  94. package/build/components/NotFound.js +3 -6
  95. package/build/components/PackageTabs.d.ts +1 -1
  96. package/build/components/PackageTabs.js +3 -7
  97. package/build/components/PageToolbar.d.ts +1 -1
  98. package/build/components/PageToolbar.js +2 -10
  99. package/build/components/PrevNext.d.ts +1 -1
  100. package/build/components/PrevNext.js +2 -10
  101. package/build/components/Root.d.ts +4 -0
  102. package/build/components/Root.js +8 -0
  103. package/build/components/Search.d.ts +5 -2
  104. package/build/components/Search.js +11 -9
  105. package/build/components/SearchDialog.d.ts +1 -1
  106. package/build/components/SearchDialog.js +10 -133
  107. package/build/components/Section.d.ts +1 -1
  108. package/build/components/SectionHeader.d.ts +1 -1
  109. package/build/components/SheetPanel.d.ts +12 -0
  110. package/build/components/SheetPanel.js +19 -0
  111. package/build/components/Sidebar.d.ts +1 -1
  112. package/build/components/Sidebar.js +12 -14
  113. package/build/components/SidebarArticles.d.ts +4 -1
  114. package/build/components/SidebarArticles.js +5 -9
  115. package/build/components/SidebarLinks.d.ts +4 -1
  116. package/build/components/SidebarLinks.js +6 -9
  117. package/build/components/SidebarPosts.d.ts +4 -1
  118. package/build/components/SidebarPosts.js +7 -23
  119. package/build/components/SidebarReleases.d.ts +4 -1
  120. package/build/components/SidebarReleases.js +5 -15
  121. package/build/components/SidebarSectionPicker.d.ts +8 -0
  122. package/build/components/SidebarSectionPicker.js +31 -0
  123. package/build/components/SidebarSections.d.ts +1 -1
  124. package/build/components/SidebarSections.js +5 -9
  125. package/build/components/SiteLink.d.ts +1 -1
  126. package/build/components/SiteTitle.d.ts +1 -1
  127. package/build/components/SiteTitle.js +2 -6
  128. package/build/components/SortableTable.d.ts +1 -1
  129. package/build/components/SoundCloud.d.ts +1 -1
  130. package/build/components/SoundCloud.js +2 -2
  131. package/build/components/TagIndex.d.ts +1 -1
  132. package/build/components/TagIndex.js +2 -11
  133. package/build/components/Theme.d.ts +2 -1
  134. package/build/components/Theme.js +35 -15
  135. package/build/components/Toc.d.ts +5 -4
  136. package/build/components/Toc.js +35 -35
  137. package/build/components/YouTube.d.ts +1 -1
  138. package/build/components/YouTube.js +2 -2
  139. package/build/components/ZoomImage.d.ts +1 -1
  140. package/build/content/article.js +4 -11
  141. package/build/content-collections.js +6 -4
  142. package/build/elements/accordion.d.ts +4 -4
  143. package/build/elements/accordion.js +2 -3
  144. package/build/elements/alert-dialog.d.ts +12 -12
  145. package/build/elements/alert-dialog.js +2 -4
  146. package/build/elements/alert.d.ts +4 -4
  147. package/build/elements/alert.js +2 -2
  148. package/build/elements/aspect-ratio.d.ts +1 -1
  149. package/build/elements/avatar.d.ts +6 -6
  150. package/build/elements/badge.d.ts +1 -1
  151. package/build/elements/badge.js +3 -1
  152. package/build/elements/breadcrumb.d.ts +6 -6
  153. package/build/elements/breadcrumb.js +2 -4
  154. package/build/elements/button-group.d.ts +2 -2
  155. package/build/elements/button-group.js +5 -5
  156. package/build/elements/button.d.ts +2 -2
  157. package/build/elements/button.js +9 -7
  158. package/build/elements/calendar.d.ts +2 -2
  159. package/build/elements/calendar.js +2 -2
  160. package/build/elements/card.d.ts +7 -7
  161. package/build/elements/card.js +4 -4
  162. package/build/elements/carousel.d.ts +5 -5
  163. package/build/elements/carousel.js +3 -7
  164. package/build/elements/chart.d.ts +6 -6
  165. package/build/elements/chart.js +5 -11
  166. package/build/elements/checkbox.d.ts +1 -1
  167. package/build/elements/collapsible.d.ts +3 -3
  168. package/build/elements/command.d.ts +9 -9
  169. package/build/elements/command.js +5 -11
  170. package/build/elements/context-menu.d.ts +15 -15
  171. package/build/elements/context-menu.js +6 -9
  172. package/build/elements/dialog.d.ts +10 -10
  173. package/build/elements/dialog.js +2 -6
  174. package/build/elements/drawer.d.ts +14 -12
  175. package/build/elements/drawer.js +14 -15
  176. package/build/elements/dropdown-menu.d.ts +15 -15
  177. package/build/elements/dropdown-menu.js +7 -10
  178. package/build/elements/empty.d.ts +6 -6
  179. package/build/elements/empty.js +2 -2
  180. package/build/elements/field.d.ts +10 -10
  181. package/build/elements/field.js +3 -4
  182. package/build/elements/hover-card.d.ts +3 -3
  183. package/build/elements/hover-card.js +2 -2
  184. package/build/elements/input-group.d.ts +7 -7
  185. package/build/elements/input-group.js +5 -5
  186. package/build/elements/input-otp.d.ts +4 -4
  187. package/build/elements/input.d.ts +1 -1
  188. package/build/elements/input.js +2 -2
  189. package/build/elements/item.d.ts +9 -9
  190. package/build/elements/item.js +2 -2
  191. package/build/elements/kbd.d.ts +2 -2
  192. package/build/elements/label.d.ts +1 -1
  193. package/build/elements/menubar.d.ts +16 -16
  194. package/build/elements/menubar.js +9 -11
  195. package/build/elements/native-select.d.ts +3 -3
  196. package/build/elements/native-select.js +2 -4
  197. package/build/elements/navigation-menu.d.ts +8 -8
  198. package/build/elements/navigation-menu.js +3 -5
  199. package/build/elements/pagination.d.ts +7 -7
  200. package/build/elements/pagination.js +4 -10
  201. package/build/elements/popover.d.ts +6 -6
  202. package/build/elements/popover.js +2 -2
  203. package/build/elements/progress.d.ts +5 -5
  204. package/build/elements/progress.js +2 -3
  205. package/build/elements/radio-group.d.ts +2 -2
  206. package/build/elements/resizable.d.ts +3 -3
  207. package/build/elements/scroll-area.d.ts +2 -2
  208. package/build/elements/scroll-area.js +2 -4
  209. package/build/elements/select.d.ts +9 -9
  210. package/build/elements/select.js +4 -9
  211. package/build/elements/separator.d.ts +1 -1
  212. package/build/elements/sheet.d.ts +8 -8
  213. package/build/elements/sheet.js +2 -6
  214. package/build/elements/sidebar.d.ts +19 -19
  215. package/build/elements/sidebar.js +15 -37
  216. package/build/elements/skeleton.d.ts +1 -1
  217. package/build/elements/slider.d.ts +2 -1
  218. package/build/elements/slider.js +2 -3
  219. package/build/elements/sonner.d.ts +1 -1
  220. package/build/elements/spinner.d.ts +1 -1
  221. package/build/elements/switch.d.ts +1 -1
  222. package/build/elements/table.d.ts +8 -8
  223. package/build/elements/tabs.d.ts +4 -4
  224. package/build/elements/tabs.js +3 -3
  225. package/build/elements/textarea.d.ts +1 -1
  226. package/build/elements/textarea.js +2 -2
  227. package/build/elements/toggle-group.d.ts +3 -2
  228. package/build/elements/toggle-group.js +3 -3
  229. package/build/elements/toggle.d.ts +1 -1
  230. package/build/elements/toggle.js +2 -2
  231. package/build/elements/tooltip.d.ts +4 -4
  232. package/build/elements/tooltip.js +2 -3
  233. package/build/head.d.ts +4 -0
  234. package/build/head.js +6 -0
  235. package/build/helpers/agent-skills.d.ts +22 -0
  236. package/build/helpers/agent-skills.js +46 -0
  237. package/build/helpers/agent-skills.unit.d.ts +1 -0
  238. package/build/helpers/agent-skills.unit.js +49 -0
  239. package/build/helpers/article-search.d.ts +19 -0
  240. package/build/helpers/article-search.js +134 -0
  241. package/build/helpers/article.d.ts +13 -0
  242. package/build/helpers/article.js +17 -1
  243. package/build/helpers/dependency-dir.d.ts +5 -0
  244. package/build/helpers/dependency-dir.js +20 -0
  245. package/build/helpers/dependency-dir.unit.d.ts +1 -0
  246. package/build/helpers/dependency-dir.unit.js +25 -0
  247. package/build/helpers/dynamic-icon-resolver.d.ts +1 -1
  248. package/build/helpers/dynamic-icon.d.ts +1 -1
  249. package/build/helpers/head.d.ts +58 -0
  250. package/build/helpers/head.js +80 -0
  251. package/build/helpers/head.unit.d.ts +1 -0
  252. package/build/helpers/head.unit.js +110 -0
  253. package/build/helpers/llms.d.ts +33 -0
  254. package/build/helpers/llms.js +72 -0
  255. package/build/helpers/llms.unit.d.ts +1 -0
  256. package/build/helpers/llms.unit.js +98 -0
  257. package/build/helpers/nav-items.d.ts +11 -0
  258. package/build/helpers/nav-items.js +65 -0
  259. package/build/helpers/render-inline-markdown.d.ts +1 -0
  260. package/build/helpers/render-inline-markdown.js +34 -0
  261. package/build/helpers/render-inline-markdown.unit.d.ts +1 -0
  262. package/build/helpers/render-inline-markdown.unit.js +20 -0
  263. package/build/helpers/search-highlight.d.ts +8 -0
  264. package/build/helpers/search-highlight.js +59 -0
  265. package/build/helpers/search-highlight.unit.d.ts +1 -0
  266. package/build/helpers/search-highlight.unit.js +36 -0
  267. package/build/helpers/target-export.d.ts +9 -0
  268. package/build/helpers/target-export.js +24 -0
  269. package/build/helpers/target-export.unit.d.ts +1 -0
  270. package/build/helpers/target-export.unit.js +25 -0
  271. package/build/helpers/theme.d.ts +6 -2
  272. package/build/helpers/theme.js +40 -7
  273. package/build/helpers/theme.unit.d.ts +1 -0
  274. package/build/helpers/theme.unit.js +30 -0
  275. package/build/hooks/hydrated.d.ts +2 -0
  276. package/build/hooks/hydrated.js +15 -0
  277. package/build/index.d.ts +1 -0
  278. package/build/index.js +1 -1
  279. package/build/models/config.d.ts +52 -6
  280. package/build/models/config.js +17 -1
  281. package/build/models/section.d.ts +4 -2
  282. package/build/models/section.js +1 -1
  283. package/build/plugins/vite-agents.d.ts +17 -0
  284. package/build/plugins/vite-agents.js +109 -0
  285. package/build/plugins/vite-agents.unit.d.ts +1 -0
  286. package/build/plugins/vite-agents.unit.js +20 -0
  287. package/build/plugins/vite-livemark.d.ts +4 -5
  288. package/build/plugins/vite-livemark.js +56 -90
  289. package/build/plugins/vite-robots.d.ts +4 -2
  290. package/build/plugins/vite-robots.js +15 -2
  291. package/build/plugins/vite-robots.unit.js +5 -1
  292. package/build/program.js +4 -2
  293. package/build/routes/$.d.ts +4 -1
  294. package/build/routes/$.js +20 -9
  295. package/build/routes/__root.js +29 -63
  296. package/build/settings.js +2 -1
  297. package/build/vite.config.js +3 -1
  298. package/package.json +69 -66
  299. package/{.agents/skills → skills}/livemark-authoring/SKILL.md +2 -2
  300. package/{.agents/skills → skills}/livemark-configuration/SKILL.md +16 -5
  301. package/skills/livemark-customization/SKILL.md +146 -0
  302. package/source/actions/config/define.unit.ts +9 -0
  303. package/source/actions/escape/copy.ts +35 -0
  304. package/source/actions/escape/copy.unit.ts +51 -0
  305. package/source/actions/escape/list.ts +30 -0
  306. package/source/actions/escape/list.unit.ts +42 -0
  307. package/source/actions/target/build.ts +49 -69
  308. package/source/actions/target/build.unit.ts +95 -0
  309. package/source/actions/target/clear.ts +14 -0
  310. package/source/actions/target/clear.unit.ts +25 -0
  311. package/source/commands/build.ts +0 -1
  312. package/source/commands/clear.ts +15 -0
  313. package/source/commands/escape.ts +42 -59
  314. package/source/commands/preview.ts +0 -1
  315. package/source/commands/start.ts +0 -1
  316. package/source/components/AnsiCode.tsx +1 -1
  317. package/source/components/Article.tsx +8 -4
  318. package/source/components/Banner.tsx +12 -2
  319. package/source/components/BlogIndex.tsx +1 -1
  320. package/source/components/Callout.tsx +4 -6
  321. package/source/components/ChangelogIndex.tsx +1 -1
  322. package/source/components/ChoiceList.tsx +69 -0
  323. package/source/components/CodeBlock.tsx +6 -6
  324. package/source/components/CodeTabs.tsx +1 -1
  325. package/source/components/ContentTabs.tsx +1 -1
  326. package/source/components/DefaultCatchBoundary.tsx +4 -6
  327. package/source/components/Details.tsx +1 -1
  328. package/source/components/FileTree.tsx +1 -1
  329. package/source/components/Header.tsx +52 -0
  330. package/source/components/HeaderMenu.tsx +41 -0
  331. package/source/components/HeaderNav.tsx +145 -0
  332. package/source/components/HeaderSearch.tsx +77 -0
  333. package/source/components/InlineBadge.tsx +1 -1
  334. package/source/components/InlineToc.tsx +1 -1
  335. package/source/components/Layout.tsx +16 -160
  336. package/source/components/LinkButton.tsx +9 -1
  337. package/source/components/MenuSheet.tsx +92 -0
  338. package/source/components/MermaidRenderer.tsx +19 -8
  339. package/source/components/NotFound.tsx +3 -5
  340. package/source/components/PackageTabs.tsx +2 -2
  341. package/source/components/PageToolbar.tsx +11 -3
  342. package/source/components/PrevNext.tsx +2 -2
  343. package/source/components/Root.tsx +13 -0
  344. package/source/components/Search.tsx +28 -8
  345. package/source/components/SearchDialog.tsx +15 -189
  346. package/source/components/SheetPanel.tsx +69 -0
  347. package/source/components/Sidebar.tsx +27 -14
  348. package/source/components/SidebarArticles.tsx +4 -3
  349. package/source/components/SidebarLinks.tsx +3 -2
  350. package/source/components/SidebarPosts.tsx +3 -2
  351. package/source/components/SidebarReleases.tsx +3 -2
  352. package/source/components/SidebarSectionPicker.tsx +70 -0
  353. package/source/components/SiteTitle.tsx +5 -5
  354. package/source/components/SoundCloud.tsx +1 -1
  355. package/source/components/TagIndex.tsx +1 -1
  356. package/source/components/Theme.tsx +55 -29
  357. package/source/components/Toc.tsx +76 -55
  358. package/source/components/YouTube.tsx +1 -1
  359. package/source/content/article.ts +8 -13
  360. package/source/content-collections.ts +5 -4
  361. package/source/elements/alert-dialog.tsx +1 -1
  362. package/source/elements/alert.tsx +1 -1
  363. package/source/elements/badge.tsx +4 -0
  364. package/source/elements/button-group.tsx +4 -4
  365. package/source/elements/button.tsx +10 -6
  366. package/source/elements/calendar.tsx +1 -1
  367. package/source/elements/card.tsx +3 -3
  368. package/source/elements/command.tsx +7 -4
  369. package/source/elements/context-menu.tsx +5 -5
  370. package/source/elements/dialog.tsx +1 -1
  371. package/source/elements/drawer.tsx +59 -43
  372. package/source/elements/dropdown-menu.tsx +6 -6
  373. package/source/elements/empty.tsx +1 -1
  374. package/source/elements/field.tsx +1 -1
  375. package/source/elements/hover-card.tsx +1 -1
  376. package/source/elements/input-group.tsx +4 -4
  377. package/source/elements/input.tsx +1 -1
  378. package/source/elements/item.tsx +1 -1
  379. package/source/elements/menubar.tsx +8 -8
  380. package/source/elements/native-select.tsx +1 -1
  381. package/source/elements/popover.tsx +1 -1
  382. package/source/elements/select.tsx +3 -3
  383. package/source/elements/sidebar.tsx +8 -21
  384. package/source/elements/tabs.tsx +2 -2
  385. package/source/elements/textarea.tsx +1 -1
  386. package/source/elements/toggle-group.tsx +2 -2
  387. package/source/elements/toggle.tsx +1 -1
  388. package/source/env.d.ts +1 -0
  389. package/source/head.ts +7 -0
  390. package/source/helpers/agent-skills.ts +59 -0
  391. package/source/helpers/agent-skills.unit.ts +56 -0
  392. package/source/helpers/article-search.tsx +192 -0
  393. package/source/helpers/article.ts +17 -0
  394. package/source/helpers/dependency-dir.ts +20 -0
  395. package/source/helpers/dependency-dir.unit.ts +39 -0
  396. package/source/helpers/head.ts +105 -0
  397. package/source/helpers/head.unit.ts +118 -0
  398. package/source/helpers/llms.ts +109 -0
  399. package/source/helpers/llms.unit.ts +115 -0
  400. package/source/helpers/nav-items.tsx +102 -0
  401. package/source/helpers/render-inline-markdown.ts +44 -0
  402. package/source/helpers/render-inline-markdown.unit.ts +30 -0
  403. package/source/helpers/search-highlight.ts +73 -0
  404. package/source/helpers/search-highlight.unit.ts +42 -0
  405. package/source/helpers/target-export.ts +28 -0
  406. package/source/helpers/target-export.unit.ts +37 -0
  407. package/source/helpers/theme.ts +40 -6
  408. package/source/helpers/theme.unit.ts +32 -0
  409. package/source/hooks/hydrated.ts +18 -0
  410. package/source/index.ts +1 -0
  411. package/source/models/config.ts +24 -0
  412. package/source/models/section.ts +3 -0
  413. package/source/plugins/vite-agents.ts +135 -0
  414. package/source/plugins/vite-agents.unit.ts +26 -0
  415. package/source/plugins/vite-livemark.ts +54 -95
  416. package/source/plugins/vite-robots.ts +18 -3
  417. package/source/plugins/vite-robots.unit.ts +7 -0
  418. package/source/program.ts +2 -0
  419. package/source/routes/$.tsx +27 -9
  420. package/source/routes/__root.tsx +51 -71
  421. package/source/settings.ts +1 -0
  422. package/source/styles/general.css +59 -11
  423. package/source/styles/markdown.css +13 -7
  424. package/source/styles/shape.css +14 -0
  425. package/source/vite.config.ts +2 -0
  426. package/.agents/skills/change-review/SKILL.md +0 -47
  427. package/.agents/skills/livemark-customization/SKILL.md +0 -118
@@ -0,0 +1,59 @@
1
+ import { createHash } from "node:crypto"
2
+ import { globSync, readFileSync } from "node:fs"
3
+ import { resolve, sep } from "node:path"
4
+ import { parse } from "yaml"
5
+ import { z } from "zod"
6
+
7
+ const SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json"
8
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/
9
+
10
+ const SkillFrontmatter = z.object({
11
+ name: z
12
+ .string()
13
+ .max(64)
14
+ .regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
15
+ description: z.string().min(1).max(1024),
16
+ })
17
+
18
+ /** A `SKILL.md` published for agents, with the raw bytes its digest covers. */
19
+ export interface AgentSkill {
20
+ name: string
21
+ description: string
22
+ source: Buffer
23
+ }
24
+
25
+ /** Read every `SKILL.md` the `skills` globs match, in a stable order. */
26
+ export function loadAgentSkills(props: { root: string; patterns: string[] }) {
27
+ // Windows globs with `\`, which would leak into messages and change the sort.
28
+ return globSync(props.patterns, { cwd: props.root })
29
+ .map(file => file.split(sep).join("/"))
30
+ .sort()
31
+ .map(file => {
32
+ const source = readFileSync(resolve(props.root, file))
33
+ const frontmatter = FRONTMATTER_RE.exec(source.toString("utf-8"))?.[1] ?? ""
34
+ const parsed = SkillFrontmatter.safeParse(parse(frontmatter))
35
+ if (!parsed.success) {
36
+ throw new Error(
37
+ `${file}: a published skill needs a frontmatter \`name\` (lowercase, hyphenated) and \`description\``,
38
+ )
39
+ }
40
+ return { ...parsed.data, source }
41
+ })
42
+ }
43
+
44
+ /** Where a skill is published inside the build output (no base). */
45
+ export function buildSkillFileName(name: string) {
46
+ return `.well-known/agent-skills/${name}/SKILL.md`
47
+ }
48
+
49
+ /** Render the Agent Skills discovery index (v0.2.0) for the published skills. */
50
+ export function renderSkillsIndex(props: { skills: AgentSkill[]; base?: string }) {
51
+ const skills = props.skills.map(skill => ({
52
+ name: skill.name,
53
+ type: "skill-md",
54
+ description: skill.description,
55
+ url: `${props.base ?? ""}/${buildSkillFileName(skill.name)}`,
56
+ digest: `sha256:${createHash("sha256").update(skill.source).digest("hex")}`,
57
+ }))
58
+ return `${JSON.stringify({ $schema: SKILLS_SCHEMA, skills }, null, 2)}\n`
59
+ }
@@ -0,0 +1,56 @@
1
+ import { createHash } from "node:crypto"
2
+ import { mkdirSync, writeFileSync } from "node:fs"
3
+ import { join } from "node:path"
4
+ import { describe, expect, it } from "vite-plus/test"
5
+ import { temporaryDirectory } from "tempy"
6
+ import { loadAgentSkills, renderSkillsIndex } from "./agent-skills.ts"
7
+
8
+ function writeSkill(root: string, dir: string, source: string) {
9
+ mkdirSync(join(root, "skills", dir), { recursive: true })
10
+ writeFileSync(join(root, "skills", dir, "SKILL.md"), source)
11
+ }
12
+
13
+ describe("loadAgentSkills", () => {
14
+ it("reads name and description from each matched SKILL.md", () => {
15
+ const root = temporaryDirectory()
16
+ writeSkill(root, "b", "---\nname: beta\ndescription: >\n Folded\n text\n---\n# B\n")
17
+ writeSkill(root, "a", "---\nname: alpha\ndescription: First\n---\n# A\n")
18
+ const skills = loadAgentSkills({ root, patterns: ["skills/*/SKILL.md"] })
19
+ expect(skills.map(s => [s.name, s.description])).toEqual([
20
+ ["alpha", "First"],
21
+ ["beta", "Folded text\n"],
22
+ ])
23
+ })
24
+
25
+ it("rejects a skill whose name agents cannot address", () => {
26
+ const root = temporaryDirectory()
27
+ writeSkill(root, "a", "---\nname: Not Valid\ndescription: First\n---\n")
28
+ expect(() => loadAgentSkills({ root, patterns: ["skills/*/SKILL.md"] })).toThrow(
29
+ "skills/a/SKILL.md",
30
+ )
31
+ })
32
+ })
33
+
34
+ describe("renderSkillsIndex", () => {
35
+ it("publishes each skill with a digest of its exact bytes", () => {
36
+ const source = Buffer.from("---\nname: alpha\ndescription: First\n---\n")
37
+ const index = JSON.parse(
38
+ renderSkillsIndex({
39
+ skills: [{ name: "alpha", description: "First", source }],
40
+ base: "/repo",
41
+ }),
42
+ )
43
+ expect(index).toEqual({
44
+ $schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
45
+ skills: [
46
+ {
47
+ name: "alpha",
48
+ type: "skill-md",
49
+ description: "First",
50
+ url: "/repo/.well-known/agent-skills/alpha/SKILL.md",
51
+ digest: `sha256:${createHash("sha256").update(source).digest("hex")}`,
52
+ },
53
+ ],
54
+ })
55
+ })
56
+ })
@@ -0,0 +1,192 @@
1
+ import { useState, useRef, useCallback, useMemo } from "react"
2
+ import { useNavigate } from "@tanstack/react-router"
3
+ import { create, insertMultiple, search } from "@orama/orama"
4
+ import { sortedArticles } from "../content/article.ts"
5
+ import { resolveArticleIcon } from "./article-icon.ts"
6
+ import { buildHighlightPattern, findTextMatches } from "./search-highlight.ts"
7
+ import { CommandGroup, CommandItem } from "../elements/command.tsx"
8
+
9
+ interface SearchResult {
10
+ title: string
11
+ path: string
12
+ icon?: string
13
+ snippet: string
14
+ group: string
15
+ }
16
+
17
+ export function useArticleSearch() {
18
+ const [results, setResults] = useState<SearchResult[]>([])
19
+ const [query, setQuery] = useState("")
20
+ const dbRef = useRef<Awaited<ReturnType<typeof create>> | null>(null)
21
+ const navigate = useNavigate()
22
+
23
+ const ensureIndex = useCallback(async () => {
24
+ if (dbRef.current) return dbRef.current
25
+ const db = await create({
26
+ schema: {
27
+ title: "string",
28
+ description: "string",
29
+ searchText: "string",
30
+ },
31
+ })
32
+ await insertMultiple(
33
+ db,
34
+ sortedArticles.map(a => ({
35
+ title: a.title,
36
+ description: a.description ?? "",
37
+ searchText: a.searchText,
38
+ })),
39
+ )
40
+ dbRef.current = db
41
+ return db
42
+ }, [])
43
+
44
+ const searchArticles = useCallback(
45
+ async (term: string) => {
46
+ setQuery(term)
47
+ if (!term.trim()) {
48
+ setResults([])
49
+ return
50
+ }
51
+ const db = await ensureIndex()
52
+ const hits = await search(db, {
53
+ term,
54
+ properties: ["title", "description", "searchText"],
55
+ tolerance: 1,
56
+ boost: { title: 3, description: 2 },
57
+ })
58
+ setResults(
59
+ hits.hits.map(hit => {
60
+ const article = sortedArticles.find(a => a.title === hit.document.title)
61
+ if (!article)
62
+ return toResult(
63
+ {
64
+ ...sortedArticles[0],
65
+ title: hit.document.title,
66
+ path: "",
67
+ searchText: hit.document.description,
68
+ description: hit.document.description,
69
+ icon: "",
70
+ },
71
+ term,
72
+ )
73
+ return toResult(article, term)
74
+ }),
75
+ )
76
+ },
77
+ [ensureIndex],
78
+ )
79
+
80
+ const selectArticle = useCallback(
81
+ (path: string) => {
82
+ setQuery("")
83
+ setResults([])
84
+ navigate({
85
+ to: "/$",
86
+ params: { _splat: path.replace(/^\/|\/$/g, "") },
87
+ search: { highlight: query.trim() || undefined },
88
+ })
89
+ },
90
+ [navigate, query],
91
+ )
92
+
93
+ const grouped = useMemo(() => {
94
+ const groups = new Map<string, SearchResult[]>()
95
+ for (const r of results) {
96
+ const list = groups.get(r.group) ?? []
97
+ list.push(r)
98
+ groups.set(r.group, list)
99
+ }
100
+ return groups
101
+ }, [results])
102
+
103
+ return { query, grouped, searchArticles, selectArticle }
104
+ }
105
+
106
+ export function SearchResultGroups(props: {
107
+ query: string
108
+ grouped: Map<string, SearchResult[]>
109
+ onSelect: (path: string) => void
110
+ }) {
111
+ return [...props.grouped.entries()].map(([group, items]) => (
112
+ <CommandGroup key={group} heading={group}>
113
+ {items.map(result => {
114
+ const Icon = resolveArticleIcon(result.icon)
115
+ return (
116
+ <CommandItem
117
+ key={result.path}
118
+ value={result.path}
119
+ onSelect={() => props.onSelect(result.path)}
120
+ className="py-2.5"
121
+ >
122
+ {Icon && <Icon className="size-5 shrink-0 mt-0.5 self-start opacity-60" />}
123
+ <div className="flex flex-col gap-0.5 min-w-0">
124
+ <span className="text-base font-medium">{result.title}</span>
125
+ {result.snippet && (
126
+ <span className="text-sm text-muted-foreground line-clamp-1">
127
+ <HighlightedText text={result.snippet} term={props.query} />
128
+ </span>
129
+ )}
130
+ </div>
131
+ </CommandItem>
132
+ )
133
+ })}
134
+ </CommandGroup>
135
+ ))
136
+ }
137
+
138
+ function toResult(article: (typeof sortedArticles)[number], term: string): SearchResult {
139
+ return {
140
+ title: article.title,
141
+ path: article.path,
142
+ icon: article.icon,
143
+ snippet: term
144
+ ? extractSnippet(article.searchText, term)
145
+ : (article.description ?? ""),
146
+ group: groupFor(article.path),
147
+ }
148
+ }
149
+
150
+ function groupFor(pathname: string) {
151
+ const segments = pathname.replace(/^\/|\/$/g, "").split("/")
152
+ if (segments.length <= 2) return "Pages"
153
+ return segments
154
+ .slice(1, -1)
155
+ .map(s =>
156
+ s
157
+ .split("-")
158
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
159
+ .join(" "),
160
+ )
161
+ .join(" › ")
162
+ }
163
+
164
+ function extractSnippet(text: string, term: string) {
165
+ const pattern = buildHighlightPattern(term)
166
+ const [match] = pattern ? findTextMatches(text, pattern) : []
167
+ if (!match) return text.slice(0, 120)
168
+ const [matchStart, matchEnd] = match
169
+ const start = Math.max(0, matchStart - 40)
170
+ const end = Math.min(text.length, matchEnd + 80)
171
+ const prefix = start > 0 ? "…" : ""
172
+ const suffix = end < text.length ? "…" : ""
173
+ return `${prefix}${text.slice(start, end)}${suffix}`
174
+ }
175
+
176
+ function HighlightedText(props: { text: string; term: string }) {
177
+ const pattern = buildHighlightPattern(props.term)
178
+ if (!pattern) return <>{props.text}</>
179
+ const parts: React.ReactNode[] = []
180
+ let cursor = 0
181
+ for (const [start, end] of findTextMatches(props.text, pattern)) {
182
+ parts.push(props.text.slice(cursor, start))
183
+ parts.push(
184
+ <span key={start} className="text-foreground font-medium">
185
+ {props.text.slice(start, end)}
186
+ </span>,
187
+ )
188
+ cursor = end
189
+ }
190
+ parts.push(props.text.slice(cursor))
191
+ return <>{parts}</>
192
+ }
@@ -50,3 +50,20 @@ export function flattenArticleTree(nodes: ArticleNode[]) {
50
50
  }
51
51
  return result
52
52
  }
53
+
54
+ /** Sort comparator on frontmatter `order`: positive ascending, then unordered,
55
+ * then negative (so `order: -1` lands last) */
56
+ export function compareArticleOrder(a: { order?: number }, b: { order?: number }) {
57
+ return orderKey(a.order) - orderKey(b.order)
58
+ }
59
+
60
+ /** Sort comparator putting the newest `date` first, as blog and changelog list */
61
+ export function compareArticleDate(a: { date?: string }, b: { date?: string }) {
62
+ return new Date(b.date ?? 0).getTime() - new Date(a.date ?? 0).getTime()
63
+ }
64
+
65
+ function orderKey(order?: number) {
66
+ if (order === undefined) return Number.MAX_SAFE_INTEGER / 2
67
+ if (order < 0) return Number.MAX_SAFE_INTEGER + order
68
+ return order
69
+ }
@@ -0,0 +1,20 @@
1
+ import { existsSync, readdirSync, realpathSync } from "node:fs"
2
+ import { basename, dirname, join } from "node:path"
3
+
4
+ /**
5
+ * Locate the directory holding livemark's own dependencies, which the project's target
6
+ * links to as its `node_modules`.
7
+ */
8
+ export function resolveDependencyDir(sourceDir: string) {
9
+ const packageDir = realpathSync(dirname(sourceDir))
10
+ const parent = dirname(packageDir)
11
+ const nested = join(packageDir, "node_modules")
12
+ // pnpm gives an installed package a `.bin`-only `node_modules`; its deps sit beside it
13
+ // in the store. Only real packages there (a checkout, npm's conflict copies) count.
14
+ if (hasPackages(nested) || basename(parent) !== "node_modules") return nested
15
+ return parent
16
+ }
17
+
18
+ function hasPackages(dir: string) {
19
+ return existsSync(dir) && readdirSync(dir).some(name => !name.startsWith("."))
20
+ }
@@ -0,0 +1,39 @@
1
+ import { realpathSync } from "node:fs"
2
+ import { mkdir } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { temporaryDirectoryTask } from "tempy"
5
+ import { describe, expect, it } from "vite-plus/test"
6
+ import { resolveDependencyDir } from "./dependency-dir.ts"
7
+
8
+ describe("resolveDependencyDir", () => {
9
+ it("uses the store dir beside an isolated install", () =>
10
+ temporaryDirectoryTask(async root => {
11
+ const store = join(root, ".pnpm/livemark@1.0.0/node_modules")
12
+ await mkdir(join(store, "livemark/source"), { recursive: true })
13
+ await mkdir(join(store, "livemark/node_modules/.bin"), { recursive: true })
14
+
15
+ expect(resolveDependencyDir(join(store, "livemark/source"))).toBe(
16
+ realpathSync(store),
17
+ )
18
+ }))
19
+
20
+ it("uses the package's own node_modules when it has one", () =>
21
+ temporaryDirectoryTask(async root => {
22
+ const packageDir = join(root, "node_modules/livemark")
23
+ await mkdir(join(packageDir, "source"), { recursive: true })
24
+ await mkdir(join(packageDir, "node_modules/zod"), { recursive: true })
25
+
26
+ expect(resolveDependencyDir(join(packageDir, "source"))).toBe(
27
+ join(realpathSync(packageDir), "node_modules"),
28
+ )
29
+ }))
30
+
31
+ it("uses the checkout's node_modules outside an install", () =>
32
+ temporaryDirectoryTask(async root => {
33
+ await mkdir(join(root, "livemark/source"), { recursive: true })
34
+
35
+ expect(resolveDependencyDir(join(root, "livemark/source"))).toBe(
36
+ join(realpathSync(root), "livemark/node_modules"),
37
+ )
38
+ }))
39
+ })
@@ -0,0 +1,105 @@
1
+ import type * as React from "react"
2
+ import type { WebsiteConfig } from "../models/config.ts"
3
+ import { faviconType } from "./favicon.ts"
4
+ import { prefixUrl } from "./prefix-url.ts"
5
+ import { buildThemeScript } from "./theme.ts"
6
+
7
+ /** Site-wide `<head>` tags the root route renders on every page. */
8
+ export interface RootHead {
9
+ meta: Array<React.JSX.IntrinsicElements["meta"]>
10
+ links: Array<React.JSX.IntrinsicElements["link"]>
11
+ scripts: Array<React.JSX.IntrinsicElements["script"]>
12
+ }
13
+
14
+ export function buildRootHead(props: {
15
+ config: WebsiteConfig
16
+ defaultFavicon: string
17
+ stylesheets: string[]
18
+ }) {
19
+ const config = props.config
20
+ const favicon = prefixUrl(
21
+ config.favicon ?? config.logo ?? props.defaultFavicon,
22
+ config.base,
23
+ )
24
+ const feedLinks = config.site
25
+ ? (config.sections ?? [])
26
+ .filter(s => s.type === "blog")
27
+ .map(s => ({
28
+ rel: "alternate",
29
+ type: "application/rss+xml",
30
+ title: `${s.siteTitle ?? s.title} RSS Feed`,
31
+ href: prefixUrl(`${s.prefix}rss.xml`, config.base),
32
+ }))
33
+ : []
34
+ return {
35
+ meta: [
36
+ { charSet: "utf-8" },
37
+ { title: config.title },
38
+ { name: "description", content: config.description },
39
+ { property: "og:title", content: config.title },
40
+ { property: "og:description", content: config.description },
41
+ { property: "og:site_name", content: config.title },
42
+ { property: "og:type", content: "website" },
43
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
44
+ ...(config.head?.meta ?? []),
45
+ ],
46
+ links: [
47
+ { rel: "icon", href: favicon, type: faviconType(favicon) },
48
+ { rel: "shortcut icon", href: favicon, type: faviconType(favicon) },
49
+ ...props.stylesheets.map(href => ({ rel: "stylesheet", href })),
50
+ ...feedLinks,
51
+ ...(config.head?.links ?? []),
52
+ ],
53
+ scripts: [{ children: buildThemeScript() }, ...(config.head?.scripts ?? [])],
54
+ }
55
+ }
56
+
57
+ export function buildPageHead(props: {
58
+ config: WebsiteConfig
59
+ path: string
60
+ title?: string
61
+ description?: string
62
+ image?: string
63
+ isArticle?: boolean
64
+ }) {
65
+ const site = props.config.site?.replace(/\/+$/, "")
66
+ const url = site ? buildAbsoluteUrl(site, props.config.base, props.path) : undefined
67
+ const image =
68
+ site && props.image
69
+ ? buildAbsoluteUrl(site, props.config.base, props.image)
70
+ : undefined
71
+ return {
72
+ meta: [
73
+ ...(props.title
74
+ ? [{ title: props.title }, { property: "og:title", content: props.title }]
75
+ : []),
76
+ ...(props.description
77
+ ? [
78
+ { name: "description", content: props.description },
79
+ { property: "og:description", content: props.description },
80
+ ]
81
+ : []),
82
+ ...(props.isArticle ? [{ property: "og:type", content: "article" }] : []),
83
+ ...(url ? [{ property: "og:url", content: url }] : []),
84
+ ...(image ? [{ property: "og:image", content: image }] : []),
85
+ ],
86
+ links: [
87
+ ...(url ? [{ rel: "canonical", href: url }] : []),
88
+ ...(props.isArticle
89
+ ? [
90
+ {
91
+ rel: "alternate",
92
+ type: "text/markdown",
93
+ href: prefixUrl(`${props.path}index.md`, props.config.base),
94
+ },
95
+ ]
96
+ : []),
97
+ ],
98
+ }
99
+ }
100
+
101
+ function buildAbsoluteUrl(site: string, base: string | undefined, url: string) {
102
+ const prefixed = prefixUrl(url, base)
103
+ if (!prefixed.startsWith("/") || prefixed.startsWith("//")) return prefixed
104
+ return `${site}${prefixed}`
105
+ }
@@ -0,0 +1,118 @@
1
+ import { describe, expect, it } from "vite-plus/test"
2
+ import type { WebsiteConfig } from "../models/config.ts"
3
+ import { buildPageHead, buildRootHead } from "./head.ts"
4
+
5
+ const config: WebsiteConfig = { title: "Docs", description: "Blurb" }
6
+
7
+ describe("buildRootHead", () => {
8
+ it("builds the default site tags", () => {
9
+ const head = buildRootHead({
10
+ config,
11
+ defaultFavicon: "/logo.svg",
12
+ stylesheets: ["/a.css"],
13
+ })
14
+ expect(head.meta).toContainEqual({ property: "og:title", content: "Docs" })
15
+ expect(head.links).toContainEqual({
16
+ rel: "icon",
17
+ href: "/logo.svg",
18
+ type: "image/svg+xml",
19
+ })
20
+ expect(head.links).toContainEqual({ rel: "stylesheet", href: "/a.css" })
21
+ expect(head.scripts).toHaveLength(1)
22
+ })
23
+
24
+ it("appends config head tags after the defaults", () => {
25
+ const head = buildRootHead({
26
+ config: {
27
+ ...config,
28
+ head: {
29
+ meta: [{ property: "og:type", content: "book" }],
30
+ links: [{ rel: "me", href: "https://x.com/me" }],
31
+ scripts: [{ src: "https://cdn.example.com/a.js" }],
32
+ },
33
+ },
34
+ defaultFavicon: "/logo.svg",
35
+ stylesheets: [],
36
+ })
37
+ expect(head.meta.at(-1)).toEqual({ property: "og:type", content: "book" })
38
+ expect(head.links.at(-1)).toEqual({ rel: "me", href: "https://x.com/me" })
39
+ expect(head.scripts.at(-1)).toEqual({ src: "https://cdn.example.com/a.js" })
40
+ })
41
+
42
+ it("links blog feeds only when site is set", () => {
43
+ const sections: WebsiteConfig["sections"] = [
44
+ { type: "blog", title: "Blog", prefix: "/blog/", position: "header" },
45
+ ]
46
+ const withoutSite = buildRootHead({
47
+ config: { ...config, sections },
48
+ defaultFavicon: "/logo.svg",
49
+ stylesheets: [],
50
+ })
51
+ const withSite = buildRootHead({
52
+ config: { ...config, sections, site: "https://example.com", base: "/repo" },
53
+ defaultFavicon: "/logo.svg",
54
+ stylesheets: [],
55
+ })
56
+ expect(withoutSite.links.some(l => l.rel === "alternate")).toBe(false)
57
+ expect(withSite.links).toContainEqual({
58
+ rel: "alternate",
59
+ type: "application/rss+xml",
60
+ title: "Blog RSS Feed",
61
+ href: "/repo/blog/rss.xml",
62
+ })
63
+ })
64
+ })
65
+
66
+ describe("buildPageHead", () => {
67
+ it("emits title, description and article type", () => {
68
+ const head = buildPageHead({
69
+ config,
70
+ path: "/guide/",
71
+ title: "Guide",
72
+ description: "How to",
73
+ isArticle: true,
74
+ })
75
+ expect(head.meta).toEqual([
76
+ { title: "Guide" },
77
+ { property: "og:title", content: "Guide" },
78
+ { name: "description", content: "How to" },
79
+ { property: "og:description", content: "How to" },
80
+ { property: "og:type", content: "article" },
81
+ ])
82
+ expect(head.links).toEqual([
83
+ { rel: "alternate", type: "text/markdown", href: "/guide/index.md" },
84
+ ])
85
+ })
86
+
87
+ it("adds absolute url, canonical and image when site is set", () => {
88
+ const head = buildPageHead({
89
+ config: { ...config, site: "https://example.com/", base: "/repo" },
90
+ path: "/guide/",
91
+ image: "/repo/assets/cover.png",
92
+ })
93
+ expect(head.meta).toEqual([
94
+ { property: "og:url", content: "https://example.com/repo/guide/" },
95
+ { property: "og:image", content: "https://example.com/repo/assets/cover.png" },
96
+ ])
97
+ expect(head.links).toEqual([
98
+ { rel: "canonical", href: "https://example.com/repo/guide/" },
99
+ ])
100
+ })
101
+
102
+ it("keeps an already absolute image", () => {
103
+ const head = buildPageHead({
104
+ config: { ...config, site: "https://example.com" },
105
+ path: "/",
106
+ image: "https://cdn.example.com/cover.png",
107
+ })
108
+ expect(head.meta).toContainEqual({
109
+ property: "og:image",
110
+ content: "https://cdn.example.com/cover.png",
111
+ })
112
+ })
113
+
114
+ it("skips og:image without site", () => {
115
+ const head = buildPageHead({ config, path: "/", image: "/cover.png" })
116
+ expect(head.meta).toEqual([])
117
+ })
118
+ })