@clarify-labs/cli 0.10.3 → 0.11.1

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 (38) hide show
  1. package/output/cli/commands/mcp.d.ts +27 -0
  2. package/output/cli/mcp/orama-loader.d.ts +56 -0
  3. package/output/cli/mcp/search-server.d.ts +34 -0
  4. package/output/cli/program.d.ts +0 -1
  5. package/output/cli.cjs +294 -13
  6. package/output/cli.cjs.map +1 -1
  7. package/output/cli.js +295 -14
  8. package/output/cli.js.map +1 -1
  9. package/output/core/config/config-schema.d.ts +138 -253
  10. package/output/core/config/config.d.ts +2 -1
  11. package/output/core/config/options.d.ts +0 -8
  12. package/output/core/config/user-config.d.ts +4 -3
  13. package/output/core/plugin/hooks.d.ts +0 -1
  14. package/output/core/project/project-info.d.ts +3 -3
  15. package/output/core/runtime/html-template.d.ts +0 -6
  16. package/output/core/runtime/ssg.d.ts +1 -1
  17. package/output/core/runtime/virtual-modules.d.ts +1 -5
  18. package/output/{engine-BDdPszs2.cjs → engine-Cbn_coqg.cjs} +569 -170
  19. package/output/engine-Cbn_coqg.cjs.map +1 -0
  20. package/output/{engine-CyFtr8Pz.js → engine-CxnKEKv8.js} +558 -171
  21. package/output/engine-CxnKEKv8.js.map +1 -0
  22. package/output/index.cjs +1 -1
  23. package/output/index.js +1 -1
  24. package/output/parsers/routes/routes.d.ts +7 -11
  25. package/output/parsers/routes/routes.test-utils.d.ts +2 -2
  26. package/output/plugins/content-artifacts/artifacts.d.ts +0 -2
  27. package/output/plugins/mcp-search/index.d.ts +15 -0
  28. package/output/plugins/mcp-search/mcp-config.d.ts +72 -0
  29. package/output/plugins/mcp-search/orama-index.d.ts +153 -0
  30. package/output/plugins/openapi/virtual-modules.d.ts +0 -1
  31. package/output/plugins/page-search/index.d.ts +2 -0
  32. package/output/plugins/source-links/source-links.d.ts +3 -3
  33. package/output/types.d.ts +64 -33
  34. package/package.json +6 -4
  35. package/output/engine-BDdPszs2.cjs.map +0 -1
  36. package/output/engine-CyFtr8Pz.js.map +0 -1
  37. package/output/plugins/search-index/index.d.ts +0 -2
  38. /package/output/plugins/{search-index → page-search}/search-language.d.ts +0 -0
@@ -18,6 +18,8 @@ import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rea
18
18
  import { build, loadConfigFromFile } from "vite";
19
19
  import { z } from "zod";
20
20
  import { stringify } from "yaml";
21
+ import { decode, encode } from "@msgpack/msgpack";
22
+ import { create, insertMultiple, load, save, search } from "@orama/orama";
21
23
  import { fileURLToPath, pathToFileURL } from "node:url";
22
24
  import SwaggerParser from "@apidevtools/swagger-parser";
23
25
  import { tmpdir } from "node:os";
@@ -379,10 +381,10 @@ function frontmatterKeywords(frontmatter) {
379
381
  const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
380
382
  return keywords.length > 0 ? keywords : void 0;
381
383
  }
382
- function withAlternates(route, routes, i18n) {
384
+ function withAlternates(route, routes, locales) {
383
385
  const basePath = route.basePath ?? route.path;
384
386
  const routeByLocaleAndBase = new Map(routes.filter((r) => !r.locale || r.path === `/${r.locale}` || r.path.startsWith(`/${r.locale}/`)).map((r) => [`${r.locale ?? ""}:${r.basePath ?? r.path}`, r]));
385
- const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
387
+ const alternates = Object.fromEntries(locales.locales.flatMap((locale) => {
386
388
  const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
387
389
  return alternate ? [[locale.code, alternate.path]] : [];
388
390
  }));
@@ -395,11 +397,11 @@ function withAlternates(route, routes, i18n) {
395
397
  * 为默认语言的 locale-prefixed 路由生成无前缀的"裸路径别名",方便不带语言
396
398
  * 前缀的 URL 也能访问。标记 `isBareAlias` 以便搜索索引生成时过滤重复索引。
397
399
  */
398
- function generateBareAliases(routes, i18n) {
400
+ function generateBareAliases(routes, locales) {
399
401
  const bareRoutes = [];
400
402
  const seenBare = new Set(routes.map((r) => r.path));
401
403
  for (const route of routes) {
402
- if (route.locale !== i18n.defaultLocale) continue;
404
+ if (route.locale !== locales.default) continue;
403
405
  const bp = route.basePath ?? route.path;
404
406
  if (bp === route.path || seenBare.has(bp)) continue;
405
407
  seenBare.add(bp);
@@ -411,42 +413,42 @@ function generateBareAliases(routes, i18n) {
411
413
  }
412
414
  return bareRoutes;
413
415
  }
414
- async function findLocalizedContentRoutes(contentRoot, i18n, options = {}) {
415
- if (!i18n) return findContentRoutes(contentRoot, contentRoot, options);
416
+ async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
417
+ if (!locales) return findContentRoutes(contentRoot, contentRoot, options);
416
418
  const localizedRoutes = [];
417
- for (const locale of i18n.locales) {
419
+ for (const locale of locales.locales) {
418
420
  const localeRoot = join(contentRoot, locale.code);
419
421
  const discovered = await findContentRoutes(localeRoot, localeRoot, options);
420
422
  for (const route of discovered) {
421
423
  const basePath = route.basePath ?? route.path;
422
424
  localizedRoutes.push({
423
425
  ...route,
424
- path: localizedRoutePath(basePath, locale.code, i18n),
426
+ path: localizedRoutePath(basePath, locale.code, locales),
425
427
  basePath,
426
428
  locale: locale.code,
427
429
  module: { virtualModuleId: virtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath) }
428
430
  });
429
431
  }
430
432
  }
431
- if (i18n.missing === "fallback") {
433
+ if (locales.missing === "fallback") {
432
434
  const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
433
- const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
435
+ const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
434
436
  for (const sourceRoute of defaultRoutes) {
435
437
  const basePath = sourceRoute.basePath ?? sourceRoute.path;
436
- for (const locale of i18n.locales) {
438
+ for (const locale of locales.locales) {
437
439
  const key = `${locale.code}:${basePath}`;
438
440
  if (routeByLocaleAndBase.has(key)) continue;
439
441
  localizedRoutes.push({
440
442
  ...sourceRoute,
441
- path: localizedRoutePath(basePath, locale.code, i18n),
443
+ path: localizedRoutePath(basePath, locale.code, locales),
442
444
  locale: locale.code,
443
445
  isFallback: true
444
446
  });
445
447
  }
446
448
  }
447
449
  }
448
- const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
449
- return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, i18n)];
450
+ const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes, locales));
451
+ return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
450
452
  }
451
453
  function buildNavigation(routes) {
452
454
  const root = [];
@@ -523,7 +525,7 @@ function collectExplicitPagePathIntents(tabs) {
523
525
  }
524
526
  return intents;
525
527
  }
526
- function applyConfiguredPageRoutePaths(routes, tabs, i18n) {
528
+ function applyConfiguredPageRoutePaths(routes, tabs, locales) {
527
529
  const pageIntents = collectExplicitPagePathIntents(tabs);
528
530
  if (!pageIntents.length) return routes;
529
531
  const additions = [];
@@ -539,15 +541,15 @@ function applyConfiguredPageRoutePaths(routes, tabs, i18n) {
539
541
  existingKeys.add(key);
540
542
  additions.push({
541
543
  ...route,
542
- path: route.locale && i18n ? localizedRoutePath(targetBasePath, route.locale, i18n) : targetBasePath,
544
+ path: route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath,
543
545
  basePath: targetBasePath
544
546
  });
545
547
  }
546
548
  }
547
549
  const nextRoutes = [...routes, ...additions];
548
- const routesWithAlternates = i18n ? nextRoutes.map((route) => withAlternates(route, nextRoutes, i18n)) : nextRoutes;
549
- if (!i18n) return routesWithAlternates;
550
- return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, i18n)];
550
+ const routesWithAlternates = locales ? nextRoutes.map((route) => withAlternates(route, nextRoutes, locales)) : nextRoutes;
551
+ if (!locales) return routesWithAlternates;
552
+ return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
551
553
  }
552
554
  function buildNavigationFromPagesConfig(routes, config) {
553
555
  if (!config || config === "FileTree") return buildNavigation(routes);
@@ -610,25 +612,25 @@ function buildNavigationFromConfig(routes, config) {
610
612
  };
611
613
  });
612
614
  }
613
- function localizeNavigationPaths(nodes, locale, i18n) {
615
+ function localizeNavigationPaths(nodes, locale, locales) {
614
616
  return nodes.map((node) => ({
615
617
  ...node,
616
- path: localizedRoutePath(node.path, locale, i18n),
617
- children: node.children ? localizeNavigationPaths(node.children, locale, i18n) : void 0
618
+ path: localizedRoutePath(node.path, locale, locales),
619
+ children: node.children ? localizeNavigationPaths(node.children, locale, locales) : void 0
618
620
  }));
619
621
  }
620
- function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
622
+ function buildLocalizedNavigationForLocale(routes, config, locale, locales) {
621
623
  if (!config || config === "FileTree") return localizeNavigationPaths(buildNavigation(routes.map((route) => ({
622
624
  ...route,
623
625
  path: route.basePath ?? route.path
624
- }))), locale, i18n);
626
+ }))), locale, locales);
625
627
  const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
626
628
  const buildItem = (item) => {
627
629
  if (typeof item !== "string" && "group" in item) {
628
630
  const children = item.pages.map(buildItem);
629
631
  return {
630
632
  path: firstNavigationPath(children),
631
- title: resolveLocalizedText(item.group, locale, i18n.defaultLocale) ?? "",
633
+ title: resolveLocalizedText(item.group, locale, locales.default) ?? "",
632
634
  icon: item.icon,
633
635
  children
634
636
  };
@@ -636,10 +638,10 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
636
638
  const intent = routeIntentFromPagesItem(item);
637
639
  const basePath = intent.kind === "openapi" ? openAPIPagePathFromRef(intent.ref, intent.tagFilter, intent.path) : routePathFromRef(intent.ref, intent.path);
638
640
  const route = routeMap.get(basePath);
639
- const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
641
+ const path = route?.path ?? localizedRoutePath(basePath, locale, locales);
640
642
  return {
641
- path: (intent.kind === "page" && intent.redirect ? localizedRoutePath(routePathFromRef(intent.redirect), locale, i18n) : void 0) ?? path,
642
- title: resolveLocalizedText(intent.title, locale, i18n.defaultLocale) ?? route?.meta.title ?? kebabToTitle(basePath.split("/").pop() ?? intent.ref),
643
+ path: (intent.kind === "page" && intent.redirect ? localizedRoutePath(routePathFromRef(intent.redirect), locale, locales) : void 0) ?? path,
644
+ title: resolveLocalizedText(intent.title, locale, locales.default) ?? route?.meta.title ?? kebabToTitle(basePath.split("/").pop() ?? intent.ref),
643
645
  icon: intent.icon,
644
646
  sections: route?.meta.sections ? navigationSections(route.meta.sections) : void 0
645
647
  };
@@ -647,24 +649,24 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
647
649
  return config.map((group) => {
648
650
  const children = group.pages.map(buildItem);
649
651
  return {
650
- path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
651
- title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
652
+ path: children[0]?.path ?? localizedRoutePath("/", locale, locales),
653
+ title: resolveLocalizedText(group.group, locale, locales.default) ?? "",
652
654
  icon: group.icon,
653
655
  children
654
656
  };
655
657
  });
656
658
  }
657
- function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
658
- if (!i18n) return void 0;
659
+ function buildLocalizedNavigationFromTabsConfig(routes, tabs, localesConfig) {
660
+ if (!localesConfig) return void 0;
659
661
  const locales = {};
660
- for (const locale of i18n.locales) {
662
+ for (const locale of localesConfig.locales) {
661
663
  const localeRoutes = routes.filter((route) => route.locale === locale.code);
662
664
  locales[locale.code] = { tabs: tabs.map((tab) => {
663
- const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
665
+ const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, localesConfig);
664
666
  return {
665
667
  type: "tab",
666
668
  path: firstNavigationPath(children),
667
- title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
669
+ title: resolveLocalizedText(tab.tab, locale.code, localesConfig.default) ?? "",
668
670
  icon: tab.icon,
669
671
  children
670
672
  };
@@ -731,7 +733,7 @@ function inferRouteForFile(filePath, contentRoot, defaultLocale) {
731
733
  const localeSegmentIndex = fileLocale ? segments.indexOf(fileLocale) : -1;
732
734
  const basePath = basePathFromRef(localeSegmentIndex >= 0 ? segments.slice(localeSegmentIndex + 1).join("/") : ref);
733
735
  const path = fileLocale && defaultLocale ? localizedRoutePath(basePath, fileLocale, {
734
- defaultLocale,
736
+ default: defaultLocale,
735
737
  missing: "fallback",
736
738
  locales: []
737
739
  }) : basePath;
@@ -784,12 +786,12 @@ async function handleDevRouteRequest(req, res, routes, context) {
784
786
  }
785
787
  const file = typeof body === "object" && body !== null ? body.file : void 0;
786
788
  if (typeof file === "string" && file.length > 0) {
787
- const route = resolveRouteForFile(file, routes, context.projectConfig.i18n?.defaultLocale);
789
+ const route = resolveRouteForFile(file, routes, context.projectConfig.locales?.default);
788
790
  if (route) {
789
791
  res.end(JSON.stringify(toDevRouteEntry(route)));
790
792
  return;
791
793
  }
792
- const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.i18n?.defaultLocale);
794
+ const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.locales?.default);
793
795
  res.end(JSON.stringify(inferred));
794
796
  return;
795
797
  }
@@ -815,8 +817,7 @@ function resolveThemeConfig(theme = {}) {
815
817
  layout: {
816
818
  ...presetTheme.layout,
817
819
  ...theme.layout
818
- },
819
- editor: theme.editor ?? presetTheme.editor
820
+ }
820
821
  };
821
822
  }
822
823
  //#endregion
@@ -824,26 +825,26 @@ function resolveThemeConfig(theme = {}) {
824
825
  var clarifyLogoConfigSchema = z.union([z.string(), z.object({
825
826
  light: z.string().optional(),
826
827
  dark: z.string().optional()
827
- })]);
828
+ }).strict()]);
828
829
  var clarifyFaviconConfigSchema = z.union([z.string(), z.object({
829
830
  light: z.string().optional(),
830
831
  dark: z.string().optional()
831
- })]);
832
+ }).strict()]);
832
833
  var clarifyLocalizedTextSchema = z.union([z.string(), z.record(z.string(), z.string())]);
833
834
  var clarifyLocaleConfigSchema = z.object({
834
835
  code: z.string(),
835
836
  label: z.string(),
836
837
  dir: z.union([z.literal("ltr"), z.literal("rtl")]).optional()
837
- });
838
- var clarifyI18nConfigSchema = z.object({
839
- defaultLocale: z.string().optional(),
838
+ }).strict();
839
+ var clarifyLocalesConfigSchema = z.object({
840
+ default: z.string().optional(),
840
841
  missing: z.union([
841
842
  z.literal("fallback"),
842
843
  z.literal("404"),
843
844
  z.literal("hide")
844
845
  ]).optional(),
845
846
  locales: z.array(clarifyLocaleConfigSchema).min(1)
846
- }).superRefine((config, ctx) => {
847
+ }).strict().superRefine((config, ctx) => {
847
848
  const localeCodes = /* @__PURE__ */ new Set();
848
849
  for (const [index, locale] of config.locales.entries()) {
849
850
  if (localeCodes.has(locale.code)) ctx.addIssue({
@@ -857,17 +858,17 @@ var clarifyI18nConfigSchema = z.object({
857
858
  });
858
859
  localeCodes.add(locale.code);
859
860
  }
860
- if (config.defaultLocale && !localeCodes.has(config.defaultLocale)) ctx.addIssue({
861
+ if (config.default && !localeCodes.has(config.default)) ctx.addIssue({
861
862
  code: z.ZodIssueCode.custom,
862
- message: "defaultLocale must be one of i18n.locales",
863
- path: ["defaultLocale"]
863
+ message: "default must be one of locales.locales",
864
+ path: ["default"]
864
865
  });
865
866
  });
866
867
  var clarifyNavbarLinkSchema = z.object({
867
868
  label: clarifyLocalizedTextSchema,
868
869
  href: z.string(),
869
870
  external: z.boolean().optional()
870
- });
871
+ }).strict();
871
872
  var clarifyBannerConfigOptionsSchema = z.object({
872
873
  content: clarifyLocalizedTextSchema,
873
874
  dismissible: z.boolean().optional(),
@@ -885,11 +886,11 @@ var clarifyVariableValueSchema = z.lazy(() => z.union([
885
886
  z.record(z.string(), clarifyVariableValueSchema)
886
887
  ]));
887
888
  var clarifyVariablesConfigSchema = z.record(z.string(), clarifyVariableValueSchema);
888
- var clarifySourceConfigSchema = z.object({
889
- repository: z.string(),
889
+ var clarifyRepositoryConfigSchema = z.object({
890
+ url: z.string().optional(),
890
891
  branch: z.string().optional(),
891
892
  directory: z.string().optional()
892
- });
893
+ }).strict();
893
894
  var clarifyPagesItemSchema = z.lazy(() => z.union([
894
895
  z.string(),
895
896
  clarifyPagesGroupSchema,
@@ -899,32 +900,32 @@ var clarifyPagesItemSchema = z.lazy(() => z.union([
899
900
  title: clarifyLocalizedTextSchema.optional(),
900
901
  icon: z.string().optional(),
901
902
  redirect: z.string().optional()
902
- }),
903
+ }).strict(),
903
904
  z.object({
904
905
  openapi: z.string(),
905
906
  path: z.string().optional(),
906
907
  icon: z.string().optional(),
907
908
  title: clarifyLocalizedTextSchema.optional(),
908
- filter: z.object({ tags: z.array(z.string()).optional() }).optional()
909
- })
909
+ filter: z.object({ tags: z.array(z.string()).optional() }).strict().optional()
910
+ }).strict()
910
911
  ]));
911
912
  var clarifyPagesGroupSchema = z.lazy(() => z.object({
912
913
  group: clarifyLocalizedTextSchema,
913
914
  icon: z.string().optional(),
914
915
  pages: z.array(clarifyPagesItemSchema)
915
- }));
916
+ }).strict());
916
917
  var clarifyPagesConfigSchema = z.union([z.array(clarifyPagesGroupSchema), z.literal("FileTree")]);
917
918
  var clarifyTabItemSchema = z.object({
918
919
  tab: clarifyLocalizedTextSchema,
919
920
  icon: z.string().optional(),
920
921
  pages: clarifyPagesConfigSchema.optional()
921
- });
922
+ }).strict();
922
923
  var clarifyTabsConfigSchema = z.array(clarifyTabItemSchema);
923
924
  var clarifyThemePresetSchema = z.enum(["default", "base"]);
924
925
  var clarifyThemeModeColorValueSchema = z.object({
925
926
  light: z.string().optional(),
926
927
  dark: z.string().optional()
927
- });
928
+ }).strict();
928
929
  var clarifyThemeColorValueSchema = z.union([z.string(), clarifyThemeModeColorValueSchema]);
929
930
  var clarifyThemeColorTokensConfigSchema = z.object({
930
931
  primary: clarifyThemeColorValueSchema.optional(),
@@ -935,42 +936,65 @@ var clarifyThemeColorTokensConfigSchema = z.object({
935
936
  muted: clarifyThemeColorValueSchema.optional(),
936
937
  border: clarifyThemeColorValueSchema.optional(),
937
938
  codeBackground: clarifyThemeColorValueSchema.optional()
938
- });
939
+ }).strict();
939
940
  var clarifyThemeRadiusTokensConfigSchema = z.object({
940
941
  sm: z.string().optional(),
941
942
  md: z.string().optional(),
942
943
  lg: z.string().optional(),
943
944
  xl: z.string().optional()
944
- });
945
+ }).strict();
945
946
  var clarifyThemeTokensConfigSchema = z.object({
946
947
  colors: clarifyThemeColorTokensConfigSchema.optional(),
947
948
  radius: clarifyThemeRadiusTokensConfigSchema.optional()
948
- });
949
- var clarifyThemeLayoutConfigSchema = z.object({ maxWidth: z.string().optional() });
949
+ }).strict();
950
+ var clarifyThemeLayoutConfigSchema = z.object({ maxWidth: z.string().optional() }).strict();
950
951
  var clarifyThemeConfigSchema = z.object({
951
952
  preset: clarifyThemePresetSchema.optional(),
952
953
  tokens: clarifyThemeTokensConfigSchema.optional(),
953
- layout: clarifyThemeLayoutConfigSchema.optional(),
954
- editor: z.boolean().optional()
955
- });
954
+ layout: clarifyThemeLayoutConfigSchema.optional()
955
+ }).strict();
956
+ function featureSchema(shape, defaults) {
957
+ const objectSchema = z.object({
958
+ enabled: z.boolean().default(true),
959
+ ...shape
960
+ }).strict().default({
961
+ enabled: true,
962
+ ...defaults
963
+ });
964
+ return z.union([z.boolean(), objectSchema]).default({
965
+ enabled: true,
966
+ ...defaults
967
+ }).transform((value) => typeof value === "boolean" ? {
968
+ enabled: value,
969
+ ...defaults
970
+ } : value);
971
+ }
972
+ var clarifyFeaturesConfigSchema = z.object({
973
+ search: featureSchema({ mcp: z.boolean().default(true) }, { mcp: true }),
974
+ repository: featureSchema(clarifyRepositoryConfigSchema.shape, {}),
975
+ themeEditor: featureSchema({}, {}),
976
+ openapi: featureSchema({ playground: z.boolean().default(true) }, { playground: true })
977
+ }).strict();
956
978
  var clarifyProjectConfigSchema = z.object({
957
979
  title: z.string().optional(),
958
980
  description: z.string().optional(),
959
981
  siteUrl: z.string().optional(),
960
- source: clarifySourceConfigSchema.optional(),
982
+ routePrefix: z.string().default("/"),
983
+ assetPrefix: z.string().optional(),
961
984
  logo: clarifyLogoConfigSchema.optional(),
962
985
  homeUrl: z.string().optional(),
963
986
  favicon: clarifyFaviconConfigSchema.optional(),
964
987
  theme: clarifyThemeConfigSchema.optional(),
965
- routePrefix: z.string().optional(),
966
- assetPrefix: z.string().optional(),
967
- navbar: z.object({ links: z.array(clarifyNavbarLinkSchema).optional() }).optional(),
988
+ navigation: z.object({
989
+ links: z.array(clarifyNavbarLinkSchema).optional(),
990
+ tabs: clarifyTabsConfigSchema.optional()
991
+ }).strict().optional(),
968
992
  banner: clarifyBannerConfigOptionsSchema.optional(),
969
993
  footer: clarifyFooterConfigSchema.optional(),
994
+ locales: clarifyLocalesConfigSchema.optional(),
970
995
  variables: clarifyVariablesConfigSchema.optional(),
971
- i18n: clarifyI18nConfigSchema.optional(),
972
- tabs: clarifyTabsConfigSchema.optional()
973
- });
996
+ features: clarifyFeaturesConfigSchema.optional()
997
+ }).strict();
974
998
  //#endregion
975
999
  //#region source/core/config/config.ts
976
1000
  function formatIssuePath(path) {
@@ -992,17 +1016,20 @@ function validateProjectConfig(value) {
992
1016
  if (!result.success) throw new Error(formatProjectConfigError(result.error));
993
1017
  return result.data;
994
1018
  }
995
- function resolveI18nConfig(i18n) {
996
- if (!i18n) return void 0;
997
- const firstLocale = i18n.locales[0]?.code;
998
- const defaultLocale = i18n.defaultLocale ?? firstLocale;
1019
+ function resolveLocalesConfig(locales) {
1020
+ if (!locales) return void 0;
1021
+ const firstLocale = locales.locales[0]?.code;
1022
+ const defaultLocale = locales.default ?? firstLocale;
999
1023
  if (!defaultLocale) return void 0;
1000
1024
  return {
1001
- defaultLocale,
1002
- missing: i18n.missing ?? "fallback",
1003
- locales: i18n.locales
1025
+ default: defaultLocale,
1026
+ missing: locales.missing ?? "fallback",
1027
+ locales: locales.locales
1004
1028
  };
1005
1029
  }
1030
+ function resolveFeaturesConfig(features = {}) {
1031
+ return clarifyFeaturesConfigSchema.parse(features);
1032
+ }
1006
1033
  function resolveRoutePrefix(routePrefix) {
1007
1034
  const trimmed = routePrefix?.trim();
1008
1035
  if (!trimmed || trimmed === "/") return "/";
@@ -1023,26 +1050,24 @@ function resolveProjectConfig(config = {}) {
1023
1050
  title: config.title ?? "Clarify Docs",
1024
1051
  description: config.description ?? "",
1025
1052
  siteUrl: config.siteUrl,
1026
- source: config.source,
1053
+ routePrefix,
1054
+ assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
1027
1055
  logo: config.logo,
1028
1056
  homeUrl: config.homeUrl,
1029
1057
  favicon: config.favicon,
1030
- routePrefix,
1031
- assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
1032
1058
  theme: resolveThemeConfig(config.theme),
1033
- navbar: config.navbar,
1059
+ navigation: config.navigation,
1034
1060
  banner: config.banner,
1035
1061
  footer: config.footer,
1062
+ locales: resolveLocalesConfig(config.locales),
1036
1063
  variables: config.variables ?? {},
1037
- i18n: resolveI18nConfig(config.i18n),
1038
- tabs: config.tabs
1064
+ features: resolveFeaturesConfig(config.features)
1039
1065
  };
1040
1066
  }
1041
1067
  //#endregion
1042
1068
  //#region source/core/config/user-config.ts
1043
1069
  function defineConfig(config) {
1044
- validateProjectConfig(config);
1045
- return config;
1070
+ return validateClarifyConfig(config);
1046
1071
  }
1047
1072
  /**
1048
1073
  * Config filenames Clarify CLI looks for at the project root, in resolution
@@ -1066,18 +1091,23 @@ function assertConfigObject(config, configFile) {
1066
1091
  }
1067
1092
  function loadJsonConfig(configFile) {
1068
1093
  const config = assertConfigObject(JSON.parse(readFileSync(configFile, "utf-8")), configFile);
1069
- const projectConfig = validateProjectConfig(config);
1070
- const ssg = config.ssg && typeof config.ssg === "object" && !Array.isArray(config.ssg) ? config.ssg : void 0;
1071
- return {
1094
+ if ("plugins" in config) throw new Error("[clarify] clarify.json does not support plugins; use clarify.ts or clarify.js");
1095
+ return validateClarifyConfig(config);
1096
+ }
1097
+ function validateClarifyConfig(config) {
1098
+ const { plugins, ...projectConfigInput } = config;
1099
+ if (plugins !== void 0 && !Array.isArray(plugins)) throw new Error("[clarify] config field \"plugins\" is invalid: Expected an array");
1100
+ const projectConfig = validateProjectConfig(projectConfigInput);
1101
+ return plugins ? {
1072
1102
  ...projectConfig,
1073
- ...ssg ? { ssg } : {}
1074
- };
1103
+ plugins
1104
+ } : projectConfig;
1075
1105
  }
1076
1106
  async function loadClarifyConfig(root, env) {
1077
1107
  const configFile = findClarifyConfigFile(root);
1078
1108
  if (!configFile) return {};
1079
1109
  if (configFile.endsWith(".json")) return loadJsonConfig(configFile);
1080
- return assertConfigObject((await loadConfigFromFile(env, configFile, root, "silent"))?.config, configFile);
1110
+ return validateClarifyConfig(assertConfigObject((await loadConfigFromFile(env, configFile, root, "silent"))?.config, configFile));
1081
1111
  }
1082
1112
  /**
1083
1113
  * File extensions Clarify treats as content files. Kept in sync with the
@@ -1103,9 +1133,9 @@ function buildProjectInfo(context) {
1103
1133
  contentRoot: (contentRoot.startsWith(projectRoot) ? contentRoot.slice(projectRoot.length).replace(/^[/\\]+/, "") : contentRoot) || "source",
1104
1134
  projectRoot
1105
1135
  };
1106
- if (projectConfig.i18n) response.i18n = {
1107
- defaultLocale: projectConfig.i18n.defaultLocale,
1108
- locales: projectConfig.i18n.locales.map((l) => l.code)
1136
+ if (projectConfig.locales) response.locales = {
1137
+ default: projectConfig.locales.default,
1138
+ locales: projectConfig.locales.locales.map((l) => l.code)
1109
1139
  };
1110
1140
  return response;
1111
1141
  }
@@ -1165,10 +1195,23 @@ function resolveVirtualModuleId(id, modules, routes) {
1165
1195
  if (route) return resolveVirtualId(route.module.virtualModuleId);
1166
1196
  return null;
1167
1197
  }
1168
- function generateConfigModule(projectConfig, buildOptions, version) {
1198
+ function generateConfigModule(projectConfig, version) {
1169
1199
  const runtimeConfig = {
1170
- ...projectConfig,
1171
- ...buildOptions
1200
+ title: projectConfig.title,
1201
+ description: projectConfig.description,
1202
+ siteUrl: projectConfig.siteUrl,
1203
+ routePrefix: projectConfig.routePrefix,
1204
+ assetPrefix: projectConfig.assetPrefix,
1205
+ logo: projectConfig.logo,
1206
+ homeUrl: projectConfig.homeUrl,
1207
+ favicon: projectConfig.favicon,
1208
+ theme: projectConfig.theme,
1209
+ navigation: projectConfig.navigation,
1210
+ banner: projectConfig.banner,
1211
+ footer: projectConfig.footer,
1212
+ locales: projectConfig.locales,
1213
+ variables: projectConfig.variables,
1214
+ features: projectConfig.features
1172
1215
  };
1173
1216
  if (version) runtimeConfig.version = version;
1174
1217
  return `export const config = ${JSON.stringify(runtimeConfig)};`;
@@ -1272,7 +1315,7 @@ function buildVirtualModules(args) {
1272
1315
  const modules = /* @__PURE__ */ new Map();
1273
1316
  const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
1274
1317
  const allPlugins = [...args.plugins ?? []];
1275
- modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions, args.version));
1318
+ modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.version));
1276
1319
  modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, "client"));
1277
1320
  modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, "server"));
1278
1321
  modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
@@ -1497,7 +1540,7 @@ function createViteAdapter(engine) {
1497
1540
  const compilePolicyPlugin = {
1498
1541
  name: "clarify:content-compile-policy",
1499
1542
  configResolved(config) {
1500
- contentCompileErrorPolicy = config.command === "build" && engine.generateOptions.ssg.failOnError ? "throw" : "diagnostic";
1543
+ contentCompileErrorPolicy = config.command === "build" ? "throw" : "diagnostic";
1501
1544
  }
1502
1545
  };
1503
1546
  const tailwind = tailwindcss();
@@ -1513,7 +1556,7 @@ function createViteAdapter(engine) {
1513
1556
  }
1514
1557
  var cliPackageVersion = {
1515
1558
  name: "@clarify-labs/cli",
1516
- version: "0.10.3",
1559
+ version: "0.11.1",
1517
1560
  license: "AGPL-3.0-only",
1518
1561
  type: "module",
1519
1562
  repository: {
@@ -1554,6 +1597,9 @@ var cliPackageVersion = {
1554
1597
  "@clarify-labs/templates": "workspace:*",
1555
1598
  "@mdx-js/mdx": "^3.1.1",
1556
1599
  "@mdx-js/rollup": "^3.1.1",
1600
+ "@modelcontextprotocol/sdk": "^1.29.0",
1601
+ "@msgpack/msgpack": "^3.1.3",
1602
+ "@orama/orama": "^3.1.18",
1557
1603
  "@shikijs/rehype": "^4.3.0",
1558
1604
  "@tailwindcss/vite": "^4.3.2",
1559
1605
  "@vitejs/plugin-react": "^6.0.3",
@@ -1572,7 +1618,6 @@ var cliPackageVersion = {
1572
1618
  "rehype-raw": "^7.0.0",
1573
1619
  "rehype-slug": "^6.0.0",
1574
1620
  "remark": "^15.0.1",
1575
- "remark-gfm": "^4.0.1",
1576
1621
  "shiki": "4.3.0",
1577
1622
  "unist-util-visit": "^5.1.0",
1578
1623
  "vite": "^8.1.4",
@@ -1593,8 +1638,7 @@ function resolveBuildOptions(options = {}) {
1593
1638
  return {
1594
1639
  projectRoot: options.projectRoot ?? process.cwd(),
1595
1640
  rootDirectory: options.rootDirectory ?? "source",
1596
- outputDirectory: options.outputDirectory,
1597
- ssg: { failOnError: options.ssg?.failOnError ?? true }
1641
+ outputDirectory: options.outputDirectory
1598
1642
  };
1599
1643
  }
1600
1644
  //#endregion
@@ -1652,7 +1696,7 @@ function llmsTxtListItem(route, basePath) {
1652
1696
  return description ? `- [${route.meta.title}](${basePath}${contentArtifactUrl}): ${description}` : `- [${route.meta.title}](${basePath}${contentArtifactUrl})`;
1653
1697
  }
1654
1698
  function llmsTxtLocaleLabel(locale, projectConfig) {
1655
- return projectConfig.i18n?.locales.find((item) => item.code === locale)?.label ?? locale;
1699
+ return projectConfig.locales?.locales.find((item) => item.code === locale)?.label ?? locale;
1656
1700
  }
1657
1701
  function groupRoutesByLocale(routes) {
1658
1702
  const groups = /* @__PURE__ */ new Map();
@@ -1664,7 +1708,7 @@ function groupRoutesByLocale(routes) {
1664
1708
  }
1665
1709
  function groupLlmsTxtRoutesByLocale(routes, projectConfig) {
1666
1710
  const routesBySource = /* @__PURE__ */ new Map();
1667
- const defaultLocale = projectConfig.i18n?.defaultLocale;
1711
+ const defaultLocale = projectConfig.locales?.default;
1668
1712
  for (const route of routes) {
1669
1713
  const sourceKey = `${route.locale ?? defaultLocale ?? "default"}:${route.basePath ?? route.artifacts?.contentArtifactUrl ?? route.path}`;
1670
1714
  const previousRoute = routesBySource.get(sourceKey);
@@ -1854,6 +1898,347 @@ function createHtmlShellPlugin() {
1854
1898
  };
1855
1899
  }
1856
1900
  //#endregion
1901
+ //#region source/plugins/mcp-search/mcp-config.ts
1902
+ /**
1903
+ * Normalize a route prefix into a root-relative path segment.
1904
+ *
1905
+ * `'/'` -> `''`, `'/docs'` -> `'/docs'`, `'docs/'` -> `'/docs'`.
1906
+ */
1907
+ function normalizePrefix(routePrefix) {
1908
+ if (!routePrefix || routePrefix === "/") return "";
1909
+ return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
1910
+ }
1911
+ /**
1912
+ * Assemble the MCP site config from build-time metadata.
1913
+ *
1914
+ * Unlike the previous pagefind-based descriptor, this no longer reads an
1915
+ * entry JSON file - the Orama index is a single binary blob, so the caller
1916
+ * supplies the document count and locale list directly from the build step
1917
+ * that produced the index.
1918
+ */
1919
+ function createMcpSiteConfig(projectConfig, options) {
1920
+ const prefix = normalizePrefix(projectConfig.routePrefix);
1921
+ return {
1922
+ version: 3,
1923
+ site: {
1924
+ title: projectConfig.title,
1925
+ description: projectConfig.description,
1926
+ url: projectConfig.siteUrl,
1927
+ routePrefix: projectConfig.routePrefix ?? "/"
1928
+ },
1929
+ capabilities: { search: {
1930
+ type: "search",
1931
+ indexPath: `${prefix}/mcp-search.msp`,
1932
+ defaultLocale: projectConfig.locales?.default,
1933
+ documentCount: options.documentCount,
1934
+ locales: options.locales
1935
+ } }
1936
+ };
1937
+ }
1938
+ //#endregion
1939
+ //#region source/plugins/mcp-search/orama-index.ts
1940
+ /**
1941
+ * Orama schema for the MCP search index.
1942
+ *
1943
+ * - `path`, `title`, `description`, `content` are full-text searchable strings.
1944
+ * - `locale` is an `enum` so we can use `where: { locale: { eq } }` filters at
1945
+ * query time. (`string` fields cannot be filtered in Orama.)
1946
+ * - `keywords` is a `string[]` also indexed for full-text matching.
1947
+ */
1948
+ var mcpSearchSchema = {
1949
+ path: "string",
1950
+ title: "string",
1951
+ description: "string",
1952
+ locale: "enum",
1953
+ content: "string",
1954
+ keywords: "string[]"
1955
+ };
1956
+ /**
1957
+ * Intl.Segmenter cache keyed by BCP 47 tag. Segmenter construction is not
1958
+ * cheap, and the MCP server issues many queries against the same locale, so
1959
+ * we reuse instances. The cache is module-scoped: build and runtime share it
1960
+ * in the same process, and separate processes get their own - which is fine.
1961
+ */
1962
+ var segmenterCache = /* @__PURE__ */ new Map();
1963
+ function getSegmenter(locale) {
1964
+ let seg = segmenterCache.get(locale);
1965
+ if (!seg) {
1966
+ seg = new Intl.Segmenter(locale, { granularity: "word" });
1967
+ segmenterCache.set(locale, seg);
1968
+ }
1969
+ return seg;
1970
+ }
1971
+ /**
1972
+ * Map a Clarify locale code (e.g. `zh-CN`, `en-US`, `ja-JP`) to a BCP 47 tag
1973
+ * suitable for `Intl.Segmenter`. Returns `null` for locales we intentionally
1974
+ * want to tokenize with the Latin/whitespace fallback (faster, and avoids
1975
+ * forcing CJK-style word segmentation on Latin scripts).
1976
+ */
1977
+ function segmenterLocale(locale) {
1978
+ if (!locale) return null;
1979
+ const lower = locale.toLowerCase();
1980
+ if (lower.startsWith("zh")) return "zh-CN";
1981
+ if (lower.startsWith("ja")) return "ja-JP";
1982
+ if (lower.startsWith("ko")) return "ko-KR";
1983
+ return null;
1984
+ }
1985
+ /**
1986
+ * Tokenize text for Orama indexing and querying.
1987
+ *
1988
+ * CJK locales use `Intl.Segmenter` (word granularity), which splits
1989
+ * spaceless scripts into words and individual characters. Latin/whitespace
1990
+ * locales fall back to a Unicode-aware split on spaces and punctuation.
1991
+ *
1992
+ * All tokens are lowercased so queries match regardless of case.
1993
+ */
1994
+ function tokenizeForSearch(text, locale) {
1995
+ if (!text) return [];
1996
+ const segLocale = segmenterLocale(locale);
1997
+ if (segLocale) {
1998
+ const seg = getSegmenter(segLocale);
1999
+ return Array.from(seg.segment(text)).filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
2000
+ }
2001
+ return text.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
2002
+ }
2003
+ /**
2004
+ * Build an Orama tokenizer component bound to a specific default locale.
2005
+ *
2006
+ * The tokenizer receives the raw field value and the per-field language hint
2007
+ * from Orama (which we don't set per-field, so it falls back to the default
2008
+ * language). We resolve the segmenter locale from the default locale at
2009
+ * construction time so the same tokenizer handles all documents in a
2010
+ * predominantly single-locale index.
2011
+ *
2012
+ * For multi-locale indexes, the tokenizer is also used at query time. Since
2013
+ * the query locale is not known to Orama's tokenizer callback, we use the
2014
+ * index's default locale - this is acceptable because:
2015
+ * - CJK queries get CJK segmentation (correct)
2016
+ * - Latin queries against a CJK-default index still work because Latin
2017
+ * text has no word boundaries for CJK segmenters to mis-split; the
2018
+ * segmenter passes through Latin words as-is.
2019
+ */
2020
+ function createMcpTokenizer(defaultLocale) {
2021
+ return {
2022
+ language: defaultLocale ?? "en",
2023
+ normalizationCache: /* @__PURE__ */ new Map(),
2024
+ tokenize(raw, language) {
2025
+ return tokenizeForSearch(typeof raw === "string" ? raw : String(raw ?? ""), language ?? defaultLocale);
2026
+ }
2027
+ };
2028
+ }
2029
+ /**
2030
+ * Compose the searchable text for a route. Shared between the Pagefind
2031
+ * (browser) and Orama (MCP) indexing paths so both backends see the same
2032
+ * field order and content.
2033
+ */
2034
+ function routeSearchContent(route) {
2035
+ return [
2036
+ route.meta.title,
2037
+ route.meta.description,
2038
+ route.meta.keywords?.join(" "),
2039
+ route.meta.sections?.map((section) => section.title).join(" "),
2040
+ route.source.content
2041
+ ].filter(Boolean).join("\n\n");
2042
+ }
2043
+ /**
2044
+ * Convert a `ContentRoute` into the document shape stored in the Orama index.
2045
+ */
2046
+ function routeToSearchDocument(route, defaultLocale) {
2047
+ if (route.isBareAlias) return null;
2048
+ const content = routeSearchContent(route);
2049
+ if (!content.trim()) return null;
2050
+ return {
2051
+ path: route.path,
2052
+ title: route.meta.title ?? "",
2053
+ description: route.meta.description ?? "",
2054
+ locale: route.locale ?? defaultLocale ?? "en",
2055
+ content,
2056
+ keywords: route.meta.keywords ?? []
2057
+ };
2058
+ }
2059
+ /**
2060
+ * Create an Orama index pre-configured with the MCP schema and CJK-aware
2061
+ * tokenizer. Both the build step and the runtime loader use this so the
2062
+ * tokenizer is guaranteed to match between indexing and querying.
2063
+ *
2064
+ * Annotated as `AnyOrama` (Orama's exported base type) rather than inferred
2065
+ * via `ReturnType<typeof create>` because the inference would pull in
2066
+ * Orama's internal component types (`DocumentsStore`, `Index`, `Sorter`)
2067
+ * which are not exported and break `.d.ts` generation (TS2883).
2068
+ */
2069
+ function createMcpSearchIndex(defaultLocale) {
2070
+ return create({
2071
+ schema: mcpSearchSchema,
2072
+ components: { tokenizer: createMcpTokenizer(defaultLocale) }
2073
+ });
2074
+ }
2075
+ /**
2076
+ * Insert search documents into an Orama index.
2077
+ */
2078
+ function indexSearchDocuments(db, documents) {
2079
+ if (documents.length === 0) return;
2080
+ insertMultiple(db, documents);
2081
+ }
2082
+ /**
2083
+ * Build a ready-to-search Orama index from content routes.
2084
+ *
2085
+ * Filters out bare aliases and empty routes. Returns the populated db.
2086
+ */
2087
+ function buildSearchIndex(routes, defaultLocale) {
2088
+ const db = createMcpSearchIndex(defaultLocale);
2089
+ const documents = [];
2090
+ for (const route of routes) {
2091
+ const doc = routeToSearchDocument(route, defaultLocale);
2092
+ if (doc) documents.push(doc);
2093
+ }
2094
+ indexSearchDocuments(db, documents);
2095
+ return {
2096
+ db,
2097
+ documentCount: documents.length
2098
+ };
2099
+ }
2100
+ /**
2101
+ * Collect the distinct locale codes that will be present in the Orama index,
2102
+ * in stable insertion order. Bare aliases are skipped (they duplicate the
2103
+ * default-locale route). Locales with no code fall back to the project
2104
+ * default so single-locale sites still surface one locale.
2105
+ */
2106
+ function collectIndexedLocales(routes, defaultLocale) {
2107
+ const seen = /* @__PURE__ */ new Set();
2108
+ const locales = [];
2109
+ for (const route of routes) {
2110
+ if (route.isBareAlias) continue;
2111
+ const code = route.locale ?? defaultLocale ?? "en";
2112
+ if (!seen.has(code)) {
2113
+ seen.add(code);
2114
+ locales.push(code);
2115
+ }
2116
+ }
2117
+ return locales;
2118
+ }
2119
+ /**
2120
+ * Search an Orama MCP index.
2121
+ *
2122
+ * Applies an optional locale filter (via the `enum` field) and returns a
2123
+ * compact, LLM-friendly hit shape. Excerpts are derived from the matched
2124
+ * content: Orama does not return highlighted excerpts, so we synthesize a
2125
+ * short window around the first query term occurrence.
2126
+ */
2127
+ function searchMcpIndex(db, params) {
2128
+ const limit = Math.max(1, Math.min(50, params.limit ?? 10));
2129
+ const result = search(db, {
2130
+ term: params.query,
2131
+ limit,
2132
+ where: params.locale ? { locale: { eq: params.locale } } : void 0
2133
+ });
2134
+ return {
2135
+ hits: result.hits.map((hit) => {
2136
+ const doc = hit.document;
2137
+ return {
2138
+ path: doc.path,
2139
+ title: doc.title || guessTitleFromPath(doc.path),
2140
+ description: doc.description,
2141
+ locale: doc.locale,
2142
+ score: hit.score,
2143
+ excerpt: makeExcerpt(doc.content, params.query),
2144
+ keywords: doc.keywords
2145
+ };
2146
+ }),
2147
+ count: result.count
2148
+ };
2149
+ }
2150
+ /**
2151
+ * Build a short excerpt around the first occurrence of any query term in the
2152
+ * content. Falls back to the beginning of the content if no term is found.
2153
+ */
2154
+ function makeExcerpt(content, query, maxLen = 240) {
2155
+ if (!content) return "";
2156
+ const terms = query.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
2157
+ const lower = content.toLowerCase();
2158
+ let pos = -1;
2159
+ for (const term of terms) {
2160
+ const i = lower.indexOf(term);
2161
+ if (i >= 0 && (pos < 0 || i < pos)) pos = i;
2162
+ }
2163
+ if (pos < 0) pos = 0;
2164
+ const half = Math.floor(maxLen / 2);
2165
+ const start = Math.max(0, pos - half);
2166
+ const end = Math.min(content.length, start + maxLen);
2167
+ const prefix = start > 0 ? "…" : "";
2168
+ const suffix = end < content.length ? "…" : "";
2169
+ return `${prefix}${content.slice(start, end).trim()}${suffix}`;
2170
+ }
2171
+ function guessTitleFromPath(path) {
2172
+ if (!path) return "(untitled)";
2173
+ const segments = path.split("/").filter(Boolean);
2174
+ if (segments.length === 0) return "Home";
2175
+ return segments[segments.length - 1].replace(/\.(md|mdx|html)$/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2176
+ }
2177
+ /**
2178
+ * Serialize an Orama index to a msgpack binary buffer.
2179
+ *
2180
+ * Uses `save()` to extract the raw index data, then msgpack-encodes it for a
2181
+ * compact binary representation. The same schema + tokenizer must be supplied
2182
+ * when loading so query-time tokenization matches indexing-time tokenization.
2183
+ */
2184
+ function serializeSearchIndex(db) {
2185
+ return encode(save(db));
2186
+ }
2187
+ /**
2188
+ * Deserialize a msgpack binary buffer into a ready-to-search Orama index.
2189
+ *
2190
+ * The caller must supply the same `defaultLocale` used when the index was
2191
+ * built, so the tokenizer matches. A mismatch would not corrupt data but
2192
+ * would cause CJK query terms to be tokenized incorrectly (e.g. "文档" left
2193
+ * as a single token instead of split into "文" + "档").
2194
+ */
2195
+ function deserializeSearchIndex(buffer, defaultLocale) {
2196
+ const raw = decode(buffer);
2197
+ const db = createMcpSearchIndex(defaultLocale);
2198
+ load(db, raw);
2199
+ return db;
2200
+ }
2201
+ //#endregion
2202
+ //#region source/plugins/mcp-search/index.ts
2203
+ /**
2204
+ * MCP search index plugin.
2205
+ *
2206
+ * Generates the Orama binary index (`mcp-search.msp`) and the MCP site
2207
+ * descriptor (`mcp.json`) as build assets consumed by the `clarify mcp`
2208
+ * command to serve documentation search to MCP-aware clients (e.g. AI coding
2209
+ * assistants).
2210
+ *
2211
+ * This plugin is independent of `clarify:page-search` (Pagefind), which
2212
+ * powers in-browser search. The MCP server runs in Node and needs a
2213
+ * self-contained, compact index with CJK-aware tokenization that doesn't
2214
+ * depend on the browser-only Pagefind runtime.
2215
+ */
2216
+ function createMcpSearchPlugin() {
2217
+ return {
2218
+ name: "clarify:mcp-search",
2219
+ hooks: { "build:assets"(ctx) {
2220
+ const searchConfig = ctx.projectConfig.features.search;
2221
+ if (!searchConfig.enabled || !searchConfig.mcp) return [];
2222
+ const defaultLocale = ctx.projectConfig.locales?.default;
2223
+ const { db, documentCount } = buildSearchIndex(ctx.routes, defaultLocale);
2224
+ const locales = collectIndexedLocales(ctx.routes, defaultLocale);
2225
+ const mcpConfig = createMcpSiteConfig(ctx.projectConfig, {
2226
+ documentCount,
2227
+ locales
2228
+ });
2229
+ const assets = [{
2230
+ fileName: `${mcpConfig.capabilities.search?.indexPath.replace(/\/mcp-search\.msp$/, "") ?? ""}/mcp-search.msp`.replace(/^\//, ""),
2231
+ source: serializeSearchIndex(db)
2232
+ }, {
2233
+ fileName: "mcp.json",
2234
+ source: `${JSON.stringify(mcpConfig, null, 2)}\n`
2235
+ }];
2236
+ console.log(`[clarify] MCP search index (${documentCount} docs, ${locales.length} locales) written to mcp-search.msp.`);
2237
+ return assets;
2238
+ } }
2239
+ };
2240
+ }
2241
+ //#endregion
1857
2242
  //#region source/plugins/openapi/parser.ts
1858
2243
  var OPENAPI_HTTP_METHODS = [
1859
2244
  "get",
@@ -2059,7 +2444,7 @@ function collectOpenAPIPageIntents(config) {
2059
2444
  if (!pages || pages === "FileTree") return;
2060
2445
  for (const group of pages) visitItems(group.pages);
2061
2446
  }
2062
- for (const tab of config.tabs ?? []) visitPages(tab.pages);
2447
+ for (const tab of config.navigation?.tabs ?? []) visitPages(tab.pages);
2063
2448
  return intents;
2064
2449
  }
2065
2450
  function virtualModuleIdFromPath(path) {
@@ -2082,14 +2467,14 @@ function cloneOpenAPIRoute(route, overrides = {}) {
2082
2467
  ...overrides
2083
2468
  };
2084
2469
  }
2085
- function findOpenAPIRoutesForContentRoot(contentRoot, i18n) {
2086
- if (!i18n) return findOpenAPIRoutes(contentRoot);
2470
+ function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
2471
+ if (!locales) return findOpenAPIRoutes(contentRoot);
2087
2472
  const localizedRoutes = [];
2088
- for (const locale of i18n.locales) {
2473
+ for (const locale of locales.locales) {
2089
2474
  const localeRoot = join(contentRoot, locale.code);
2090
2475
  for (const route of findOpenAPIRoutes(localeRoot, localeRoot)) {
2091
2476
  const basePath = route.basePath ?? route.path;
2092
- const path = localizedRoutePath(basePath, locale.code, i18n);
2477
+ const path = localizedRoutePath(basePath, locale.code, locales);
2093
2478
  localizedRoutes.push({
2094
2479
  ...route,
2095
2480
  path,
@@ -2099,15 +2484,15 @@ function findOpenAPIRoutesForContentRoot(contentRoot, i18n) {
2099
2484
  });
2100
2485
  }
2101
2486
  }
2102
- if (i18n.missing === "fallback") {
2487
+ if (locales.missing === "fallback") {
2103
2488
  const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
2104
- const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
2489
+ const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
2105
2490
  for (const sourceRoute of defaultRoutes) {
2106
2491
  const basePath = sourceRoute.basePath ?? sourceRoute.path;
2107
- for (const locale of i18n.locales) {
2492
+ for (const locale of locales.locales) {
2108
2493
  const key = `${locale.code}:${basePath}`;
2109
2494
  if (routeByLocaleAndBase.has(key)) continue;
2110
- const path = localizedRoutePath(basePath, locale.code, i18n);
2495
+ const path = localizedRoutePath(basePath, locale.code, locales);
2111
2496
  localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
2112
2497
  path,
2113
2498
  locale: locale.code,
@@ -2134,7 +2519,7 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2134
2519
  const key = `${route.locale ?? ""}:${targetBasePath}`;
2135
2520
  if (existingKeys.has(key)) continue;
2136
2521
  existingKeys.add(key);
2137
- const path = route.locale && config.i18n ? localizedRoutePath(targetBasePath, route.locale, config.i18n) : targetBasePath;
2522
+ const path = route.locale && config.locales ? localizedRoutePath(targetBasePath, route.locale, config.locales) : targetBasePath;
2138
2523
  additions.push(cloneOpenAPIRoute(route, {
2139
2524
  path,
2140
2525
  basePath: targetBasePath,
@@ -2147,12 +2532,12 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2147
2532
  }
2148
2533
  }
2149
2534
  const nextRoutes = [...routes, ...additions];
2150
- if (!config.i18n) return nextRoutes;
2151
- const routesWithAlternates = nextRoutes.map((route) => withAlternates(route, nextRoutes, config.i18n));
2535
+ if (!config.locales) return nextRoutes;
2536
+ const routesWithAlternates = nextRoutes.map((route) => withAlternates(route, nextRoutes, config.locales));
2152
2537
  const bareAliases = [];
2153
2538
  const seenBare = new Set(routesWithAlternates.map((route) => route.path));
2154
2539
  for (const route of routesWithAlternates) {
2155
- if (route.locale !== config.i18n.defaultLocale) continue;
2540
+ if (route.locale !== config.locales.default) continue;
2156
2541
  const basePath = route.basePath ?? route.path;
2157
2542
  if (basePath === route.path || seenBare.has(basePath)) continue;
2158
2543
  seenBare.add(basePath);
@@ -2191,7 +2576,7 @@ function createOpenAPIPlugin() {
2191
2576
  hooks: {
2192
2577
  "routes:discover": (input, ctx) => ({
2193
2578
  ...input,
2194
- routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.i18n)]
2579
+ routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.locales)]
2195
2580
  }),
2196
2581
  "routes:discovered": async (routes, ctx) => {
2197
2582
  specs.clear();
@@ -2276,12 +2661,12 @@ function removeClarifyTempDir(path) {
2276
2661
  });
2277
2662
  }
2278
2663
  //#endregion
2279
- //#region source/plugins/search-index/search-language.ts
2664
+ //#region source/plugins/page-search/search-language.ts
2280
2665
  function toPagefindLanguage(locale) {
2281
2666
  return locale?.trim().replace(/_/g, "-").split("-").filter(Boolean).join("-").toLowerCase() || "en";
2282
2667
  }
2283
2668
  //#endregion
2284
- //#region source/plugins/search-index/index.ts
2669
+ //#region source/plugins/page-search/index.ts
2285
2670
  var pagefindContentTypes = {
2286
2671
  ".css": "text/css; charset=utf-8",
2287
2672
  ".js": "text/javascript; charset=utf-8",
@@ -2321,15 +2706,6 @@ async function generatePagefindIndex(options, pagefind) {
2321
2706
  if (!pagefind) await runtime.close();
2322
2707
  }
2323
2708
  }
2324
- function routeSearchContent(route) {
2325
- return [
2326
- route.meta.title,
2327
- route.meta.description,
2328
- route.meta.keywords?.join(" "),
2329
- route.meta.sections?.map((section) => section.title).join(" "),
2330
- route.source.content
2331
- ].filter(Boolean).join("\n\n");
2332
- }
2333
2709
  async function generateDevSearchIndex(ctx, root, pagefind) {
2334
2710
  const { errors, index } = await pagefind.createIndex({
2335
2711
  includeCharacters: "._-/#:@",
@@ -2348,7 +2724,7 @@ async function generateDevSearchIndex(ctx, root, pagefind) {
2348
2724
  const result = await index.addCustomRecord({
2349
2725
  url: route.path,
2350
2726
  content,
2351
- language: toPagefindLanguage(route.locale ?? ctx.projectConfig.i18n?.defaultLocale),
2727
+ language: toPagefindLanguage(route.locale ?? ctx.projectConfig.locales?.default),
2352
2728
  meta: { title: route.meta.title }
2353
2729
  });
2354
2730
  assertNoPagefindErrors(`addCustomRecord ${route.path}`, result.errors);
@@ -2386,11 +2762,12 @@ function serveDevPagefind(server, getRoot, ctx) {
2386
2762
  createReadStream(filePath).pipe(res);
2387
2763
  });
2388
2764
  }
2389
- function createSearchIndexPlugin() {
2765
+ function createPageSearchPlugin() {
2390
2766
  return {
2391
- name: "clarify:search-index",
2767
+ name: "clarify:page-search",
2392
2768
  hooks: {
2393
2769
  async "dev:configureServer"(server, ctx) {
2770
+ if (!ctx.projectConfig.features.search.enabled) return;
2394
2771
  const pagefind = await import("pagefind");
2395
2772
  let currentRoot;
2396
2773
  const staleRoots = /* @__PURE__ */ new Set();
@@ -2430,10 +2807,11 @@ function createSearchIndexPlugin() {
2430
2807
  });
2431
2808
  },
2432
2809
  async "build:done"(ctx) {
2810
+ if (!ctx.projectConfig.features.search.enabled) return;
2433
2811
  const outputDirectory = ctx.generateOptions.outputDirectory;
2434
2812
  if (!outputDirectory) return;
2435
2813
  const pageCount = await generatePagefindIndex({ outputRoot: resolve(ctx.generateOptions.projectRoot, outputDirectory) });
2436
- console.log(`[clarify] Search index generated for ${pageCount} pages.`);
2814
+ console.log(`[clarify] Page search index generated for ${pageCount} pages.`);
2437
2815
  }
2438
2816
  }
2439
2817
  };
@@ -2482,16 +2860,18 @@ function createSeoPlugin() {
2482
2860
  return {
2483
2861
  name: "clarify:seo",
2484
2862
  hooks: { "build:assets"(ctx) {
2863
+ const assets = [];
2485
2864
  const sitemap = createSitemapXml(ctx.routes, ctx.projectConfig);
2486
2865
  const robots = createRobotsTxt(ctx.projectConfig);
2487
- if (!sitemap || !robots) return [];
2488
- return [{
2866
+ if (sitemap) assets.push({
2489
2867
  fileName: "sitemap.xml",
2490
2868
  source: sitemap
2491
- }, {
2869
+ });
2870
+ if (robots) assets.push({
2492
2871
  fileName: "robots.txt",
2493
2872
  source: robots
2494
- }];
2873
+ });
2874
+ return assets;
2495
2875
  } }
2496
2876
  };
2497
2877
  }
@@ -2507,7 +2887,8 @@ function encodePath(path) {
2507
2887
  return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
2508
2888
  }
2509
2889
  function createSourceEditUrl(filePath, contentRoot, source) {
2510
- const repository = normalizeRepositoryUrl(source.repository);
2890
+ if (!source.url) return "";
2891
+ const repository = normalizeRepositoryUrl(source.url);
2511
2892
  const branch = source.branch ?? "main";
2512
2893
  const sourcePath = [source.directory ? trimSlashes(source.directory) : "", relative(contentRoot, filePath).replaceAll("\\", "/")].filter(Boolean).join("/");
2513
2894
  return `${repository}/edit/${encodeURIComponent(branch)}/${encodePath(sourcePath)}`;
@@ -2525,7 +2906,8 @@ function createSourceLinksPlugin() {
2525
2906
  return {
2526
2907
  name: "clarify:source-links",
2527
2908
  hooks: { "routes:discovered": (routes, ctx) => {
2528
- attachSourceEditUrls(routes, join(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.source);
2909
+ if (!ctx.projectConfig.features.repository.enabled) return routes;
2910
+ attachSourceEditUrls(routes, join(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.features.repository);
2529
2911
  return routes;
2530
2912
  } }
2531
2913
  };
@@ -2578,7 +2960,8 @@ function createBuiltinPlugins(options = {}) {
2578
2960
  createSourceLinksPlugin(),
2579
2961
  createContentArtifactsPlugin(),
2580
2962
  createSeoPlugin(),
2581
- createSearchIndexPlugin()
2963
+ createPageSearchPlugin(),
2964
+ createMcpSearchPlugin()
2582
2965
  ];
2583
2966
  if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
2584
2967
  return plugins;
@@ -2640,18 +3023,18 @@ async function resolveProjectContext(options = {}, env = {
2640
3023
  }) {
2641
3024
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
2642
3025
  const initialBuildOptions = resolveBuildOptions(options);
2643
- const contentRoot = join(projectRoot, initialBuildOptions.rootDirectory);
2644
3026
  const configFilePath = findClarifyConfigFile(projectRoot);
2645
3027
  const config = configFilePath && existsSync(configFilePath) ? await loadClarifyConfig(projectRoot, env) : {};
2646
3028
  const mergedOptions = {
2647
3029
  ...config,
2648
3030
  ...options,
2649
3031
  projectRoot,
2650
- rootDirectory: initialBuildOptions.rootDirectory,
2651
- outputDirectory: initialBuildOptions.outputDirectory
3032
+ rootDirectory: options.rootDirectory ?? initialBuildOptions.rootDirectory,
3033
+ outputDirectory: options.outputDirectory ?? initialBuildOptions.outputDirectory
2652
3034
  };
2653
3035
  const resolvedProjectConfig = resolveProjectConfig(mergedOptions);
2654
3036
  const resolvedBuildOptions = resolveBuildOptions(mergedOptions);
3037
+ const contentRoot = join(projectRoot, resolvedBuildOptions.rootDirectory);
2655
3038
  return {
2656
3039
  projectRoot,
2657
3040
  contentRoot,
@@ -2741,9 +3124,9 @@ function canonicalUrl(contextOrConfig, route) {
2741
3124
  /** Inject `lang` (and optional `dir`) onto the `<html>` tag for the route locale. */
2742
3125
  function injectHtmlLocaleAttributes(html, contextOrConfig, route) {
2743
3126
  const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2744
- const localeCode = route?.locale ?? projectConfig.i18n?.defaultLocale;
3127
+ const localeCode = route?.locale ?? projectConfig.locales?.default;
2745
3128
  if (!localeCode) return html;
2746
- const dir = (projectConfig.i18n?.locales.find((locale) => locale.code === localeCode))?.dir;
3129
+ const dir = (projectConfig.locales?.locales.find((locale) => locale.code === localeCode))?.dir;
2747
3130
  return html.replace(/<html\b([^>]*)>/i, (_match, attributes) => {
2748
3131
  let nextAttributes = attributes.replace(/\s+lang=("[^"]*"|'[^']*'|[^\s>]*)/i, "").replace(/\s+dir=("[^"]*"|'[^']*'|[^\s>]*)/i, "");
2749
3132
  nextAttributes = `${nextAttributes} lang="${escapeHtml(localeCode)}"`;
@@ -2921,20 +3304,23 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
2921
3304
  }
2922
3305
  });
2923
3306
  }
2924
- async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath, failOnError = true) {
3307
+ async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath) {
2925
3308
  const { render } = await import(pathToFileURL(ssrBundlePath).href);
2926
3309
  const template = readIndexHtml(outputDirectory);
2927
3310
  if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
2928
- for (const route of routes) try {
2929
- const finalHtml = injectSSRIntoTemplate(template, await render(route.path), contextOrConfig, route);
2930
- for (const outFile of routeOutputFiles(outputDirectory, route)) {
2931
- mkdirSync(dirname(outFile), { recursive: true });
2932
- writeFileSync(outFile, finalHtml, "utf-8");
3311
+ await Promise.all(routes.map(async (route) => {
3312
+ try {
3313
+ const appHtml = await render(route.path);
3314
+ const finalHtml = injectSSRIntoTemplate(template, appHtml, contextOrConfig, route);
3315
+ for (const outFile of routeOutputFiles(outputDirectory, route)) {
3316
+ mkdirSync(dirname(outFile), { recursive: true });
3317
+ writeFileSync(outFile, finalHtml, "utf-8");
3318
+ }
3319
+ } catch (err) {
3320
+ console.error(`[clarify] Failed to render route "${route.path}":`, err);
3321
+ throw err;
2933
3322
  }
2934
- } catch (err) {
2935
- console.error(`[clarify] Failed to render route "${route.path}":`, err);
2936
- if (failOnError) throw err;
2937
- }
3323
+ }));
2938
3324
  }
2939
3325
  //#endregion
2940
3326
  //#region source/core/runtime/startup.ts
@@ -3010,10 +3396,10 @@ var ClarifyContext = class {
3010
3396
  this.currentPlugins = plugins;
3011
3397
  }
3012
3398
  get isI18n() {
3013
- return Boolean(this.projectConfig.i18n);
3399
+ return Boolean(this.projectConfig.locales);
3014
3400
  }
3015
3401
  get defaultLocale() {
3016
- return this.projectConfig.i18n?.defaultLocale;
3402
+ return this.projectConfig.locales?.default;
3017
3403
  }
3018
3404
  get(key) {
3019
3405
  return this.values.get(key);
@@ -3207,10 +3593,10 @@ var ClarifyEngine = class {
3207
3593
  async discoverSite() {
3208
3594
  const plugins = this.ctx.plugins;
3209
3595
  const contentRoot = this.ctx.contentRoot;
3210
- const { i18n, tabs } = this.ctx.projectConfig;
3596
+ const { locales, navigation: navigationConfig } = this.ctx.projectConfig;
3211
3597
  let routes = await runPhase(plugins, "site:discover", this.ctx, async () => {
3212
3598
  const options = { contentProcessor: createProjectContentProcessor(plugins, this.ctx) };
3213
- const discovered = i18n ? await findLocalizedContentRoutes(contentRoot, i18n, options) : await findContentRoutes(contentRoot, contentRoot, options);
3599
+ const discovered = locales ? await findLocalizedContentRoutes(contentRoot, locales, options) : await findContentRoutes(contentRoot, contentRoot, options);
3214
3600
  const result = await runHooks(plugins, "routes:discover", {
3215
3601
  contentRoot,
3216
3602
  routes: discovered
@@ -3239,8 +3625,9 @@ var ClarifyEngine = class {
3239
3625
  };
3240
3626
  });
3241
3627
  });
3242
- routes = applyConfiguredPageRoutePaths(routes, tabs, i18n);
3243
- const navigation = tabs ? i18n ? buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) ?? {
3628
+ const tabs = navigationConfig?.tabs;
3629
+ routes = applyConfiguredPageRoutePaths(routes, tabs, locales);
3630
+ const navigation = tabs ? locales ? buildLocalizedNavigationFromTabsConfig(routes, tabs, locales) ?? {
3244
3631
  kind: "localized-tabbed",
3245
3632
  locales: {}
3246
3633
  } : buildNavigationFromTabsConfig(routes, tabs) : {
@@ -3270,7 +3657,7 @@ var ClarifyEngine = class {
3270
3657
  routes: this.routes,
3271
3658
  navigation: this.navigation,
3272
3659
  plugins: this.plugins,
3273
- themeEditor: this.runtime.command === "serve" || this.runtime.themeEditor === true || this.projectConfig.theme.editor,
3660
+ themeEditor: this.runtime.command === "serve" || this.runtime.themeEditor === true || this.projectConfig.features.themeEditor.enabled,
3274
3661
  version: this.ctx.version
3275
3662
  });
3276
3663
  this.virtualModules = await runHooks(this.plugins, "modules:before", this.virtualModules, this.ctx);
@@ -3313,7 +3700,7 @@ var ClarifyEngine = class {
3313
3700
  await runPhase(this.plugins, "ssg", this.ctx, async () => {
3314
3701
  if (process.env.SKIP_CLARIFY_SSG) return;
3315
3702
  if (!await runInterceptHooks(this.plugins, "ssg:shouldRun", this.ctx)) return;
3316
- if (this.generateOptions.ssg.failOnError) assertNoContentDiagnostics(this.routes);
3703
+ assertNoContentDiagnostics(this.routes);
3317
3704
  const outputDir = this.runtime.outputDirectory ?? this.generateOptions.outputDirectory;
3318
3705
  if (!outputDir) throw new Error("[clarify] outputDirectory is required before SSG runs");
3319
3706
  const ssrOutputDir = join(outputDir, ".ssr");
@@ -3327,10 +3714,10 @@ var ClarifyEngine = class {
3327
3714
  plugins: this.runtime.ssrPlugins ?? []
3328
3715
  });
3329
3716
  const ssrBundlePath = join(ssrOutputDir, "entry-server.js");
3330
- await renderSSGRoutes(this.routes, this.runtimeContext(), outputDir, ssrBundlePath, this.generateOptions.ssg.failOnError);
3717
+ await renderSSGRoutes(this.routes, this.runtimeContext(), outputDir, ssrBundlePath);
3331
3718
  } catch (err) {
3332
3719
  console.error("[clarify] SSG failed:", err);
3333
- if (this.generateOptions.ssg.failOnError) throw err;
3720
+ throw err;
3334
3721
  } finally {
3335
3722
  if (tempEntryPath) try {
3336
3723
  rmSync(tempEntryPath, { force: true });
@@ -3372,6 +3759,6 @@ function createClarifyEngine(options = {}) {
3372
3759
  return new ClarifyEngine(options);
3373
3760
  }
3374
3761
  //#endregion
3375
- export { cliPackageVersion as a, defineConfig as c, createClarifyTempDir as i, createClarifyEngine as n, cliPackageVersionWithCaret as o, createClarifyRuntimeAliases as r, createViteAdapter as s, ClarifyEngine as t };
3762
+ export { deserializeSearchIndex as a, cliPackageVersionWithCaret as c, createClarifyTempDir as i, createViteAdapter as l, createClarifyEngine as n, searchMcpIndex as o, createClarifyRuntimeAliases as r, cliPackageVersion as s, ClarifyEngine as t, defineConfig as u };
3376
3763
 
3377
- //# sourceMappingURL=engine-CyFtr8Pz.js.map
3764
+ //# sourceMappingURL=engine-CxnKEKv8.js.map