@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
@@ -48,6 +48,8 @@ let node_fs = require("node:fs");
48
48
  let vite = require("vite");
49
49
  let zod = require("zod");
50
50
  let yaml = require("yaml");
51
+ let _msgpack_msgpack = require("@msgpack/msgpack");
52
+ let _orama_orama = require("@orama/orama");
51
53
  let node_url = require("node:url");
52
54
  let _apidevtools_swagger_parser = require("@apidevtools/swagger-parser");
53
55
  _apidevtools_swagger_parser = __toESM(_apidevtools_swagger_parser, 1);
@@ -410,10 +412,10 @@ function frontmatterKeywords(frontmatter) {
410
412
  const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
411
413
  return keywords.length > 0 ? keywords : void 0;
412
414
  }
413
- function withAlternates(route, routes, i18n) {
415
+ function withAlternates(route, routes, locales) {
414
416
  const basePath = route.basePath ?? route.path;
415
417
  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]));
416
- const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
418
+ const alternates = Object.fromEntries(locales.locales.flatMap((locale) => {
417
419
  const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
418
420
  return alternate ? [[locale.code, alternate.path]] : [];
419
421
  }));
@@ -426,11 +428,11 @@ function withAlternates(route, routes, i18n) {
426
428
  * 为默认语言的 locale-prefixed 路由生成无前缀的"裸路径别名",方便不带语言
427
429
  * 前缀的 URL 也能访问。标记 `isBareAlias` 以便搜索索引生成时过滤重复索引。
428
430
  */
429
- function generateBareAliases(routes, i18n) {
431
+ function generateBareAliases(routes, locales) {
430
432
  const bareRoutes = [];
431
433
  const seenBare = new Set(routes.map((r) => r.path));
432
434
  for (const route of routes) {
433
- if (route.locale !== i18n.defaultLocale) continue;
435
+ if (route.locale !== locales.default) continue;
434
436
  const bp = route.basePath ?? route.path;
435
437
  if (bp === route.path || seenBare.has(bp)) continue;
436
438
  seenBare.add(bp);
@@ -442,42 +444,42 @@ function generateBareAliases(routes, i18n) {
442
444
  }
443
445
  return bareRoutes;
444
446
  }
445
- async function findLocalizedContentRoutes(contentRoot, i18n, options = {}) {
446
- if (!i18n) return findContentRoutes(contentRoot, contentRoot, options);
447
+ async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
448
+ if (!locales) return findContentRoutes(contentRoot, contentRoot, options);
447
449
  const localizedRoutes = [];
448
- for (const locale of i18n.locales) {
450
+ for (const locale of locales.locales) {
449
451
  const localeRoot = (0, node_path.join)(contentRoot, locale.code);
450
452
  const discovered = await findContentRoutes(localeRoot, localeRoot, options);
451
453
  for (const route of discovered) {
452
454
  const basePath = route.basePath ?? route.path;
453
455
  localizedRoutes.push({
454
456
  ...route,
455
- path: localizedRoutePath(basePath, locale.code, i18n),
457
+ path: localizedRoutePath(basePath, locale.code, locales),
456
458
  basePath,
457
459
  locale: locale.code,
458
460
  module: { virtualModuleId: virtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath) }
459
461
  });
460
462
  }
461
463
  }
462
- if (i18n.missing === "fallback") {
464
+ if (locales.missing === "fallback") {
463
465
  const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
464
- const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
466
+ const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
465
467
  for (const sourceRoute of defaultRoutes) {
466
468
  const basePath = sourceRoute.basePath ?? sourceRoute.path;
467
- for (const locale of i18n.locales) {
469
+ for (const locale of locales.locales) {
468
470
  const key = `${locale.code}:${basePath}`;
469
471
  if (routeByLocaleAndBase.has(key)) continue;
470
472
  localizedRoutes.push({
471
473
  ...sourceRoute,
472
- path: localizedRoutePath(basePath, locale.code, i18n),
474
+ path: localizedRoutePath(basePath, locale.code, locales),
473
475
  locale: locale.code,
474
476
  isFallback: true
475
477
  });
476
478
  }
477
479
  }
478
480
  }
479
- const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
480
- return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, i18n)];
481
+ const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes, locales));
482
+ return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
481
483
  }
482
484
  function buildNavigation(routes) {
483
485
  const root = [];
@@ -554,7 +556,7 @@ function collectExplicitPagePathIntents(tabs) {
554
556
  }
555
557
  return intents;
556
558
  }
557
- function applyConfiguredPageRoutePaths(routes, tabs, i18n) {
559
+ function applyConfiguredPageRoutePaths(routes, tabs, locales) {
558
560
  const pageIntents = collectExplicitPagePathIntents(tabs);
559
561
  if (!pageIntents.length) return routes;
560
562
  const additions = [];
@@ -570,15 +572,15 @@ function applyConfiguredPageRoutePaths(routes, tabs, i18n) {
570
572
  existingKeys.add(key);
571
573
  additions.push({
572
574
  ...route,
573
- path: route.locale && i18n ? localizedRoutePath(targetBasePath, route.locale, i18n) : targetBasePath,
575
+ path: route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath,
574
576
  basePath: targetBasePath
575
577
  });
576
578
  }
577
579
  }
578
580
  const nextRoutes = [...routes, ...additions];
579
- const routesWithAlternates = i18n ? nextRoutes.map((route) => withAlternates(route, nextRoutes, i18n)) : nextRoutes;
580
- if (!i18n) return routesWithAlternates;
581
- return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, i18n)];
581
+ const routesWithAlternates = locales ? nextRoutes.map((route) => withAlternates(route, nextRoutes, locales)) : nextRoutes;
582
+ if (!locales) return routesWithAlternates;
583
+ return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
582
584
  }
583
585
  function buildNavigationFromPagesConfig(routes, config) {
584
586
  if (!config || config === "FileTree") return buildNavigation(routes);
@@ -641,25 +643,25 @@ function buildNavigationFromConfig(routes, config) {
641
643
  };
642
644
  });
643
645
  }
644
- function localizeNavigationPaths(nodes, locale, i18n) {
646
+ function localizeNavigationPaths(nodes, locale, locales) {
645
647
  return nodes.map((node) => ({
646
648
  ...node,
647
- path: localizedRoutePath(node.path, locale, i18n),
648
- children: node.children ? localizeNavigationPaths(node.children, locale, i18n) : void 0
649
+ path: localizedRoutePath(node.path, locale, locales),
650
+ children: node.children ? localizeNavigationPaths(node.children, locale, locales) : void 0
649
651
  }));
650
652
  }
651
- function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
653
+ function buildLocalizedNavigationForLocale(routes, config, locale, locales) {
652
654
  if (!config || config === "FileTree") return localizeNavigationPaths(buildNavigation(routes.map((route) => ({
653
655
  ...route,
654
656
  path: route.basePath ?? route.path
655
- }))), locale, i18n);
657
+ }))), locale, locales);
656
658
  const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
657
659
  const buildItem = (item) => {
658
660
  if (typeof item !== "string" && "group" in item) {
659
661
  const children = item.pages.map(buildItem);
660
662
  return {
661
663
  path: firstNavigationPath(children),
662
- title: resolveLocalizedText(item.group, locale, i18n.defaultLocale) ?? "",
664
+ title: resolveLocalizedText(item.group, locale, locales.default) ?? "",
663
665
  icon: item.icon,
664
666
  children
665
667
  };
@@ -667,10 +669,10 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
667
669
  const intent = routeIntentFromPagesItem(item);
668
670
  const basePath = intent.kind === "openapi" ? openAPIPagePathFromRef(intent.ref, intent.tagFilter, intent.path) : routePathFromRef(intent.ref, intent.path);
669
671
  const route = routeMap.get(basePath);
670
- const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
672
+ const path = route?.path ?? localizedRoutePath(basePath, locale, locales);
671
673
  return {
672
- path: (intent.kind === "page" && intent.redirect ? localizedRoutePath(routePathFromRef(intent.redirect), locale, i18n) : void 0) ?? path,
673
- title: resolveLocalizedText(intent.title, locale, i18n.defaultLocale) ?? route?.meta.title ?? kebabToTitle(basePath.split("/").pop() ?? intent.ref),
674
+ path: (intent.kind === "page" && intent.redirect ? localizedRoutePath(routePathFromRef(intent.redirect), locale, locales) : void 0) ?? path,
675
+ title: resolveLocalizedText(intent.title, locale, locales.default) ?? route?.meta.title ?? kebabToTitle(basePath.split("/").pop() ?? intent.ref),
674
676
  icon: intent.icon,
675
677
  sections: route?.meta.sections ? navigationSections(route.meta.sections) : void 0
676
678
  };
@@ -678,24 +680,24 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
678
680
  return config.map((group) => {
679
681
  const children = group.pages.map(buildItem);
680
682
  return {
681
- path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
682
- title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
683
+ path: children[0]?.path ?? localizedRoutePath("/", locale, locales),
684
+ title: resolveLocalizedText(group.group, locale, locales.default) ?? "",
683
685
  icon: group.icon,
684
686
  children
685
687
  };
686
688
  });
687
689
  }
688
- function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
689
- if (!i18n) return void 0;
690
+ function buildLocalizedNavigationFromTabsConfig(routes, tabs, localesConfig) {
691
+ if (!localesConfig) return void 0;
690
692
  const locales = {};
691
- for (const locale of i18n.locales) {
693
+ for (const locale of localesConfig.locales) {
692
694
  const localeRoutes = routes.filter((route) => route.locale === locale.code);
693
695
  locales[locale.code] = { tabs: tabs.map((tab) => {
694
- const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
696
+ const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, localesConfig);
695
697
  return {
696
698
  type: "tab",
697
699
  path: firstNavigationPath(children),
698
- title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
700
+ title: resolveLocalizedText(tab.tab, locale.code, localesConfig.default) ?? "",
699
701
  icon: tab.icon,
700
702
  children
701
703
  };
@@ -762,7 +764,7 @@ function inferRouteForFile(filePath, contentRoot, defaultLocale) {
762
764
  const localeSegmentIndex = fileLocale ? segments.indexOf(fileLocale) : -1;
763
765
  const basePath = basePathFromRef(localeSegmentIndex >= 0 ? segments.slice(localeSegmentIndex + 1).join("/") : ref);
764
766
  const path = fileLocale && defaultLocale ? localizedRoutePath(basePath, fileLocale, {
765
- defaultLocale,
767
+ default: defaultLocale,
766
768
  missing: "fallback",
767
769
  locales: []
768
770
  }) : basePath;
@@ -815,12 +817,12 @@ async function handleDevRouteRequest(req, res, routes, context) {
815
817
  }
816
818
  const file = typeof body === "object" && body !== null ? body.file : void 0;
817
819
  if (typeof file === "string" && file.length > 0) {
818
- const route = resolveRouteForFile(file, routes, context.projectConfig.i18n?.defaultLocale);
820
+ const route = resolveRouteForFile(file, routes, context.projectConfig.locales?.default);
819
821
  if (route) {
820
822
  res.end(JSON.stringify(toDevRouteEntry(route)));
821
823
  return;
822
824
  }
823
- const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.i18n?.defaultLocale);
825
+ const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.locales?.default);
824
826
  res.end(JSON.stringify(inferred));
825
827
  return;
826
828
  }
@@ -846,8 +848,7 @@ function resolveThemeConfig(theme = {}) {
846
848
  layout: {
847
849
  ...presetTheme.layout,
848
850
  ...theme.layout
849
- },
850
- editor: theme.editor ?? presetTheme.editor
851
+ }
851
852
  };
852
853
  }
853
854
  //#endregion
@@ -855,26 +856,26 @@ function resolveThemeConfig(theme = {}) {
855
856
  var clarifyLogoConfigSchema = zod.z.union([zod.z.string(), zod.z.object({
856
857
  light: zod.z.string().optional(),
857
858
  dark: zod.z.string().optional()
858
- })]);
859
+ }).strict()]);
859
860
  var clarifyFaviconConfigSchema = zod.z.union([zod.z.string(), zod.z.object({
860
861
  light: zod.z.string().optional(),
861
862
  dark: zod.z.string().optional()
862
- })]);
863
+ }).strict()]);
863
864
  var clarifyLocalizedTextSchema = zod.z.union([zod.z.string(), zod.z.record(zod.z.string(), zod.z.string())]);
864
865
  var clarifyLocaleConfigSchema = zod.z.object({
865
866
  code: zod.z.string(),
866
867
  label: zod.z.string(),
867
868
  dir: zod.z.union([zod.z.literal("ltr"), zod.z.literal("rtl")]).optional()
868
- });
869
- var clarifyI18nConfigSchema = zod.z.object({
870
- defaultLocale: zod.z.string().optional(),
869
+ }).strict();
870
+ var clarifyLocalesConfigSchema = zod.z.object({
871
+ default: zod.z.string().optional(),
871
872
  missing: zod.z.union([
872
873
  zod.z.literal("fallback"),
873
874
  zod.z.literal("404"),
874
875
  zod.z.literal("hide")
875
876
  ]).optional(),
876
877
  locales: zod.z.array(clarifyLocaleConfigSchema).min(1)
877
- }).superRefine((config, ctx) => {
878
+ }).strict().superRefine((config, ctx) => {
878
879
  const localeCodes = /* @__PURE__ */ new Set();
879
880
  for (const [index, locale] of config.locales.entries()) {
880
881
  if (localeCodes.has(locale.code)) ctx.addIssue({
@@ -888,17 +889,17 @@ var clarifyI18nConfigSchema = zod.z.object({
888
889
  });
889
890
  localeCodes.add(locale.code);
890
891
  }
891
- if (config.defaultLocale && !localeCodes.has(config.defaultLocale)) ctx.addIssue({
892
+ if (config.default && !localeCodes.has(config.default)) ctx.addIssue({
892
893
  code: zod.z.ZodIssueCode.custom,
893
- message: "defaultLocale must be one of i18n.locales",
894
- path: ["defaultLocale"]
894
+ message: "default must be one of locales.locales",
895
+ path: ["default"]
895
896
  });
896
897
  });
897
898
  var clarifyNavbarLinkSchema = zod.z.object({
898
899
  label: clarifyLocalizedTextSchema,
899
900
  href: zod.z.string(),
900
901
  external: zod.z.boolean().optional()
901
- });
902
+ }).strict();
902
903
  var clarifyBannerConfigOptionsSchema = zod.z.object({
903
904
  content: clarifyLocalizedTextSchema,
904
905
  dismissible: zod.z.boolean().optional(),
@@ -916,11 +917,11 @@ var clarifyVariableValueSchema = zod.z.lazy(() => zod.z.union([
916
917
  zod.z.record(zod.z.string(), clarifyVariableValueSchema)
917
918
  ]));
918
919
  var clarifyVariablesConfigSchema = zod.z.record(zod.z.string(), clarifyVariableValueSchema);
919
- var clarifySourceConfigSchema = zod.z.object({
920
- repository: zod.z.string(),
920
+ var clarifyRepositoryConfigSchema = zod.z.object({
921
+ url: zod.z.string().optional(),
921
922
  branch: zod.z.string().optional(),
922
923
  directory: zod.z.string().optional()
923
- });
924
+ }).strict();
924
925
  var clarifyPagesItemSchema = zod.z.lazy(() => zod.z.union([
925
926
  zod.z.string(),
926
927
  clarifyPagesGroupSchema,
@@ -930,32 +931,32 @@ var clarifyPagesItemSchema = zod.z.lazy(() => zod.z.union([
930
931
  title: clarifyLocalizedTextSchema.optional(),
931
932
  icon: zod.z.string().optional(),
932
933
  redirect: zod.z.string().optional()
933
- }),
934
+ }).strict(),
934
935
  zod.z.object({
935
936
  openapi: zod.z.string(),
936
937
  path: zod.z.string().optional(),
937
938
  icon: zod.z.string().optional(),
938
939
  title: clarifyLocalizedTextSchema.optional(),
939
- filter: zod.z.object({ tags: zod.z.array(zod.z.string()).optional() }).optional()
940
- })
940
+ filter: zod.z.object({ tags: zod.z.array(zod.z.string()).optional() }).strict().optional()
941
+ }).strict()
941
942
  ]));
942
943
  var clarifyPagesGroupSchema = zod.z.lazy(() => zod.z.object({
943
944
  group: clarifyLocalizedTextSchema,
944
945
  icon: zod.z.string().optional(),
945
946
  pages: zod.z.array(clarifyPagesItemSchema)
946
- }));
947
+ }).strict());
947
948
  var clarifyPagesConfigSchema = zod.z.union([zod.z.array(clarifyPagesGroupSchema), zod.z.literal("FileTree")]);
948
949
  var clarifyTabItemSchema = zod.z.object({
949
950
  tab: clarifyLocalizedTextSchema,
950
951
  icon: zod.z.string().optional(),
951
952
  pages: clarifyPagesConfigSchema.optional()
952
- });
953
+ }).strict();
953
954
  var clarifyTabsConfigSchema = zod.z.array(clarifyTabItemSchema);
954
955
  var clarifyThemePresetSchema = zod.z.enum(["default", "base"]);
955
956
  var clarifyThemeModeColorValueSchema = zod.z.object({
956
957
  light: zod.z.string().optional(),
957
958
  dark: zod.z.string().optional()
958
- });
959
+ }).strict();
959
960
  var clarifyThemeColorValueSchema = zod.z.union([zod.z.string(), clarifyThemeModeColorValueSchema]);
960
961
  var clarifyThemeColorTokensConfigSchema = zod.z.object({
961
962
  primary: clarifyThemeColorValueSchema.optional(),
@@ -966,42 +967,65 @@ var clarifyThemeColorTokensConfigSchema = zod.z.object({
966
967
  muted: clarifyThemeColorValueSchema.optional(),
967
968
  border: clarifyThemeColorValueSchema.optional(),
968
969
  codeBackground: clarifyThemeColorValueSchema.optional()
969
- });
970
+ }).strict();
970
971
  var clarifyThemeRadiusTokensConfigSchema = zod.z.object({
971
972
  sm: zod.z.string().optional(),
972
973
  md: zod.z.string().optional(),
973
974
  lg: zod.z.string().optional(),
974
975
  xl: zod.z.string().optional()
975
- });
976
+ }).strict();
976
977
  var clarifyThemeTokensConfigSchema = zod.z.object({
977
978
  colors: clarifyThemeColorTokensConfigSchema.optional(),
978
979
  radius: clarifyThemeRadiusTokensConfigSchema.optional()
979
- });
980
- var clarifyThemeLayoutConfigSchema = zod.z.object({ maxWidth: zod.z.string().optional() });
980
+ }).strict();
981
+ var clarifyThemeLayoutConfigSchema = zod.z.object({ maxWidth: zod.z.string().optional() }).strict();
981
982
  var clarifyThemeConfigSchema = zod.z.object({
982
983
  preset: clarifyThemePresetSchema.optional(),
983
984
  tokens: clarifyThemeTokensConfigSchema.optional(),
984
- layout: clarifyThemeLayoutConfigSchema.optional(),
985
- editor: zod.z.boolean().optional()
986
- });
985
+ layout: clarifyThemeLayoutConfigSchema.optional()
986
+ }).strict();
987
+ function featureSchema(shape, defaults) {
988
+ const objectSchema = zod.z.object({
989
+ enabled: zod.z.boolean().default(true),
990
+ ...shape
991
+ }).strict().default({
992
+ enabled: true,
993
+ ...defaults
994
+ });
995
+ return zod.z.union([zod.z.boolean(), objectSchema]).default({
996
+ enabled: true,
997
+ ...defaults
998
+ }).transform((value) => typeof value === "boolean" ? {
999
+ enabled: value,
1000
+ ...defaults
1001
+ } : value);
1002
+ }
1003
+ var clarifyFeaturesConfigSchema = zod.z.object({
1004
+ search: featureSchema({ mcp: zod.z.boolean().default(true) }, { mcp: true }),
1005
+ repository: featureSchema(clarifyRepositoryConfigSchema.shape, {}),
1006
+ themeEditor: featureSchema({}, {}),
1007
+ openapi: featureSchema({ playground: zod.z.boolean().default(true) }, { playground: true })
1008
+ }).strict();
987
1009
  var clarifyProjectConfigSchema = zod.z.object({
988
1010
  title: zod.z.string().optional(),
989
1011
  description: zod.z.string().optional(),
990
1012
  siteUrl: zod.z.string().optional(),
991
- source: clarifySourceConfigSchema.optional(),
1013
+ routePrefix: zod.z.string().default("/"),
1014
+ assetPrefix: zod.z.string().optional(),
992
1015
  logo: clarifyLogoConfigSchema.optional(),
993
1016
  homeUrl: zod.z.string().optional(),
994
1017
  favicon: clarifyFaviconConfigSchema.optional(),
995
1018
  theme: clarifyThemeConfigSchema.optional(),
996
- routePrefix: zod.z.string().optional(),
997
- assetPrefix: zod.z.string().optional(),
998
- navbar: zod.z.object({ links: zod.z.array(clarifyNavbarLinkSchema).optional() }).optional(),
1019
+ navigation: zod.z.object({
1020
+ links: zod.z.array(clarifyNavbarLinkSchema).optional(),
1021
+ tabs: clarifyTabsConfigSchema.optional()
1022
+ }).strict().optional(),
999
1023
  banner: clarifyBannerConfigOptionsSchema.optional(),
1000
1024
  footer: clarifyFooterConfigSchema.optional(),
1025
+ locales: clarifyLocalesConfigSchema.optional(),
1001
1026
  variables: clarifyVariablesConfigSchema.optional(),
1002
- i18n: clarifyI18nConfigSchema.optional(),
1003
- tabs: clarifyTabsConfigSchema.optional()
1004
- });
1027
+ features: clarifyFeaturesConfigSchema.optional()
1028
+ }).strict();
1005
1029
  //#endregion
1006
1030
  //#region source/core/config/config.ts
1007
1031
  function formatIssuePath(path) {
@@ -1023,17 +1047,20 @@ function validateProjectConfig(value) {
1023
1047
  if (!result.success) throw new Error(formatProjectConfigError(result.error));
1024
1048
  return result.data;
1025
1049
  }
1026
- function resolveI18nConfig(i18n) {
1027
- if (!i18n) return void 0;
1028
- const firstLocale = i18n.locales[0]?.code;
1029
- const defaultLocale = i18n.defaultLocale ?? firstLocale;
1050
+ function resolveLocalesConfig(locales) {
1051
+ if (!locales) return void 0;
1052
+ const firstLocale = locales.locales[0]?.code;
1053
+ const defaultLocale = locales.default ?? firstLocale;
1030
1054
  if (!defaultLocale) return void 0;
1031
1055
  return {
1032
- defaultLocale,
1033
- missing: i18n.missing ?? "fallback",
1034
- locales: i18n.locales
1056
+ default: defaultLocale,
1057
+ missing: locales.missing ?? "fallback",
1058
+ locales: locales.locales
1035
1059
  };
1036
1060
  }
1061
+ function resolveFeaturesConfig(features = {}) {
1062
+ return clarifyFeaturesConfigSchema.parse(features);
1063
+ }
1037
1064
  function resolveRoutePrefix(routePrefix) {
1038
1065
  const trimmed = routePrefix?.trim();
1039
1066
  if (!trimmed || trimmed === "/") return "/";
@@ -1054,26 +1081,24 @@ function resolveProjectConfig(config = {}) {
1054
1081
  title: config.title ?? "Clarify Docs",
1055
1082
  description: config.description ?? "",
1056
1083
  siteUrl: config.siteUrl,
1057
- source: config.source,
1084
+ routePrefix,
1085
+ assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
1058
1086
  logo: config.logo,
1059
1087
  homeUrl: config.homeUrl,
1060
1088
  favicon: config.favicon,
1061
- routePrefix,
1062
- assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
1063
1089
  theme: resolveThemeConfig(config.theme),
1064
- navbar: config.navbar,
1090
+ navigation: config.navigation,
1065
1091
  banner: config.banner,
1066
1092
  footer: config.footer,
1093
+ locales: resolveLocalesConfig(config.locales),
1067
1094
  variables: config.variables ?? {},
1068
- i18n: resolveI18nConfig(config.i18n),
1069
- tabs: config.tabs
1095
+ features: resolveFeaturesConfig(config.features)
1070
1096
  };
1071
1097
  }
1072
1098
  //#endregion
1073
1099
  //#region source/core/config/user-config.ts
1074
1100
  function defineConfig(config) {
1075
- validateProjectConfig(config);
1076
- return config;
1101
+ return validateClarifyConfig(config);
1077
1102
  }
1078
1103
  /**
1079
1104
  * Config filenames Clarify CLI looks for at the project root, in resolution
@@ -1097,18 +1122,23 @@ function assertConfigObject(config, configFile) {
1097
1122
  }
1098
1123
  function loadJsonConfig(configFile) {
1099
1124
  const config = assertConfigObject(JSON.parse((0, node_fs.readFileSync)(configFile, "utf-8")), configFile);
1100
- const projectConfig = validateProjectConfig(config);
1101
- const ssg = config.ssg && typeof config.ssg === "object" && !Array.isArray(config.ssg) ? config.ssg : void 0;
1102
- return {
1125
+ if ("plugins" in config) throw new Error("[clarify] clarify.json does not support plugins; use clarify.ts or clarify.js");
1126
+ return validateClarifyConfig(config);
1127
+ }
1128
+ function validateClarifyConfig(config) {
1129
+ const { plugins, ...projectConfigInput } = config;
1130
+ if (plugins !== void 0 && !Array.isArray(plugins)) throw new Error("[clarify] config field \"plugins\" is invalid: Expected an array");
1131
+ const projectConfig = validateProjectConfig(projectConfigInput);
1132
+ return plugins ? {
1103
1133
  ...projectConfig,
1104
- ...ssg ? { ssg } : {}
1105
- };
1134
+ plugins
1135
+ } : projectConfig;
1106
1136
  }
1107
1137
  async function loadClarifyConfig(root, env) {
1108
1138
  const configFile = findClarifyConfigFile(root);
1109
1139
  if (!configFile) return {};
1110
1140
  if (configFile.endsWith(".json")) return loadJsonConfig(configFile);
1111
- return assertConfigObject((await (0, vite.loadConfigFromFile)(env, configFile, root, "silent"))?.config, configFile);
1141
+ return validateClarifyConfig(assertConfigObject((await (0, vite.loadConfigFromFile)(env, configFile, root, "silent"))?.config, configFile));
1112
1142
  }
1113
1143
  /**
1114
1144
  * File extensions Clarify treats as content files. Kept in sync with the
@@ -1134,9 +1164,9 @@ function buildProjectInfo(context) {
1134
1164
  contentRoot: (contentRoot.startsWith(projectRoot) ? contentRoot.slice(projectRoot.length).replace(/^[/\\]+/, "") : contentRoot) || "source",
1135
1165
  projectRoot
1136
1166
  };
1137
- if (projectConfig.i18n) response.i18n = {
1138
- defaultLocale: projectConfig.i18n.defaultLocale,
1139
- locales: projectConfig.i18n.locales.map((l) => l.code)
1167
+ if (projectConfig.locales) response.locales = {
1168
+ default: projectConfig.locales.default,
1169
+ locales: projectConfig.locales.locales.map((l) => l.code)
1140
1170
  };
1141
1171
  return response;
1142
1172
  }
@@ -1196,10 +1226,23 @@ function resolveVirtualModuleId(id, modules, routes) {
1196
1226
  if (route) return resolveVirtualId(route.module.virtualModuleId);
1197
1227
  return null;
1198
1228
  }
1199
- function generateConfigModule(projectConfig, buildOptions, version) {
1229
+ function generateConfigModule(projectConfig, version) {
1200
1230
  const runtimeConfig = {
1201
- ...projectConfig,
1202
- ...buildOptions
1231
+ title: projectConfig.title,
1232
+ description: projectConfig.description,
1233
+ siteUrl: projectConfig.siteUrl,
1234
+ routePrefix: projectConfig.routePrefix,
1235
+ assetPrefix: projectConfig.assetPrefix,
1236
+ logo: projectConfig.logo,
1237
+ homeUrl: projectConfig.homeUrl,
1238
+ favicon: projectConfig.favicon,
1239
+ theme: projectConfig.theme,
1240
+ navigation: projectConfig.navigation,
1241
+ banner: projectConfig.banner,
1242
+ footer: projectConfig.footer,
1243
+ locales: projectConfig.locales,
1244
+ variables: projectConfig.variables,
1245
+ features: projectConfig.features
1203
1246
  };
1204
1247
  if (version) runtimeConfig.version = version;
1205
1248
  return `export const config = ${JSON.stringify(runtimeConfig)};`;
@@ -1303,7 +1346,7 @@ function buildVirtualModules(args) {
1303
1346
  const modules = /* @__PURE__ */ new Map();
1304
1347
  const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
1305
1348
  const allPlugins = [...args.plugins ?? []];
1306
- modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions, args.version));
1349
+ modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.version));
1307
1350
  modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, "client"));
1308
1351
  modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, "server"));
1309
1352
  modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
@@ -1528,7 +1571,7 @@ function createViteAdapter(engine) {
1528
1571
  const compilePolicyPlugin = {
1529
1572
  name: "clarify:content-compile-policy",
1530
1573
  configResolved(config) {
1531
- contentCompileErrorPolicy = config.command === "build" && engine.generateOptions.ssg.failOnError ? "throw" : "diagnostic";
1574
+ contentCompileErrorPolicy = config.command === "build" ? "throw" : "diagnostic";
1532
1575
  }
1533
1576
  };
1534
1577
  const tailwind = (0, _tailwindcss_vite.default)();
@@ -1544,7 +1587,7 @@ function createViteAdapter(engine) {
1544
1587
  }
1545
1588
  var cliPackageVersion = {
1546
1589
  name: "@clarify-labs/cli",
1547
- version: "0.10.3",
1590
+ version: "0.11.1",
1548
1591
  license: "AGPL-3.0-only",
1549
1592
  type: "module",
1550
1593
  repository: {
@@ -1585,6 +1628,9 @@ var cliPackageVersion = {
1585
1628
  "@clarify-labs/templates": "workspace:*",
1586
1629
  "@mdx-js/mdx": "^3.1.1",
1587
1630
  "@mdx-js/rollup": "^3.1.1",
1631
+ "@modelcontextprotocol/sdk": "^1.29.0",
1632
+ "@msgpack/msgpack": "^3.1.3",
1633
+ "@orama/orama": "^3.1.18",
1588
1634
  "@shikijs/rehype": "^4.3.0",
1589
1635
  "@tailwindcss/vite": "^4.3.2",
1590
1636
  "@vitejs/plugin-react": "^6.0.3",
@@ -1603,7 +1649,6 @@ var cliPackageVersion = {
1603
1649
  "rehype-raw": "^7.0.0",
1604
1650
  "rehype-slug": "^6.0.0",
1605
1651
  "remark": "^15.0.1",
1606
- "remark-gfm": "^4.0.1",
1607
1652
  "shiki": "4.3.0",
1608
1653
  "unist-util-visit": "^5.1.0",
1609
1654
  "vite": "^8.1.4",
@@ -1624,8 +1669,7 @@ function resolveBuildOptions(options = {}) {
1624
1669
  return {
1625
1670
  projectRoot: options.projectRoot ?? process.cwd(),
1626
1671
  rootDirectory: options.rootDirectory ?? "source",
1627
- outputDirectory: options.outputDirectory,
1628
- ssg: { failOnError: options.ssg?.failOnError ?? true }
1672
+ outputDirectory: options.outputDirectory
1629
1673
  };
1630
1674
  }
1631
1675
  //#endregion
@@ -1683,7 +1727,7 @@ function llmsTxtListItem(route, basePath) {
1683
1727
  return description ? `- [${route.meta.title}](${basePath}${contentArtifactUrl}): ${description}` : `- [${route.meta.title}](${basePath}${contentArtifactUrl})`;
1684
1728
  }
1685
1729
  function llmsTxtLocaleLabel(locale, projectConfig) {
1686
- return projectConfig.i18n?.locales.find((item) => item.code === locale)?.label ?? locale;
1730
+ return projectConfig.locales?.locales.find((item) => item.code === locale)?.label ?? locale;
1687
1731
  }
1688
1732
  function groupRoutesByLocale(routes) {
1689
1733
  const groups = /* @__PURE__ */ new Map();
@@ -1695,7 +1739,7 @@ function groupRoutesByLocale(routes) {
1695
1739
  }
1696
1740
  function groupLlmsTxtRoutesByLocale(routes, projectConfig) {
1697
1741
  const routesBySource = /* @__PURE__ */ new Map();
1698
- const defaultLocale = projectConfig.i18n?.defaultLocale;
1742
+ const defaultLocale = projectConfig.locales?.default;
1699
1743
  for (const route of routes) {
1700
1744
  const sourceKey = `${route.locale ?? defaultLocale ?? "default"}:${route.basePath ?? route.artifacts?.contentArtifactUrl ?? route.path}`;
1701
1745
  const previousRoute = routesBySource.get(sourceKey);
@@ -1885,6 +1929,347 @@ function createHtmlShellPlugin() {
1885
1929
  };
1886
1930
  }
1887
1931
  //#endregion
1932
+ //#region source/plugins/mcp-search/mcp-config.ts
1933
+ /**
1934
+ * Normalize a route prefix into a root-relative path segment.
1935
+ *
1936
+ * `'/'` -> `''`, `'/docs'` -> `'/docs'`, `'docs/'` -> `'/docs'`.
1937
+ */
1938
+ function normalizePrefix(routePrefix) {
1939
+ if (!routePrefix || routePrefix === "/") return "";
1940
+ return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
1941
+ }
1942
+ /**
1943
+ * Assemble the MCP site config from build-time metadata.
1944
+ *
1945
+ * Unlike the previous pagefind-based descriptor, this no longer reads an
1946
+ * entry JSON file - the Orama index is a single binary blob, so the caller
1947
+ * supplies the document count and locale list directly from the build step
1948
+ * that produced the index.
1949
+ */
1950
+ function createMcpSiteConfig(projectConfig, options) {
1951
+ const prefix = normalizePrefix(projectConfig.routePrefix);
1952
+ return {
1953
+ version: 3,
1954
+ site: {
1955
+ title: projectConfig.title,
1956
+ description: projectConfig.description,
1957
+ url: projectConfig.siteUrl,
1958
+ routePrefix: projectConfig.routePrefix ?? "/"
1959
+ },
1960
+ capabilities: { search: {
1961
+ type: "search",
1962
+ indexPath: `${prefix}/mcp-search.msp`,
1963
+ defaultLocale: projectConfig.locales?.default,
1964
+ documentCount: options.documentCount,
1965
+ locales: options.locales
1966
+ } }
1967
+ };
1968
+ }
1969
+ //#endregion
1970
+ //#region source/plugins/mcp-search/orama-index.ts
1971
+ /**
1972
+ * Orama schema for the MCP search index.
1973
+ *
1974
+ * - `path`, `title`, `description`, `content` are full-text searchable strings.
1975
+ * - `locale` is an `enum` so we can use `where: { locale: { eq } }` filters at
1976
+ * query time. (`string` fields cannot be filtered in Orama.)
1977
+ * - `keywords` is a `string[]` also indexed for full-text matching.
1978
+ */
1979
+ var mcpSearchSchema = {
1980
+ path: "string",
1981
+ title: "string",
1982
+ description: "string",
1983
+ locale: "enum",
1984
+ content: "string",
1985
+ keywords: "string[]"
1986
+ };
1987
+ /**
1988
+ * Intl.Segmenter cache keyed by BCP 47 tag. Segmenter construction is not
1989
+ * cheap, and the MCP server issues many queries against the same locale, so
1990
+ * we reuse instances. The cache is module-scoped: build and runtime share it
1991
+ * in the same process, and separate processes get their own - which is fine.
1992
+ */
1993
+ var segmenterCache = /* @__PURE__ */ new Map();
1994
+ function getSegmenter(locale) {
1995
+ let seg = segmenterCache.get(locale);
1996
+ if (!seg) {
1997
+ seg = new Intl.Segmenter(locale, { granularity: "word" });
1998
+ segmenterCache.set(locale, seg);
1999
+ }
2000
+ return seg;
2001
+ }
2002
+ /**
2003
+ * Map a Clarify locale code (e.g. `zh-CN`, `en-US`, `ja-JP`) to a BCP 47 tag
2004
+ * suitable for `Intl.Segmenter`. Returns `null` for locales we intentionally
2005
+ * want to tokenize with the Latin/whitespace fallback (faster, and avoids
2006
+ * forcing CJK-style word segmentation on Latin scripts).
2007
+ */
2008
+ function segmenterLocale(locale) {
2009
+ if (!locale) return null;
2010
+ const lower = locale.toLowerCase();
2011
+ if (lower.startsWith("zh")) return "zh-CN";
2012
+ if (lower.startsWith("ja")) return "ja-JP";
2013
+ if (lower.startsWith("ko")) return "ko-KR";
2014
+ return null;
2015
+ }
2016
+ /**
2017
+ * Tokenize text for Orama indexing and querying.
2018
+ *
2019
+ * CJK locales use `Intl.Segmenter` (word granularity), which splits
2020
+ * spaceless scripts into words and individual characters. Latin/whitespace
2021
+ * locales fall back to a Unicode-aware split on spaces and punctuation.
2022
+ *
2023
+ * All tokens are lowercased so queries match regardless of case.
2024
+ */
2025
+ function tokenizeForSearch(text, locale) {
2026
+ if (!text) return [];
2027
+ const segLocale = segmenterLocale(locale);
2028
+ if (segLocale) {
2029
+ const seg = getSegmenter(segLocale);
2030
+ return Array.from(seg.segment(text)).filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
2031
+ }
2032
+ return text.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
2033
+ }
2034
+ /**
2035
+ * Build an Orama tokenizer component bound to a specific default locale.
2036
+ *
2037
+ * The tokenizer receives the raw field value and the per-field language hint
2038
+ * from Orama (which we don't set per-field, so it falls back to the default
2039
+ * language). We resolve the segmenter locale from the default locale at
2040
+ * construction time so the same tokenizer handles all documents in a
2041
+ * predominantly single-locale index.
2042
+ *
2043
+ * For multi-locale indexes, the tokenizer is also used at query time. Since
2044
+ * the query locale is not known to Orama's tokenizer callback, we use the
2045
+ * index's default locale - this is acceptable because:
2046
+ * - CJK queries get CJK segmentation (correct)
2047
+ * - Latin queries against a CJK-default index still work because Latin
2048
+ * text has no word boundaries for CJK segmenters to mis-split; the
2049
+ * segmenter passes through Latin words as-is.
2050
+ */
2051
+ function createMcpTokenizer(defaultLocale) {
2052
+ return {
2053
+ language: defaultLocale ?? "en",
2054
+ normalizationCache: /* @__PURE__ */ new Map(),
2055
+ tokenize(raw, language) {
2056
+ return tokenizeForSearch(typeof raw === "string" ? raw : String(raw ?? ""), language ?? defaultLocale);
2057
+ }
2058
+ };
2059
+ }
2060
+ /**
2061
+ * Compose the searchable text for a route. Shared between the Pagefind
2062
+ * (browser) and Orama (MCP) indexing paths so both backends see the same
2063
+ * field order and content.
2064
+ */
2065
+ function routeSearchContent(route) {
2066
+ return [
2067
+ route.meta.title,
2068
+ route.meta.description,
2069
+ route.meta.keywords?.join(" "),
2070
+ route.meta.sections?.map((section) => section.title).join(" "),
2071
+ route.source.content
2072
+ ].filter(Boolean).join("\n\n");
2073
+ }
2074
+ /**
2075
+ * Convert a `ContentRoute` into the document shape stored in the Orama index.
2076
+ */
2077
+ function routeToSearchDocument(route, defaultLocale) {
2078
+ if (route.isBareAlias) return null;
2079
+ const content = routeSearchContent(route);
2080
+ if (!content.trim()) return null;
2081
+ return {
2082
+ path: route.path,
2083
+ title: route.meta.title ?? "",
2084
+ description: route.meta.description ?? "",
2085
+ locale: route.locale ?? defaultLocale ?? "en",
2086
+ content,
2087
+ keywords: route.meta.keywords ?? []
2088
+ };
2089
+ }
2090
+ /**
2091
+ * Create an Orama index pre-configured with the MCP schema and CJK-aware
2092
+ * tokenizer. Both the build step and the runtime loader use this so the
2093
+ * tokenizer is guaranteed to match between indexing and querying.
2094
+ *
2095
+ * Annotated as `AnyOrama` (Orama's exported base type) rather than inferred
2096
+ * via `ReturnType<typeof create>` because the inference would pull in
2097
+ * Orama's internal component types (`DocumentsStore`, `Index`, `Sorter`)
2098
+ * which are not exported and break `.d.ts` generation (TS2883).
2099
+ */
2100
+ function createMcpSearchIndex(defaultLocale) {
2101
+ return (0, _orama_orama.create)({
2102
+ schema: mcpSearchSchema,
2103
+ components: { tokenizer: createMcpTokenizer(defaultLocale) }
2104
+ });
2105
+ }
2106
+ /**
2107
+ * Insert search documents into an Orama index.
2108
+ */
2109
+ function indexSearchDocuments(db, documents) {
2110
+ if (documents.length === 0) return;
2111
+ (0, _orama_orama.insertMultiple)(db, documents);
2112
+ }
2113
+ /**
2114
+ * Build a ready-to-search Orama index from content routes.
2115
+ *
2116
+ * Filters out bare aliases and empty routes. Returns the populated db.
2117
+ */
2118
+ function buildSearchIndex(routes, defaultLocale) {
2119
+ const db = createMcpSearchIndex(defaultLocale);
2120
+ const documents = [];
2121
+ for (const route of routes) {
2122
+ const doc = routeToSearchDocument(route, defaultLocale);
2123
+ if (doc) documents.push(doc);
2124
+ }
2125
+ indexSearchDocuments(db, documents);
2126
+ return {
2127
+ db,
2128
+ documentCount: documents.length
2129
+ };
2130
+ }
2131
+ /**
2132
+ * Collect the distinct locale codes that will be present in the Orama index,
2133
+ * in stable insertion order. Bare aliases are skipped (they duplicate the
2134
+ * default-locale route). Locales with no code fall back to the project
2135
+ * default so single-locale sites still surface one locale.
2136
+ */
2137
+ function collectIndexedLocales(routes, defaultLocale) {
2138
+ const seen = /* @__PURE__ */ new Set();
2139
+ const locales = [];
2140
+ for (const route of routes) {
2141
+ if (route.isBareAlias) continue;
2142
+ const code = route.locale ?? defaultLocale ?? "en";
2143
+ if (!seen.has(code)) {
2144
+ seen.add(code);
2145
+ locales.push(code);
2146
+ }
2147
+ }
2148
+ return locales;
2149
+ }
2150
+ /**
2151
+ * Search an Orama MCP index.
2152
+ *
2153
+ * Applies an optional locale filter (via the `enum` field) and returns a
2154
+ * compact, LLM-friendly hit shape. Excerpts are derived from the matched
2155
+ * content: Orama does not return highlighted excerpts, so we synthesize a
2156
+ * short window around the first query term occurrence.
2157
+ */
2158
+ function searchMcpIndex(db, params) {
2159
+ const limit = Math.max(1, Math.min(50, params.limit ?? 10));
2160
+ const result = (0, _orama_orama.search)(db, {
2161
+ term: params.query,
2162
+ limit,
2163
+ where: params.locale ? { locale: { eq: params.locale } } : void 0
2164
+ });
2165
+ return {
2166
+ hits: result.hits.map((hit) => {
2167
+ const doc = hit.document;
2168
+ return {
2169
+ path: doc.path,
2170
+ title: doc.title || guessTitleFromPath(doc.path),
2171
+ description: doc.description,
2172
+ locale: doc.locale,
2173
+ score: hit.score,
2174
+ excerpt: makeExcerpt(doc.content, params.query),
2175
+ keywords: doc.keywords
2176
+ };
2177
+ }),
2178
+ count: result.count
2179
+ };
2180
+ }
2181
+ /**
2182
+ * Build a short excerpt around the first occurrence of any query term in the
2183
+ * content. Falls back to the beginning of the content if no term is found.
2184
+ */
2185
+ function makeExcerpt(content, query, maxLen = 240) {
2186
+ if (!content) return "";
2187
+ const terms = query.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
2188
+ const lower = content.toLowerCase();
2189
+ let pos = -1;
2190
+ for (const term of terms) {
2191
+ const i = lower.indexOf(term);
2192
+ if (i >= 0 && (pos < 0 || i < pos)) pos = i;
2193
+ }
2194
+ if (pos < 0) pos = 0;
2195
+ const half = Math.floor(maxLen / 2);
2196
+ const start = Math.max(0, pos - half);
2197
+ const end = Math.min(content.length, start + maxLen);
2198
+ const prefix = start > 0 ? "…" : "";
2199
+ const suffix = end < content.length ? "…" : "";
2200
+ return `${prefix}${content.slice(start, end).trim()}${suffix}`;
2201
+ }
2202
+ function guessTitleFromPath(path) {
2203
+ if (!path) return "(untitled)";
2204
+ const segments = path.split("/").filter(Boolean);
2205
+ if (segments.length === 0) return "Home";
2206
+ return segments[segments.length - 1].replace(/\.(md|mdx|html)$/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2207
+ }
2208
+ /**
2209
+ * Serialize an Orama index to a msgpack binary buffer.
2210
+ *
2211
+ * Uses `save()` to extract the raw index data, then msgpack-encodes it for a
2212
+ * compact binary representation. The same schema + tokenizer must be supplied
2213
+ * when loading so query-time tokenization matches indexing-time tokenization.
2214
+ */
2215
+ function serializeSearchIndex(db) {
2216
+ return (0, _msgpack_msgpack.encode)((0, _orama_orama.save)(db));
2217
+ }
2218
+ /**
2219
+ * Deserialize a msgpack binary buffer into a ready-to-search Orama index.
2220
+ *
2221
+ * The caller must supply the same `defaultLocale` used when the index was
2222
+ * built, so the tokenizer matches. A mismatch would not corrupt data but
2223
+ * would cause CJK query terms to be tokenized incorrectly (e.g. "文档" left
2224
+ * as a single token instead of split into "文" + "档").
2225
+ */
2226
+ function deserializeSearchIndex(buffer, defaultLocale) {
2227
+ const raw = (0, _msgpack_msgpack.decode)(buffer);
2228
+ const db = createMcpSearchIndex(defaultLocale);
2229
+ (0, _orama_orama.load)(db, raw);
2230
+ return db;
2231
+ }
2232
+ //#endregion
2233
+ //#region source/plugins/mcp-search/index.ts
2234
+ /**
2235
+ * MCP search index plugin.
2236
+ *
2237
+ * Generates the Orama binary index (`mcp-search.msp`) and the MCP site
2238
+ * descriptor (`mcp.json`) as build assets consumed by the `clarify mcp`
2239
+ * command to serve documentation search to MCP-aware clients (e.g. AI coding
2240
+ * assistants).
2241
+ *
2242
+ * This plugin is independent of `clarify:page-search` (Pagefind), which
2243
+ * powers in-browser search. The MCP server runs in Node and needs a
2244
+ * self-contained, compact index with CJK-aware tokenization that doesn't
2245
+ * depend on the browser-only Pagefind runtime.
2246
+ */
2247
+ function createMcpSearchPlugin() {
2248
+ return {
2249
+ name: "clarify:mcp-search",
2250
+ hooks: { "build:assets"(ctx) {
2251
+ const searchConfig = ctx.projectConfig.features.search;
2252
+ if (!searchConfig.enabled || !searchConfig.mcp) return [];
2253
+ const defaultLocale = ctx.projectConfig.locales?.default;
2254
+ const { db, documentCount } = buildSearchIndex(ctx.routes, defaultLocale);
2255
+ const locales = collectIndexedLocales(ctx.routes, defaultLocale);
2256
+ const mcpConfig = createMcpSiteConfig(ctx.projectConfig, {
2257
+ documentCount,
2258
+ locales
2259
+ });
2260
+ const assets = [{
2261
+ fileName: `${mcpConfig.capabilities.search?.indexPath.replace(/\/mcp-search\.msp$/, "") ?? ""}/mcp-search.msp`.replace(/^\//, ""),
2262
+ source: serializeSearchIndex(db)
2263
+ }, {
2264
+ fileName: "mcp.json",
2265
+ source: `${JSON.stringify(mcpConfig, null, 2)}\n`
2266
+ }];
2267
+ console.log(`[clarify] MCP search index (${documentCount} docs, ${locales.length} locales) written to mcp-search.msp.`);
2268
+ return assets;
2269
+ } }
2270
+ };
2271
+ }
2272
+ //#endregion
1888
2273
  //#region source/plugins/openapi/parser.ts
1889
2274
  var OPENAPI_HTTP_METHODS = [
1890
2275
  "get",
@@ -2090,7 +2475,7 @@ function collectOpenAPIPageIntents(config) {
2090
2475
  if (!pages || pages === "FileTree") return;
2091
2476
  for (const group of pages) visitItems(group.pages);
2092
2477
  }
2093
- for (const tab of config.tabs ?? []) visitPages(tab.pages);
2478
+ for (const tab of config.navigation?.tabs ?? []) visitPages(tab.pages);
2094
2479
  return intents;
2095
2480
  }
2096
2481
  function virtualModuleIdFromPath(path) {
@@ -2113,14 +2498,14 @@ function cloneOpenAPIRoute(route, overrides = {}) {
2113
2498
  ...overrides
2114
2499
  };
2115
2500
  }
2116
- function findOpenAPIRoutesForContentRoot(contentRoot, i18n) {
2117
- if (!i18n) return findOpenAPIRoutes(contentRoot);
2501
+ function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
2502
+ if (!locales) return findOpenAPIRoutes(contentRoot);
2118
2503
  const localizedRoutes = [];
2119
- for (const locale of i18n.locales) {
2504
+ for (const locale of locales.locales) {
2120
2505
  const localeRoot = (0, node_path.join)(contentRoot, locale.code);
2121
2506
  for (const route of findOpenAPIRoutes(localeRoot, localeRoot)) {
2122
2507
  const basePath = route.basePath ?? route.path;
2123
- const path = localizedRoutePath(basePath, locale.code, i18n);
2508
+ const path = localizedRoutePath(basePath, locale.code, locales);
2124
2509
  localizedRoutes.push({
2125
2510
  ...route,
2126
2511
  path,
@@ -2130,15 +2515,15 @@ function findOpenAPIRoutesForContentRoot(contentRoot, i18n) {
2130
2515
  });
2131
2516
  }
2132
2517
  }
2133
- if (i18n.missing === "fallback") {
2518
+ if (locales.missing === "fallback") {
2134
2519
  const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
2135
- const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
2520
+ const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
2136
2521
  for (const sourceRoute of defaultRoutes) {
2137
2522
  const basePath = sourceRoute.basePath ?? sourceRoute.path;
2138
- for (const locale of i18n.locales) {
2523
+ for (const locale of locales.locales) {
2139
2524
  const key = `${locale.code}:${basePath}`;
2140
2525
  if (routeByLocaleAndBase.has(key)) continue;
2141
- const path = localizedRoutePath(basePath, locale.code, i18n);
2526
+ const path = localizedRoutePath(basePath, locale.code, locales);
2142
2527
  localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
2143
2528
  path,
2144
2529
  locale: locale.code,
@@ -2165,7 +2550,7 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2165
2550
  const key = `${route.locale ?? ""}:${targetBasePath}`;
2166
2551
  if (existingKeys.has(key)) continue;
2167
2552
  existingKeys.add(key);
2168
- const path = route.locale && config.i18n ? localizedRoutePath(targetBasePath, route.locale, config.i18n) : targetBasePath;
2553
+ const path = route.locale && config.locales ? localizedRoutePath(targetBasePath, route.locale, config.locales) : targetBasePath;
2169
2554
  additions.push(cloneOpenAPIRoute(route, {
2170
2555
  path,
2171
2556
  basePath: targetBasePath,
@@ -2178,12 +2563,12 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2178
2563
  }
2179
2564
  }
2180
2565
  const nextRoutes = [...routes, ...additions];
2181
- if (!config.i18n) return nextRoutes;
2182
- const routesWithAlternates = nextRoutes.map((route) => withAlternates(route, nextRoutes, config.i18n));
2566
+ if (!config.locales) return nextRoutes;
2567
+ const routesWithAlternates = nextRoutes.map((route) => withAlternates(route, nextRoutes, config.locales));
2183
2568
  const bareAliases = [];
2184
2569
  const seenBare = new Set(routesWithAlternates.map((route) => route.path));
2185
2570
  for (const route of routesWithAlternates) {
2186
- if (route.locale !== config.i18n.defaultLocale) continue;
2571
+ if (route.locale !== config.locales.default) continue;
2187
2572
  const basePath = route.basePath ?? route.path;
2188
2573
  if (basePath === route.path || seenBare.has(basePath)) continue;
2189
2574
  seenBare.add(basePath);
@@ -2222,7 +2607,7 @@ function createOpenAPIPlugin() {
2222
2607
  hooks: {
2223
2608
  "routes:discover": (input, ctx) => ({
2224
2609
  ...input,
2225
- routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.i18n)]
2610
+ routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.locales)]
2226
2611
  }),
2227
2612
  "routes:discovered": async (routes, ctx) => {
2228
2613
  specs.clear();
@@ -2307,12 +2692,12 @@ function removeClarifyTempDir(path) {
2307
2692
  });
2308
2693
  }
2309
2694
  //#endregion
2310
- //#region source/plugins/search-index/search-language.ts
2695
+ //#region source/plugins/page-search/search-language.ts
2311
2696
  function toPagefindLanguage(locale) {
2312
2697
  return locale?.trim().replace(/_/g, "-").split("-").filter(Boolean).join("-").toLowerCase() || "en";
2313
2698
  }
2314
2699
  //#endregion
2315
- //#region source/plugins/search-index/index.ts
2700
+ //#region source/plugins/page-search/index.ts
2316
2701
  var pagefindContentTypes = {
2317
2702
  ".css": "text/css; charset=utf-8",
2318
2703
  ".js": "text/javascript; charset=utf-8",
@@ -2352,15 +2737,6 @@ async function generatePagefindIndex(options, pagefind) {
2352
2737
  if (!pagefind) await runtime.close();
2353
2738
  }
2354
2739
  }
2355
- function routeSearchContent(route) {
2356
- return [
2357
- route.meta.title,
2358
- route.meta.description,
2359
- route.meta.keywords?.join(" "),
2360
- route.meta.sections?.map((section) => section.title).join(" "),
2361
- route.source.content
2362
- ].filter(Boolean).join("\n\n");
2363
- }
2364
2740
  async function generateDevSearchIndex(ctx, root, pagefind) {
2365
2741
  const { errors, index } = await pagefind.createIndex({
2366
2742
  includeCharacters: "._-/#:@",
@@ -2379,7 +2755,7 @@ async function generateDevSearchIndex(ctx, root, pagefind) {
2379
2755
  const result = await index.addCustomRecord({
2380
2756
  url: route.path,
2381
2757
  content,
2382
- language: toPagefindLanguage(route.locale ?? ctx.projectConfig.i18n?.defaultLocale),
2758
+ language: toPagefindLanguage(route.locale ?? ctx.projectConfig.locales?.default),
2383
2759
  meta: { title: route.meta.title }
2384
2760
  });
2385
2761
  assertNoPagefindErrors(`addCustomRecord ${route.path}`, result.errors);
@@ -2417,11 +2793,12 @@ function serveDevPagefind(server, getRoot, ctx) {
2417
2793
  (0, node_fs.createReadStream)(filePath).pipe(res);
2418
2794
  });
2419
2795
  }
2420
- function createSearchIndexPlugin() {
2796
+ function createPageSearchPlugin() {
2421
2797
  return {
2422
- name: "clarify:search-index",
2798
+ name: "clarify:page-search",
2423
2799
  hooks: {
2424
2800
  async "dev:configureServer"(server, ctx) {
2801
+ if (!ctx.projectConfig.features.search.enabled) return;
2425
2802
  const pagefind = await import("pagefind");
2426
2803
  let currentRoot;
2427
2804
  const staleRoots = /* @__PURE__ */ new Set();
@@ -2461,10 +2838,11 @@ function createSearchIndexPlugin() {
2461
2838
  });
2462
2839
  },
2463
2840
  async "build:done"(ctx) {
2841
+ if (!ctx.projectConfig.features.search.enabled) return;
2464
2842
  const outputDirectory = ctx.generateOptions.outputDirectory;
2465
2843
  if (!outputDirectory) return;
2466
2844
  const pageCount = await generatePagefindIndex({ outputRoot: (0, node_path.resolve)(ctx.generateOptions.projectRoot, outputDirectory) });
2467
- console.log(`[clarify] Search index generated for ${pageCount} pages.`);
2845
+ console.log(`[clarify] Page search index generated for ${pageCount} pages.`);
2468
2846
  }
2469
2847
  }
2470
2848
  };
@@ -2513,16 +2891,18 @@ function createSeoPlugin() {
2513
2891
  return {
2514
2892
  name: "clarify:seo",
2515
2893
  hooks: { "build:assets"(ctx) {
2894
+ const assets = [];
2516
2895
  const sitemap = createSitemapXml(ctx.routes, ctx.projectConfig);
2517
2896
  const robots = createRobotsTxt(ctx.projectConfig);
2518
- if (!sitemap || !robots) return [];
2519
- return [{
2897
+ if (sitemap) assets.push({
2520
2898
  fileName: "sitemap.xml",
2521
2899
  source: sitemap
2522
- }, {
2900
+ });
2901
+ if (robots) assets.push({
2523
2902
  fileName: "robots.txt",
2524
2903
  source: robots
2525
- }];
2904
+ });
2905
+ return assets;
2526
2906
  } }
2527
2907
  };
2528
2908
  }
@@ -2538,7 +2918,8 @@ function encodePath(path) {
2538
2918
  return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
2539
2919
  }
2540
2920
  function createSourceEditUrl(filePath, contentRoot, source) {
2541
- const repository = normalizeRepositoryUrl(source.repository);
2921
+ if (!source.url) return "";
2922
+ const repository = normalizeRepositoryUrl(source.url);
2542
2923
  const branch = source.branch ?? "main";
2543
2924
  const sourcePath = [source.directory ? trimSlashes(source.directory) : "", (0, node_path.relative)(contentRoot, filePath).replaceAll("\\", "/")].filter(Boolean).join("/");
2544
2925
  return `${repository}/edit/${encodeURIComponent(branch)}/${encodePath(sourcePath)}`;
@@ -2556,7 +2937,8 @@ function createSourceLinksPlugin() {
2556
2937
  return {
2557
2938
  name: "clarify:source-links",
2558
2939
  hooks: { "routes:discovered": (routes, ctx) => {
2559
- attachSourceEditUrls(routes, (0, node_path.join)(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.source);
2940
+ if (!ctx.projectConfig.features.repository.enabled) return routes;
2941
+ attachSourceEditUrls(routes, (0, node_path.join)(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.features.repository);
2560
2942
  return routes;
2561
2943
  } }
2562
2944
  };
@@ -2609,7 +2991,8 @@ function createBuiltinPlugins(options = {}) {
2609
2991
  createSourceLinksPlugin(),
2610
2992
  createContentArtifactsPlugin(),
2611
2993
  createSeoPlugin(),
2612
- createSearchIndexPlugin()
2994
+ createPageSearchPlugin(),
2995
+ createMcpSearchPlugin()
2613
2996
  ];
2614
2997
  if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
2615
2998
  return plugins;
@@ -2671,18 +3054,18 @@ async function resolveProjectContext(options = {}, env = {
2671
3054
  }) {
2672
3055
  const projectRoot = (0, node_path.resolve)(options.projectRoot ?? process.cwd());
2673
3056
  const initialBuildOptions = resolveBuildOptions(options);
2674
- const contentRoot = (0, node_path.join)(projectRoot, initialBuildOptions.rootDirectory);
2675
3057
  const configFilePath = findClarifyConfigFile(projectRoot);
2676
3058
  const config = configFilePath && (0, node_fs.existsSync)(configFilePath) ? await loadClarifyConfig(projectRoot, env) : {};
2677
3059
  const mergedOptions = {
2678
3060
  ...config,
2679
3061
  ...options,
2680
3062
  projectRoot,
2681
- rootDirectory: initialBuildOptions.rootDirectory,
2682
- outputDirectory: initialBuildOptions.outputDirectory
3063
+ rootDirectory: options.rootDirectory ?? initialBuildOptions.rootDirectory,
3064
+ outputDirectory: options.outputDirectory ?? initialBuildOptions.outputDirectory
2683
3065
  };
2684
3066
  const resolvedProjectConfig = resolveProjectConfig(mergedOptions);
2685
3067
  const resolvedBuildOptions = resolveBuildOptions(mergedOptions);
3068
+ const contentRoot = (0, node_path.join)(projectRoot, resolvedBuildOptions.rootDirectory);
2686
3069
  return {
2687
3070
  projectRoot,
2688
3071
  contentRoot,
@@ -2772,9 +3155,9 @@ function canonicalUrl(contextOrConfig, route) {
2772
3155
  /** Inject `lang` (and optional `dir`) onto the `<html>` tag for the route locale. */
2773
3156
  function injectHtmlLocaleAttributes(html, contextOrConfig, route) {
2774
3157
  const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2775
- const localeCode = route?.locale ?? projectConfig.i18n?.defaultLocale;
3158
+ const localeCode = route?.locale ?? projectConfig.locales?.default;
2776
3159
  if (!localeCode) return html;
2777
- const dir = (projectConfig.i18n?.locales.find((locale) => locale.code === localeCode))?.dir;
3160
+ const dir = (projectConfig.locales?.locales.find((locale) => locale.code === localeCode))?.dir;
2778
3161
  return html.replace(/<html\b([^>]*)>/i, (_match, attributes) => {
2779
3162
  let nextAttributes = attributes.replace(/\s+lang=("[^"]*"|'[^']*'|[^\s>]*)/i, "").replace(/\s+dir=("[^"]*"|'[^']*'|[^\s>]*)/i, "");
2780
3163
  nextAttributes = `${nextAttributes} lang="${escapeHtml(localeCode)}"`;
@@ -2952,20 +3335,23 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
2952
3335
  }
2953
3336
  });
2954
3337
  }
2955
- async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath, failOnError = true) {
3338
+ async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath) {
2956
3339
  const { render } = await import((0, node_url.pathToFileURL)(ssrBundlePath).href);
2957
3340
  const template = readIndexHtml(outputDirectory);
2958
3341
  if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
2959
- for (const route of routes) try {
2960
- const finalHtml = injectSSRIntoTemplate(template, await render(route.path), contextOrConfig, route);
2961
- for (const outFile of routeOutputFiles(outputDirectory, route)) {
2962
- (0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
2963
- (0, node_fs.writeFileSync)(outFile, finalHtml, "utf-8");
3342
+ await Promise.all(routes.map(async (route) => {
3343
+ try {
3344
+ const appHtml = await render(route.path);
3345
+ const finalHtml = injectSSRIntoTemplate(template, appHtml, contextOrConfig, route);
3346
+ for (const outFile of routeOutputFiles(outputDirectory, route)) {
3347
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
3348
+ (0, node_fs.writeFileSync)(outFile, finalHtml, "utf-8");
3349
+ }
3350
+ } catch (err) {
3351
+ console.error(`[clarify] Failed to render route "${route.path}":`, err);
3352
+ throw err;
2964
3353
  }
2965
- } catch (err) {
2966
- console.error(`[clarify] Failed to render route "${route.path}":`, err);
2967
- if (failOnError) throw err;
2968
- }
3354
+ }));
2969
3355
  }
2970
3356
  //#endregion
2971
3357
  //#region source/core/runtime/startup.ts
@@ -3041,10 +3427,10 @@ var ClarifyContext = class {
3041
3427
  this.currentPlugins = plugins;
3042
3428
  }
3043
3429
  get isI18n() {
3044
- return Boolean(this.projectConfig.i18n);
3430
+ return Boolean(this.projectConfig.locales);
3045
3431
  }
3046
3432
  get defaultLocale() {
3047
- return this.projectConfig.i18n?.defaultLocale;
3433
+ return this.projectConfig.locales?.default;
3048
3434
  }
3049
3435
  get(key) {
3050
3436
  return this.values.get(key);
@@ -3238,10 +3624,10 @@ var ClarifyEngine = class {
3238
3624
  async discoverSite() {
3239
3625
  const plugins = this.ctx.plugins;
3240
3626
  const contentRoot = this.ctx.contentRoot;
3241
- const { i18n, tabs } = this.ctx.projectConfig;
3627
+ const { locales, navigation: navigationConfig } = this.ctx.projectConfig;
3242
3628
  let routes = await runPhase(plugins, "site:discover", this.ctx, async () => {
3243
3629
  const options = { contentProcessor: createProjectContentProcessor(plugins, this.ctx) };
3244
- const discovered = i18n ? await findLocalizedContentRoutes(contentRoot, i18n, options) : await findContentRoutes(contentRoot, contentRoot, options);
3630
+ const discovered = locales ? await findLocalizedContentRoutes(contentRoot, locales, options) : await findContentRoutes(contentRoot, contentRoot, options);
3245
3631
  const result = await runHooks(plugins, "routes:discover", {
3246
3632
  contentRoot,
3247
3633
  routes: discovered
@@ -3270,8 +3656,9 @@ var ClarifyEngine = class {
3270
3656
  };
3271
3657
  });
3272
3658
  });
3273
- routes = applyConfiguredPageRoutePaths(routes, tabs, i18n);
3274
- const navigation = tabs ? i18n ? buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) ?? {
3659
+ const tabs = navigationConfig?.tabs;
3660
+ routes = applyConfiguredPageRoutePaths(routes, tabs, locales);
3661
+ const navigation = tabs ? locales ? buildLocalizedNavigationFromTabsConfig(routes, tabs, locales) ?? {
3275
3662
  kind: "localized-tabbed",
3276
3663
  locales: {}
3277
3664
  } : buildNavigationFromTabsConfig(routes, tabs) : {
@@ -3301,7 +3688,7 @@ var ClarifyEngine = class {
3301
3688
  routes: this.routes,
3302
3689
  navigation: this.navigation,
3303
3690
  plugins: this.plugins,
3304
- themeEditor: this.runtime.command === "serve" || this.runtime.themeEditor === true || this.projectConfig.theme.editor,
3691
+ themeEditor: this.runtime.command === "serve" || this.runtime.themeEditor === true || this.projectConfig.features.themeEditor.enabled,
3305
3692
  version: this.ctx.version
3306
3693
  });
3307
3694
  this.virtualModules = await runHooks(this.plugins, "modules:before", this.virtualModules, this.ctx);
@@ -3344,7 +3731,7 @@ var ClarifyEngine = class {
3344
3731
  await runPhase(this.plugins, "ssg", this.ctx, async () => {
3345
3732
  if (process.env.SKIP_CLARIFY_SSG) return;
3346
3733
  if (!await runInterceptHooks(this.plugins, "ssg:shouldRun", this.ctx)) return;
3347
- if (this.generateOptions.ssg.failOnError) assertNoContentDiagnostics(this.routes);
3734
+ assertNoContentDiagnostics(this.routes);
3348
3735
  const outputDir = this.runtime.outputDirectory ?? this.generateOptions.outputDirectory;
3349
3736
  if (!outputDir) throw new Error("[clarify] outputDirectory is required before SSG runs");
3350
3737
  const ssrOutputDir = (0, node_path.join)(outputDir, ".ssr");
@@ -3358,10 +3745,10 @@ var ClarifyEngine = class {
3358
3745
  plugins: this.runtime.ssrPlugins ?? []
3359
3746
  });
3360
3747
  const ssrBundlePath = (0, node_path.join)(ssrOutputDir, "entry-server.js");
3361
- await renderSSGRoutes(this.routes, this.runtimeContext(), outputDir, ssrBundlePath, this.generateOptions.ssg.failOnError);
3748
+ await renderSSGRoutes(this.routes, this.runtimeContext(), outputDir, ssrBundlePath);
3362
3749
  } catch (err) {
3363
3750
  console.error("[clarify] SSG failed:", err);
3364
- if (this.generateOptions.ssg.failOnError) throw err;
3751
+ throw err;
3365
3752
  } finally {
3366
3753
  if (tempEntryPath) try {
3367
3754
  (0, node_fs.rmSync)(tempEntryPath, { force: true });
@@ -3451,5 +3838,17 @@ Object.defineProperty(exports, "defineConfig", {
3451
3838
  return defineConfig;
3452
3839
  }
3453
3840
  });
3841
+ Object.defineProperty(exports, "deserializeSearchIndex", {
3842
+ enumerable: true,
3843
+ get: function() {
3844
+ return deserializeSearchIndex;
3845
+ }
3846
+ });
3847
+ Object.defineProperty(exports, "searchMcpIndex", {
3848
+ enumerable: true,
3849
+ get: function() {
3850
+ return searchMcpIndex;
3851
+ }
3852
+ });
3454
3853
 
3455
- //# sourceMappingURL=engine-BDdPszs2.cjs.map
3854
+ //# sourceMappingURL=engine-Cbn_coqg.cjs.map