doccupine 0.0.82 → 0.0.84

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 (42) hide show
  1. package/dist/lib/layout.js +16 -11
  2. package/dist/lib/structures.js +8 -2
  3. package/dist/templates/app/robots.d.ts +1 -0
  4. package/dist/templates/app/robots.js +11 -0
  5. package/dist/templates/app/theme.d.ts +1 -1
  6. package/dist/templates/app/theme.js +1 -1
  7. package/dist/templates/components/Docs.d.ts +1 -1
  8. package/dist/templates/components/Docs.js +1 -1
  9. package/dist/templates/components/DocsSideBar.d.ts +1 -1
  10. package/dist/templates/components/DocsSideBar.js +11 -8
  11. package/dist/templates/components/PostHogProvider.d.ts +1 -1
  12. package/dist/templates/components/PostHogProvider.js +9 -62
  13. package/dist/templates/components/PostHogProviderLazy.d.ts +1 -0
  14. package/dist/templates/components/PostHogProviderLazy.js +70 -0
  15. package/dist/templates/components/SearchDocs.d.ts +1 -1
  16. package/dist/templates/components/SearchDocs.js +40 -270
  17. package/dist/templates/components/SearchModalContent.d.ts +1 -0
  18. package/dist/templates/components/SearchModalContent.js +326 -0
  19. package/dist/templates/components/SideBar.d.ts +1 -1
  20. package/dist/templates/components/SideBar.js +5 -1
  21. package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
  22. package/dist/templates/components/layout/DocsComponents.js +1 -1
  23. package/dist/templates/components/layout/DocsNavigation.d.ts +1 -1
  24. package/dist/templates/components/layout/DocsNavigation.js +1 -1
  25. package/dist/templates/components/layout/Footer.d.ts +1 -1
  26. package/dist/templates/components/layout/Footer.js +8 -3
  27. package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
  28. package/dist/templates/components/layout/SharedStyles.js +2 -1
  29. package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
  30. package/dist/templates/components/layout/StaticLinks.js +3 -3
  31. package/dist/templates/mdx/lists-and-tables.mdx.d.ts +1 -0
  32. package/dist/templates/mdx/lists-and-tables.mdx.js +78 -0
  33. package/dist/templates/mdx/navigation.mdx.d.ts +1 -1
  34. package/dist/templates/mdx/navigation.mdx.js +7 -4
  35. package/dist/templates/mdx/theme.mdx.d.ts +1 -1
  36. package/dist/templates/mdx/theme.mdx.js +1 -1
  37. package/dist/templates/package.js +12 -12
  38. package/dist/templates/services/mcp/tools.d.ts +1 -1
  39. package/dist/templates/services/mcp/tools.js +9 -10
  40. package/dist/templates/tsconfig.d.ts +1 -1
  41. package/dist/templates/tsconfig.js +0 -1
  42. package/package.json +4 -4
@@ -0,0 +1,78 @@
1
+ export const listsAndTablesMdxTemplate = `---
2
+ title: "Lists and tables"
3
+ description: "Present structured information using lists or tables."
4
+ date: "2026-02-19"
5
+ category: "Components"
6
+ categoryOrder: 1
7
+ order: 2
8
+ ---
9
+ # Lists and Tables
10
+ Present structured information using lists or tables.
11
+
12
+ ## Lists
13
+ Markdown supports both *ordered* and *unordered* lists, as well as nested list structures.
14
+
15
+ ### Ordered List
16
+ Start each item with a number followed by a period to create an ordered list.
17
+
18
+ \`\`\`md
19
+ 1. First item
20
+ 2. Second item
21
+ 3. Third item
22
+ 4. Fourth item
23
+ \`\`\`
24
+
25
+ 1. First item
26
+ 2. Second item
27
+ 3. Third item
28
+ 4. Fourth item
29
+
30
+
31
+ ### Unordered List
32
+ Use dashes (\`-\`), asterisks (\`*\`), or plus signs (\`+\`) before each item for unordered lists.
33
+
34
+ \`\`\`md
35
+ - First item
36
+ - Second item
37
+ - Third item
38
+ - Fourth item
39
+ \`\`\`
40
+
41
+ - First item
42
+ - Second item
43
+ - Third item
44
+ - Fourth item
45
+
46
+ ### Nested List
47
+ Indent items under another to create nested lists.
48
+
49
+ \`\`\`md
50
+ - First item
51
+ - Second item
52
+ - Additional item
53
+ - Additional item
54
+ - Third item
55
+ \`\`\`
56
+
57
+ - First item
58
+ - Second item
59
+ - Additional item
60
+ - Additional item
61
+ - Third item
62
+
63
+ ## Tables
64
+ Markdown tables use pipes (\`|\`) to separate columns and hyphens (\`---\`) to define the header row. Place a pipe at the start and end of each row for better compatibility.
65
+
66
+ \`\`\`md
67
+ | Property | Description |
68
+ | -------- | -------------------------------------- |
69
+ | Name | Full name of the user |
70
+ | Age | Age in years |
71
+ | Joined | Indicates if user joined the community |
72
+ \`\`\`
73
+
74
+ | Property | Description |
75
+ | -------- | -------------------------------------- |
76
+ | Name | Full name of the user |
77
+ | Age | Age in years |
78
+ | Joined | Indicates if user joined the community |`;
@@ -1 +1 @@
1
- export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n# Navigation\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"deployment\", \"title\": \"Deployment\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n- **label**: The section header shown in the sidebar.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
1
+ export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n# Navigation\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"components\", \"title\": \"Components\" },\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" },\n { \"slug\": \"color-swatches\", \"title\": \"Color Swatches\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"analytics\", \"title\": \"Analytics\" },\n { \"slug\": \"deployment-and-hosting\", \"title\": \"Deployment & Hosting\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n- **label**: The section header shown in the sidebar.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
@@ -41,15 +41,16 @@ The simplest format is an array of categories. When using [sections](/sections),
41
41
  \`\`\`json
42
42
  [
43
43
  {
44
- "label": "General",
44
+ "label": "Getting Started",
45
45
  "links": [
46
- { "slug": "", "title": "Getting Started" },
46
+ { "slug": "", "title": "Introduction" },
47
47
  { "slug": "commands", "title": "Commands" }
48
48
  ]
49
49
  },
50
50
  {
51
51
  "label": "Components",
52
52
  "links": [
53
+ { "slug": "components", "title": "Components" },
53
54
  { "slug": "headers-and-text", "title": "Headers and Text" },
54
55
  { "slug": "lists-and-tables", "title": "Lists and tables" },
55
56
  { "slug": "code", "title": "Code" },
@@ -63,7 +64,8 @@ The simplest format is an array of categories. When using [sections](/sections),
63
64
  { "slug": "fields", "title": "Fields" },
64
65
  { "slug": "update", "title": "Update" },
65
66
  { "slug": "columns", "title": "Columns" },
66
- { "slug": "steps", "title": "Steps" }
67
+ { "slug": "steps", "title": "Steps" },
68
+ { "slug": "color-swatches", "title": "Color Swatches" }
67
69
  ]
68
70
  },
69
71
  {
@@ -78,7 +80,8 @@ The simplest format is an array of categories. When using [sections](/sections),
78
80
  { "slug": "fonts", "title": "Fonts" },
79
81
  { "slug": "ai-assistant", "title": "AI Assistant" },
80
82
  { "slug": "model-context-protocol", "title": "Model Context Protocol" },
81
- { "slug": "deployment", "title": "Deployment" }
83
+ { "slug": "analytics", "title": "Analytics" },
84
+ { "slug": "deployment-and-hosting", "title": "Deployment & Hosting" }
82
85
  ]
83
86
  }
84
87
  ]
@@ -1 +1 @@
1
- export declare const themeMdxTemplate = "---\ntitle: \"Theme\"\ndescription: \"Customize the documentation UI colors with a theme.json file.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 5\n---\n# Theme\nDefine your site\u2019s color system with a `theme.json` file. This lets you tailor the look and feel of your documentation without changing content.\n\n## theme.json\nPlace a `theme.json` at your project root (the same folder where you execute `npx doccupine`). It supports multiple modes. Define a `default` mode and a `dark` mode.\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#93c5fd\",\n \"primary\": \"#3b82f6\",\n \"primaryDark\": \"#1e40af\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#9bcaff\",\n \"primary\": \"#1e7ae0\",\n \"primaryDark\": \"#033d7e\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n },\n \"logo\": {\n \"dark\": \"https://docs.doccupine.com/logo-dark.svg\",\n \"light\": \"https://docs.doccupine.com/logo-light.svg\"\n }\n}\n```\n\n## Modes\n- **default**: The base color palette for your site.\n- **dark**: Dark\u2011mode palette.\n\n## Fields\n- **primaryLight**: A lighter variant of your brand color, used for subtle accents and backgrounds.\n- **primary**: The main brand color.\n- **primaryDark**: A darker variant of your brand color for emphasis and hover states.\n- **secondaryLight**: A lighter variant of your secondary color, used for subtle accents and backgrounds.\n- **secondary**: The secondary brand color used for highlights and UI accents.\n- **secondaryDark**: A darker variant of your secondary color for emphasis and hover states.\n- **tertiaryLight**: A lighter variant of your tertiary color, used for subtle accents and backgrounds.\n- **tertiary**: The tertiary accent color.\n- **tertiaryDark**: A darker variant of your tertiary color for emphasis and hover states.\n- **grayLight**: Light gray for surfaces and borders.\n- **gray**: Neutral gray for text and UI elements.\n- **grayDark**: Dark gray for headings or high\u2011contrast text.\n- **success**: Positive/confirmation color.\n- **error**: Error/destructive color.\n- **warning**: Warning/attention color.\n- **info**: Informational/highlight color.\n- **dark**: The darkest/base color (often page background in dark mode).\n- **light**: The lightest/base color (often page background in light mode).\n- **logo.light**: Path or URL to the logo used on light backgrounds. Recommended size: 164\u00D730 px.\n- **logo.dark**: Path or URL to the logo used on dark backgrounds. Recommended size: 164\u00D730 px.\n\n## Behavior\n- **Placement**: Put `theme.json` in the project root alongside `config.json`.\n- **Partial palettes**: If a key is missing in a mode, consumers may fall back to the `default` value.\n- **Logo size**: Recommended dimensions are 164px width and 30px height.\n\n<Callout type=\"warning\">\n Use valid hex colors (e.g., `#22c55e`). Invalid color values may cause unexpected rendering.\n</Callout>\n\n## Tips\n- **Contrast**: Ensure sufficient contrast between text and backgrounds for readability.\n- **Branding**: Start with your brand\u2019s `primary` color, then derive `primaryLight` and `primaryDark`.\n- **Iterate**: Adjust colors and refresh the site to preview changes quickly.\n\n# Demo\nIn the following demos, you can see how the theme can be changed. To override the theme, create a `theme.json` file in the project root and copy paste the code below.\n\n<DemoTheme variant=\"purple\" />\n<DemoTheme variant=\"green\" />\n<DemoTheme variant=\"yellow\" />\n<DemoTheme />\n\n## Purple\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#c4b5fd\",\n \"primary\": \"#8b5cf6\",\n \"primaryDark\": \"#5b21b6\",\n \"secondaryLight\": \"#86efac\",\n \"secondary\": \"#22c55e\",\n \"secondaryDark\": \"#15803d\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#ddd6fe\",\n \"primary\": \"#a78bfa\",\n \"primaryDark\": \"#7c3aed\",\n \"secondaryLight\": \"#6ee7b7\",\n \"secondary\": \"#10b981\",\n \"secondaryDark\": \"#065f46\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n<DemoTheme variant=\"purple\" />\n\n## Green\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#86efac\",\n \"primary\": \"#22c55e\",\n \"primaryDark\": \"#15803d\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#6ee7b7\",\n \"primary\": \"#10b981\",\n \"primaryDark\": \"#065f46\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n<DemoTheme variant=\"green\" />\n\n## Yellow\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#fbbf24\",\n \"primary\": \"#f59e0b\",\n \"primaryDark\": \"#d97706\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#86efac\",\n \"tertiary\": \"#22c55e\",\n \"tertiaryDark\": \"#15803d\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#fed7aa\",\n \"primary\": \"#fb923c\",\n \"primaryDark\": \"#ea580c\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#6ee7b7\",\n \"tertiary\": \"#10b981\",\n \"tertiaryDark\": \"#065f46\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"yellow\" />";
1
+ export declare const themeMdxTemplate = "---\ntitle: \"Theme\"\ndescription: \"Customize the documentation UI colors with a theme.json file.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 5\n---\n# Theme\nDefine your site\u2019s color system with a `theme.json` file. This lets you tailor the look and feel of your documentation without changing content.\n\n## theme.json\nPlace a `theme.json` at your project root (the same folder where you execute `npx doccupine`). It supports multiple modes. Define a `default` mode and a `dark` mode.\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#93c5fd\",\n \"primary\": \"#2563eb\",\n \"primaryDark\": \"#1e40af\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#9bcaff\",\n \"primary\": \"#1e7ae0\",\n \"primaryDark\": \"#033d7e\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n },\n \"logo\": {\n \"dark\": \"https://docs.doccupine.com/logo-dark.svg\",\n \"light\": \"https://docs.doccupine.com/logo-light.svg\"\n }\n}\n```\n\n## Modes\n- **default**: The base color palette for your site.\n- **dark**: Dark\u2011mode palette.\n\n## Fields\n- **primaryLight**: A lighter variant of your brand color, used for subtle accents and backgrounds.\n- **primary**: The main brand color.\n- **primaryDark**: A darker variant of your brand color for emphasis and hover states.\n- **secondaryLight**: A lighter variant of your secondary color, used for subtle accents and backgrounds.\n- **secondary**: The secondary brand color used for highlights and UI accents.\n- **secondaryDark**: A darker variant of your secondary color for emphasis and hover states.\n- **tertiaryLight**: A lighter variant of your tertiary color, used for subtle accents and backgrounds.\n- **tertiary**: The tertiary accent color.\n- **tertiaryDark**: A darker variant of your tertiary color for emphasis and hover states.\n- **grayLight**: Light gray for surfaces and borders.\n- **gray**: Neutral gray for text and UI elements.\n- **grayDark**: Dark gray for headings or high\u2011contrast text.\n- **success**: Positive/confirmation color.\n- **error**: Error/destructive color.\n- **warning**: Warning/attention color.\n- **info**: Informational/highlight color.\n- **dark**: The darkest/base color (often page background in dark mode).\n- **light**: The lightest/base color (often page background in light mode).\n- **logo.light**: Path or URL to the logo used on light backgrounds. Recommended size: 164\u00D730 px.\n- **logo.dark**: Path or URL to the logo used on dark backgrounds. Recommended size: 164\u00D730 px.\n\n## Behavior\n- **Placement**: Put `theme.json` in the project root alongside `config.json`.\n- **Partial palettes**: If a key is missing in a mode, consumers may fall back to the `default` value.\n- **Logo size**: Recommended dimensions are 164px width and 30px height.\n\n<Callout type=\"warning\">\n Use valid hex colors (e.g., `#22c55e`). Invalid color values may cause unexpected rendering.\n</Callout>\n\n## Tips\n- **Contrast**: Ensure sufficient contrast between text and backgrounds for readability.\n- **Branding**: Start with your brand\u2019s `primary` color, then derive `primaryLight` and `primaryDark`.\n- **Iterate**: Adjust colors and refresh the site to preview changes quickly.\n\n# Demo\nIn the following demos, you can see how the theme can be changed. To override the theme, create a `theme.json` file in the project root and copy paste the code below.\n\n<DemoTheme variant=\"purple\" />\n<DemoTheme variant=\"green\" />\n<DemoTheme variant=\"yellow\" />\n<DemoTheme />\n\n## Purple\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#c4b5fd\",\n \"primary\": \"#8b5cf6\",\n \"primaryDark\": \"#5b21b6\",\n \"secondaryLight\": \"#86efac\",\n \"secondary\": \"#22c55e\",\n \"secondaryDark\": \"#15803d\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#ddd6fe\",\n \"primary\": \"#a78bfa\",\n \"primaryDark\": \"#7c3aed\",\n \"secondaryLight\": \"#6ee7b7\",\n \"secondary\": \"#10b981\",\n \"secondaryDark\": \"#065f46\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n<DemoTheme variant=\"purple\" />\n\n## Green\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#86efac\",\n \"primary\": \"#22c55e\",\n \"primaryDark\": \"#15803d\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#6ee7b7\",\n \"primary\": \"#10b981\",\n \"primaryDark\": \"#065f46\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n<DemoTheme variant=\"green\" />\n\n## Yellow\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#fbbf24\",\n \"primary\": \"#f59e0b\",\n \"primaryDark\": \"#d97706\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#86efac\",\n \"tertiary\": \"#22c55e\",\n \"tertiaryDark\": \"#15803d\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#fed7aa\",\n \"primary\": \"#fb923c\",\n \"primaryDark\": \"#ea580c\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#6ee7b7\",\n \"tertiary\": \"#10b981\",\n \"tertiaryDark\": \"#065f46\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"yellow\" />";
@@ -16,7 +16,7 @@ Place a \`theme.json\` at your project root (the same folder where you execute \
16
16
  {
17
17
  "default": {
18
18
  "primaryLight": "#93c5fd",
19
- "primary": "#3b82f6",
19
+ "primary": "#2563eb",
20
20
  "primaryDark": "#1e40af",
21
21
  "secondaryLight": "#c4b5fd",
22
22
  "secondary": "#8b5cf6",
@@ -11,21 +11,21 @@ export const packageJsonTemplate = JSON.stringify({
11
11
  },
12
12
  dependencies: {
13
13
  "@langchain/anthropic": "^1.3.25",
14
- "@langchain/core": "^1.1.34",
14
+ "@langchain/core": "^1.1.36",
15
15
  "@langchain/google-genai": "^2.1.26",
16
- "@langchain/openai": "^1.3.0",
16
+ "@langchain/openai": "^1.3.1",
17
17
  "@mdx-js/react": "^3.1.1",
18
- "@modelcontextprotocol/sdk": "^1.27.1",
18
+ "@modelcontextprotocol/sdk": "^1.28.0",
19
19
  "@posthog/react": "^1.8.2",
20
- "cherry-styled-components": "^0.1.13",
21
- langchain: "^1.2.35",
22
- "lucide-react": "^0.577.0",
20
+ "cherry-styled-components": "^0.1.16",
21
+ langchain: "^1.2.37",
22
+ "lucide-react": "^1.7.0",
23
23
  minisearch: "^7.2.0",
24
- next: "16.2.0",
24
+ next: "16.2.1",
25
25
  "next-mdx-remote": "^6.0.0",
26
26
  polished: "^4.3.1",
27
- "posthog-js": "^1.362.0",
28
- "posthog-node": "^5.28.4",
27
+ "posthog-js": "^1.364.2",
28
+ "posthog-node": "^5.28.9",
29
29
  react: "19.2.4",
30
30
  "react-dom": "19.2.4",
31
31
  "rehype-highlight": "^7.0.2",
@@ -40,10 +40,10 @@ export const packageJsonTemplate = JSON.stringify({
40
40
  "@types/node": "^25",
41
41
  "@types/react": "^19",
42
42
  "@types/react-dom": "^19",
43
- "baseline-browser-mapping": "^2.10.9",
43
+ "baseline-browser-mapping": "^2.10.12",
44
44
  eslint: "^9",
45
- "eslint-config-next": "16.2.0",
45
+ "eslint-config-next": "16.2.1",
46
46
  prettier: "^3.8.1",
47
- typescript: "^5",
47
+ typescript: "^6",
48
48
  },
49
49
  }, null, 2) + "\n";
@@ -1 +1 @@
1
- export declare const mcpToolsTemplate = "import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport type {\n MCPToolDefinition,\n DocsResource,\n DocsChunk,\n GetDocParams,\n ListDocsParams,\n} from \"@/services/mcp/types\";\n\nconst PROJECT_ROOT = process.cwd();\nconst APP_DIR = path.join(PROJECT_ROOT, \"app\");\nconst VALID_EXT = new Set([\".ts\", \".tsx\", \".js\", \".jsx\"]);\n\n/**\n * Tool definitions for MCP - these describe the available tools\n */\nexport const DOCS_TOOLS: MCPToolDefinition[] = [\n {\n name: \"search_docs\",\n description:\n \"Search through the documentation content using semantic search. Returns relevant chunks of documentation based on the query.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: {\n type: \"string\",\n description: \"The search query to find relevant documentation\",\n },\n limit: {\n type: \"number\",\n description: \"Maximum number of results to return (default: 6)\",\n },\n },\n required: [\"query\"],\n },\n },\n {\n name: \"get_doc\",\n description:\n \"Get the full content of a specific documentation page by its path.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description:\n \"The file path to the documentation page (e.g., 'app/getting-started/page.tsx')\",\n },\n },\n required: [\"path\"],\n },\n },\n {\n name: \"list_docs\",\n description:\n \"List all available documentation pages, optionally filtered by directory.\",\n inputSchema: {\n type: \"object\",\n properties: {\n directory: {\n type: \"string\",\n description:\n \"Optional directory to filter results (e.g., 'components')\",\n },\n },\n },\n },\n];\n\n/**\n * Recursively walk directory to find documentation files\n */\nasync function* walkDocs(dir: string): AsyncGenerator<string> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if ([\"node_modules\", \".next\", \".git\", \"api\"].includes(entry.name)) {\n continue;\n }\n yield* walkDocs(fullPath);\n } else {\n const ext = path.extname(entry.name).toLowerCase();\n if (VALID_EXT.has(ext) && entry.name.startsWith(\"page.\")) {\n yield fullPath;\n }\n }\n }\n}\n\n/**\n * Extract content blocks from a file\n */\nfunction extractContentBlocks(fileText: string): string[] {\n const results: string[] = [];\n\n const tplRegex = /(?:export\\s+)?const\\s+content\\s*=\\s*`((?:\\\\`|[^`])*)`\\s*;/g;\n let m: RegExpExecArray | null;\n while ((m = tplRegex.exec(fileText)) !== null) {\n results.push(m[1]);\n }\n\n const sglRegex = /(?:export\\s+)?const\\s+content\\s*=\\s*'([^']*)'\\s*;/g;\n while ((m = sglRegex.exec(fileText)) !== null) {\n results.push(m[1]);\n }\n\n const dblRegex = /(?:export\\s+)?const\\s+content\\s*=\\s*\"([^\"]*)\"\\s*;/g;\n while ((m = dblRegex.exec(fileText)) !== null) {\n results.push(m[1]);\n }\n\n return results;\n}\n\n/**\n * Get the title from markdown content\n */\nfunction extractTitle(content: string): string {\n const match = content.match(/^#\\s+(.+)$/m);\n return match ? match[1].trim() : \"Untitled\";\n}\n\n/**\n * List all documentation resources\n */\nexport async function listDocs(\n params?: ListDocsParams,\n): Promise<DocsResource[]> {\n const resources: DocsResource[] = [];\n const filterDir = params?.directory;\n\n for await (const filePath of walkDocs(APP_DIR)) {\n const relativePath = path.relative(PROJECT_ROOT, filePath);\n\n if (filterDir && !relativePath.includes(filterDir)) {\n continue;\n }\n\n try {\n const fileContent = await fs.readFile(filePath, \"utf8\");\n const blocks = extractContentBlocks(fileContent);\n const content = blocks.join(\"\\n\\n\");\n const title = extractTitle(content);\n const docPath = path.dirname(relativePath).replace(/^app\\/?/, \"\") || \"/\";\n\n resources.push({\n uri: `docs://${docPath}`,\n name: title,\n path: relativePath,\n content,\n });\n } catch (error) {\n console.warn(`Failed to read doc file: ${filePath}`, error);\n }\n }\n\n return resources;\n}\n\n/**\n * Get a specific documentation page\n */\nexport async function getDoc(\n params: GetDocParams,\n): Promise<DocsResource | null> {\n let targetPath = params.path;\n\n // Normalize path\n if (!targetPath.startsWith(\"app/\")) {\n targetPath = `app/${targetPath}`;\n }\n if (!targetPath.includes(\"page.\")) {\n targetPath = path.join(targetPath, \"page.tsx\");\n }\n\n const fullPath = path.join(PROJECT_ROOT, targetPath);\n\n // Prevent path traversal\n const resolvedPath = path.resolve(fullPath);\n if (!resolvedPath.startsWith(path.resolve(APP_DIR))) {\n return null;\n }\n\n try {\n const fileContent = await fs.readFile(fullPath, \"utf8\");\n const blocks = extractContentBlocks(fileContent);\n const content = blocks.join(\"\\n\\n\");\n const title = extractTitle(content);\n const docPath = path.dirname(targetPath).replace(/^app\\/?/, \"\") || \"/\";\n\n return {\n uri: `docs://${docPath}`,\n name: title,\n path: targetPath,\n content,\n };\n } catch (error) {\n console.warn(`Failed to read doc: ${targetPath}`, error);\n return null;\n }\n}\n\n/**\n * Chunk text for embeddings.\n * - chunkSize=800 chars balances granularity with embedding context window limits\n * - overlap=100 chars ensures continuity so searches don't miss content at chunk boundaries\n */\nfunction chunkText(text: string, chunkSize = 800, overlap = 100): string[] {\n const chunks: string[] = [];\n let i = 0;\n while (i < text.length) {\n const end = Math.min(i + chunkSize, text.length);\n chunks.push(text.slice(i, end));\n if (end === text.length) break;\n i = end - overlap;\n if (i < 0) i = 0;\n }\n return chunks;\n}\n\n/**\n * Get all documentation chunks for indexing\n */\nexport async function getAllDocsChunks(): Promise<DocsChunk[]> {\n const allChunks: DocsChunk[] = [];\n const docs = await listDocs();\n\n for (const doc of docs) {\n const cleanContent = doc.content\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .slice(0, 200_000);\n\n const textChunks = chunkText(cleanContent);\n for (let i = 0; i < textChunks.length; i++) {\n allChunks.push({\n id: `${doc.path}:${i}`,\n text: textChunks[i],\n path: doc.path,\n uri: doc.uri,\n });\n }\n }\n\n return allChunks;\n}\n";
1
+ export declare const mcpToolsTemplate = "import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport type {\n MCPToolDefinition,\n DocsResource,\n DocsChunk,\n GetDocParams,\n ListDocsParams,\n} from \"@/services/mcp/types\";\n\nconst APP_DIR = path.join(process.cwd(), \"app\");\nconst VALID_EXT = new Set([\".ts\", \".tsx\", \".js\", \".jsx\"]);\n\n/**\n * Tool definitions for MCP - these describe the available tools\n */\nexport const DOCS_TOOLS: MCPToolDefinition[] = [\n {\n name: \"search_docs\",\n description:\n \"Search through the documentation content using semantic search. Returns relevant chunks of documentation based on the query.\",\n inputSchema: {\n type: \"object\",\n properties: {\n query: {\n type: \"string\",\n description: \"The search query to find relevant documentation\",\n },\n limit: {\n type: \"number\",\n description: \"Maximum number of results to return (default: 6)\",\n },\n },\n required: [\"query\"],\n },\n },\n {\n name: \"get_doc\",\n description:\n \"Get the full content of a specific documentation page by its path.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description:\n \"The file path to the documentation page (e.g., 'app/getting-started/page.tsx')\",\n },\n },\n required: [\"path\"],\n },\n },\n {\n name: \"list_docs\",\n description:\n \"List all available documentation pages, optionally filtered by directory.\",\n inputSchema: {\n type: \"object\",\n properties: {\n directory: {\n type: \"string\",\n description:\n \"Optional directory to filter results (e.g., 'components')\",\n },\n },\n },\n },\n];\n\n/**\n * Recursively walk directory to find documentation files\n */\nasync function* walkDocs(dir: string): AsyncGenerator<string> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if ([\"node_modules\", \".next\", \".git\", \"api\"].includes(entry.name)) {\n continue;\n }\n yield* walkDocs(fullPath);\n } else {\n const ext = path.extname(entry.name).toLowerCase();\n if (VALID_EXT.has(ext) && entry.name.startsWith(\"page.\")) {\n yield fullPath;\n }\n }\n }\n}\n\n/**\n * Extract content blocks from a file\n */\nfunction extractContentBlocks(fileText: string): string[] {\n const results: string[] = [];\n\n const tplRegex = /(?:export\\s+)?const\\s+content\\s*=\\s*`((?:\\\\`|[^`])*)`\\s*;/g;\n let m: RegExpExecArray | null;\n while ((m = tplRegex.exec(fileText)) !== null) {\n results.push(m[1]);\n }\n\n const sglRegex = /(?:export\\s+)?const\\s+content\\s*=\\s*'([^']*)'\\s*;/g;\n while ((m = sglRegex.exec(fileText)) !== null) {\n results.push(m[1]);\n }\n\n const dblRegex = /(?:export\\s+)?const\\s+content\\s*=\\s*\"([^\"]*)\"\\s*;/g;\n while ((m = dblRegex.exec(fileText)) !== null) {\n results.push(m[1]);\n }\n\n return results;\n}\n\n/**\n * Get the title from markdown content\n */\nfunction extractTitle(content: string): string {\n const match = content.match(/^#\\s+(.+)$/m);\n return match ? match[1].trim() : \"Untitled\";\n}\n\n/**\n * List all documentation resources\n */\nexport async function listDocs(\n params?: ListDocsParams,\n): Promise<DocsResource[]> {\n const resources: DocsResource[] = [];\n const filterDir = params?.directory;\n\n for await (const filePath of walkDocs(APP_DIR)) {\n const relativePath = path.join(\"app\", path.relative(APP_DIR, filePath));\n\n if (filterDir && !relativePath.includes(filterDir)) {\n continue;\n }\n\n try {\n const fileContent = await fs.readFile(filePath, \"utf8\");\n const blocks = extractContentBlocks(fileContent);\n const content = blocks.join(\"\\n\\n\");\n const title = extractTitle(content);\n const docPath = path.dirname(relativePath).replace(/^app\\/?/, \"\") || \"/\";\n\n resources.push({\n uri: `docs://${docPath}`,\n name: title,\n path: relativePath,\n content,\n });\n } catch (error) {\n console.warn(`Failed to read doc file: ${filePath}`, error);\n }\n }\n\n return resources;\n}\n\n/**\n * Get a specific documentation page\n */\nexport async function getDoc(\n params: GetDocParams,\n): Promise<DocsResource | null> {\n let targetPath = params.path;\n\n // Normalize path - strip leading \"app/\" if present to get the relative part\n const relativePart = targetPath.replace(/^app\\//, \"\");\n if (!relativePart.includes(\"page.\")) {\n targetPath = path.join(\"app\", relativePart, \"page.tsx\");\n } else if (!targetPath.startsWith(\"app/\")) {\n targetPath = path.join(\"app\", relativePart);\n }\n\n const fullPath = path.join(APP_DIR, targetPath.replace(/^app\\//, \"\"));\n\n // Prevent path traversal\n const resolvedPath = path.resolve(fullPath);\n if (!resolvedPath.startsWith(path.resolve(APP_DIR))) {\n return null;\n }\n\n try {\n const fileContent = await fs.readFile(fullPath, \"utf8\");\n const blocks = extractContentBlocks(fileContent);\n const content = blocks.join(\"\\n\\n\");\n const title = extractTitle(content);\n const docPath = path.dirname(targetPath).replace(/^app\\/?/, \"\") || \"/\";\n\n return {\n uri: `docs://${docPath}`,\n name: title,\n path: targetPath,\n content,\n };\n } catch (error) {\n console.warn(`Failed to read doc: ${targetPath}`, error);\n return null;\n }\n}\n\n/**\n * Chunk text for embeddings.\n * - chunkSize=800 chars balances granularity with embedding context window limits\n * - overlap=100 chars ensures continuity so searches don't miss content at chunk boundaries\n */\nfunction chunkText(text: string, chunkSize = 800, overlap = 100): string[] {\n const chunks: string[] = [];\n let i = 0;\n while (i < text.length) {\n const end = Math.min(i + chunkSize, text.length);\n chunks.push(text.slice(i, end));\n if (end === text.length) break;\n i = end - overlap;\n if (i < 0) i = 0;\n }\n return chunks;\n}\n\n/**\n * Get all documentation chunks for indexing\n */\nexport async function getAllDocsChunks(): Promise<DocsChunk[]> {\n const allChunks: DocsChunk[] = [];\n const docs = await listDocs();\n\n for (const doc of docs) {\n const cleanContent = doc.content\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .slice(0, 200_000);\n\n const textChunks = chunkText(cleanContent);\n for (let i = 0; i < textChunks.length; i++) {\n allChunks.push({\n id: `${doc.path}:${i}`,\n text: textChunks[i],\n path: doc.path,\n uri: doc.uri,\n });\n }\n }\n\n return allChunks;\n}\n";
@@ -8,8 +8,7 @@ import type {
8
8
  ListDocsParams,
9
9
  } from "@/services/mcp/types";
10
10
 
11
- const PROJECT_ROOT = process.cwd();
12
- const APP_DIR = path.join(PROJECT_ROOT, "app");
11
+ const APP_DIR = path.join(process.cwd(), "app");
13
12
  const VALID_EXT = new Set([".ts", ".tsx", ".js", ".jsx"]);
14
13
 
15
14
  /**
@@ -132,7 +131,7 @@ export async function listDocs(
132
131
  const filterDir = params?.directory;
133
132
 
134
133
  for await (const filePath of walkDocs(APP_DIR)) {
135
- const relativePath = path.relative(PROJECT_ROOT, filePath);
134
+ const relativePath = path.join("app", path.relative(APP_DIR, filePath));
136
135
 
137
136
  if (filterDir && !relativePath.includes(filterDir)) {
138
137
  continue;
@@ -167,15 +166,15 @@ export async function getDoc(
167
166
  ): Promise<DocsResource | null> {
168
167
  let targetPath = params.path;
169
168
 
170
- // Normalize path
171
- if (!targetPath.startsWith("app/")) {
172
- targetPath = \`app/\${targetPath}\`;
173
- }
174
- if (!targetPath.includes("page.")) {
175
- targetPath = path.join(targetPath, "page.tsx");
169
+ // Normalize path - strip leading "app/" if present to get the relative part
170
+ const relativePart = targetPath.replace(/^app\\//, "");
171
+ if (!relativePart.includes("page.")) {
172
+ targetPath = path.join("app", relativePart, "page.tsx");
173
+ } else if (!targetPath.startsWith("app/")) {
174
+ targetPath = path.join("app", relativePart);
176
175
  }
177
176
 
178
- const fullPath = path.join(PROJECT_ROOT, targetPath);
177
+ const fullPath = path.join(APP_DIR, targetPath.replace(/^app\\//, ""));
179
178
 
180
179
  // Prevent path traversal
181
180
  const resolvedPath = path.resolve(fullPath);
@@ -1 +1 @@
1
- export declare const tsconfigTemplate = "{\n \"compilerOptions\": {\n \"target\": \"es2020\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\n \"next-env.d.ts\",\n \"**/*.ts\",\n \"**/*.tsx\",\n \"**/*.d.ts\",\n \".next/types/**/*.ts\",\n \".next/dev/types/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\"]\n}\n";
1
+ export declare const tsconfigTemplate = "{\n \"compilerOptions\": {\n \"target\": \"es2020\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\n \"next-env.d.ts\",\n \"**/*.ts\",\n \"**/*.tsx\",\n \"**/*.d.ts\",\n \".next/types/**/*.ts\",\n \".next/dev/types/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\"]\n}\n";
@@ -14,7 +14,6 @@ export const tsconfigTemplate = `{
14
14
  "jsx": "react-jsx",
15
15
  "incremental": true,
16
16
  "plugins": [{ "name": "next" }],
17
- "baseUrl": ".",
18
17
  "paths": {
19
18
  "@/*": ["./*"]
20
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.82",
3
+ "version": "0.0.84",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "commander": "^14.0.3",
40
40
  "fs-extra": "^11.3.4",
41
41
  "gray-matter": "^4.0.3",
42
- "next": "^16.2.0",
42
+ "next": "^16.2.1",
43
43
  "prompts": "^2.4.2",
44
44
  "react": "^19.2.4",
45
45
  "react-dom": "^19.2.4"
@@ -50,8 +50,8 @@
50
50
  "@types/node": "^25.5.0",
51
51
  "@types/prompts": "^2.4.9",
52
52
  "prettier": "^3.8.1",
53
- "typescript": "^5.9.3",
54
- "vitest": "^4.1.0"
53
+ "typescript": "^6.0.2",
54
+ "vitest": "^4.1.2"
55
55
  },
56
56
  "files": [
57
57
  "dist/**/*"