doccupine 0.0.60 → 0.0.61
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.
package/README.md
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
- **Live preview** - watches your MDX files and regenerates pages on every save
|
|
10
10
|
- **Auto-generated navigation** - sidebar built from frontmatter (`category`, `order`)
|
|
11
|
+
- **Sections** - organize docs into tabbed sections via frontmatter or `sections.json`
|
|
11
12
|
- **Theming** - dark/light mode with customizable theme via `theme.json`
|
|
12
13
|
- **AI chat assistant** - built-in RAG-powered chat (OpenAI, Anthropic, or Google)
|
|
13
14
|
- **MCP server** - exposes `search_docs`, `get_doc`, and `list_docs` tools for AI agents
|
|
14
15
|
- **Custom fonts** - Google Fonts or local fonts via `fonts.json`
|
|
16
|
+
- **Static assets** - `public/` directory is watched and synced to the generated app
|
|
15
17
|
- **Zero config to start** - `npx doccupine` scaffolds everything and starts the server
|
|
16
18
|
|
|
17
19
|
## Quick Start
|
|
@@ -34,8 +36,8 @@ It then scaffolds the app, installs dependencies, and starts the dev server. Ope
|
|
|
34
36
|
```bash
|
|
35
37
|
doccupine watch [options] # Default. Watch MDX files and start dev server
|
|
36
38
|
doccupine build [options] # One-time build without starting the server
|
|
37
|
-
doccupine config
|
|
38
|
-
doccupine config
|
|
39
|
+
doccupine config -show # Show current configuration
|
|
40
|
+
doccupine config -reset # Re-prompt for configuration
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
### Options
|
|
@@ -59,11 +61,66 @@ categoryOrder: 0 # Sort order for the category group
|
|
|
59
61
|
order: 1 # Sort order within the category
|
|
60
62
|
icon: "https://..." # Page favicon URL
|
|
61
63
|
image: "https://..." # OpenGraph image URL
|
|
64
|
+
date: "2025-01-01" # Page date metadata
|
|
65
|
+
section: "API Reference" # Section this page belongs to
|
|
66
|
+
sectionOrder: 1 # Sort order for the section in the tab bar
|
|
62
67
|
---
|
|
63
68
|
```
|
|
64
69
|
|
|
65
70
|
Navigation is auto-generated from `category`, `categoryOrder`, and `order`. Pages without a category appear ungrouped.
|
|
66
71
|
|
|
72
|
+
Use `sectionLabel` on your root `index.mdx` to rename the default "Docs" tab:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
---
|
|
76
|
+
title: "Welcome"
|
|
77
|
+
sectionLabel: "Guides"
|
|
78
|
+
---
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Sections
|
|
82
|
+
|
|
83
|
+
Sections let you split your docs into separate tabbed groups (e.g. "Docs", "API Reference", "SDKs"). There are two ways to configure them:
|
|
84
|
+
|
|
85
|
+
### Via frontmatter
|
|
86
|
+
|
|
87
|
+
Add a `section` field to your MDX files. The section slug is derived from the label automatically. Use `sectionOrder` to control tab order (lower numbers appear first):
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
---
|
|
91
|
+
title: "Authentication"
|
|
92
|
+
section: "API Reference"
|
|
93
|
+
sectionOrder: 1
|
|
94
|
+
---
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Pages without a `section` field stay at the root URL under the default "Docs" tab.
|
|
98
|
+
|
|
99
|
+
### Via `sections.json`
|
|
100
|
+
|
|
101
|
+
For full control, create a `sections.json` file in your project root:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
[
|
|
105
|
+
{ "label": "Docs", "slug": "" },
|
|
106
|
+
{ "label": "API Reference", "slug": "api" },
|
|
107
|
+
{ "label": "SDKs", "slug": "sdks" }
|
|
108
|
+
]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each entry has:
|
|
112
|
+
|
|
113
|
+
- `label` - display name shown in the section bar
|
|
114
|
+
- `slug` - URL prefix for this section (use `""` for the root section)
|
|
115
|
+
- `directory` (optional) - subdirectory containing this section's MDX files, only needed when the directory name differs from the slug
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
[
|
|
119
|
+
{ "label": "Guides", "slug": "", "directory": "guides" },
|
|
120
|
+
{ "label": "API Reference", "slug": "api", "directory": "api-reference" }
|
|
121
|
+
]
|
|
122
|
+
```
|
|
123
|
+
|
|
67
124
|
## Configuration Files
|
|
68
125
|
|
|
69
126
|
Place these JSON files in your project root (where you run `doccupine`). They are auto-copied to the generated app and watched for changes.
|
|
@@ -76,6 +133,11 @@ Place these JSON files in your project root (where you run `doccupine`). They ar
|
|
|
76
133
|
| `navigation.json` | Manual navigation structure (overrides auto-generated) |
|
|
77
134
|
| `links.json` | Static header/footer links |
|
|
78
135
|
| `fonts.json` | Font configuration (Google Fonts or local) |
|
|
136
|
+
| `sections.json` | Section definitions for tabbed doc groups (see [Sections](#sections)) |
|
|
137
|
+
|
|
138
|
+
## Public Directory
|
|
139
|
+
|
|
140
|
+
Place static assets (images, favicons, `robots.txt`, etc.) in a `public/` directory at your project root. Doccupine copies it to the generated Next.js app on startup and watches for changes, so added, modified, or deleted files are synced automatically.
|
|
79
141
|
|
|
80
142
|
## AI Chat Setup
|
|
81
143
|
|
|
@@ -83,13 +145,33 @@ The generated app includes an AI chat assistant. To enable it, create a `.env.lo
|
|
|
83
145
|
|
|
84
146
|
```env
|
|
85
147
|
LLM_PROVIDER=openai # openai | anthropic | google
|
|
148
|
+
|
|
149
|
+
# API Keys (set the one matching your provider)
|
|
86
150
|
OPENAI_API_KEY=sk-...
|
|
87
|
-
|
|
88
|
-
|
|
151
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
152
|
+
GOOGLE_API_KEY=...
|
|
89
153
|
```
|
|
90
154
|
|
|
91
155
|
If `LLM_PROVIDER` is not set, the chat component is hidden automatically.
|
|
92
156
|
|
|
157
|
+
> **Note:** Anthropic does not provide an embeddings API. When using `anthropic` as your provider, you must also set `OPENAI_API_KEY` for embeddings to work.
|
|
158
|
+
|
|
159
|
+
### Optional overrides
|
|
160
|
+
|
|
161
|
+
```env
|
|
162
|
+
LLM_CHAT_MODEL=gpt-4.1-nano # Override the default chat model
|
|
163
|
+
LLM_EMBEDDING_MODEL=text-embedding-3-small # Override the default embedding model
|
|
164
|
+
LLM_TEMPERATURE=0 # Set temperature (0-1, default: 0)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Default models per provider:
|
|
168
|
+
|
|
169
|
+
| Provider | Chat model | Embedding model |
|
|
170
|
+
| --------- | ---------------------------- | ------------------------ |
|
|
171
|
+
| OpenAI | `gpt-4.1-nano` | `text-embedding-3-small` |
|
|
172
|
+
| Anthropic | `claude-sonnet-4-5-20250929` | OpenAI fallback |
|
|
173
|
+
| Google | `gemini-2.5-flash-lite` | `text-embedding-004` |
|
|
174
|
+
|
|
93
175
|
## MCP Server
|
|
94
176
|
|
|
95
177
|
The generated app exposes an MCP endpoint at `/api/mcp` with three tools:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sectionBarTemplate = "\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\ninterface SectionConfig {\n label: string;\n slug: string;\n directory?: string;\n}\n\ninterface SectionBarProps {\n sections: SectionConfig[];\n}\n\nconst StyledSectionBar = styled.nav<{ theme: Theme }>`\n display: flex;\n order: 3;\n width: calc(100% + 20px);\n margin: 0 0 0 -
|
|
1
|
+
export declare const sectionBarTemplate = "\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\ninterface SectionConfig {\n label: string;\n slug: string;\n directory?: string;\n}\n\ninterface SectionBarProps {\n sections: SectionConfig[];\n}\n\nconst StyledSectionBar = styled.nav<{ theme: Theme }>`\n display: flex;\n order: 3;\n width: calc(100% + 20px);\n margin: 0 0 0 -10px;\n padding: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n position: relative;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n padding: 0 10px;\n order: unset;\n width: 100%;\n margin: 0;\n justify-content: flex-end;\n }\n`;\n\nconst StyledSectionLink = styled(Link)<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n text-decoration: none;\n padding: 16px 10px;\n white-space: nowrap;\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"500\")};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.gray};\n border-bottom: solid 2px\n ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : \"transparent\"};\n transition: all 0.3s ease;\n min-width: fit-content;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nfunction SectionBar({ sections }: SectionBarProps) {\n const pathname = usePathname();\n const currentPath = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n\n const activeSection = sections.find((section) => {\n if (section.slug === \"\") return false;\n return (\n currentPath === section.slug || currentPath.startsWith(section.slug + \"/\")\n );\n });\n\n const activeSectionSlug = activeSection ? activeSection.slug : \"\";\n\n return (\n <StyledSectionBar>\n {sections.map((section) => (\n <StyledSectionLink\n key={section.slug}\n href={section.slug === \"\" ? \"/\" : `/${section.slug}`}\n $isActive={activeSectionSlug === section.slug}\n >\n {section.label}\n </StyledSectionLink>\n ))}\n </StyledSectionBar>\n );\n}\n\nexport { SectionBar };\n";
|
|
@@ -19,7 +19,7 @@ const StyledSectionBar = styled.nav<{ theme: Theme }>\`
|
|
|
19
19
|
display: flex;
|
|
20
20
|
order: 3;
|
|
21
21
|
width: calc(100% + 20px);
|
|
22
|
-
margin: 0 0 0 -
|
|
22
|
+
margin: 0 0 0 -10px;
|
|
23
23
|
padding: 0;
|
|
24
24
|
overflow-x: auto;
|
|
25
25
|
-webkit-overflow-scrolling: touch;
|
|
@@ -30,7 +30,7 @@ const StyledSectionBar = styled.nav<{ theme: Theme }>\`
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
\${mq("lg")} {
|
|
33
|
-
padding: 0
|
|
33
|
+
padding: 0 10px;
|
|
34
34
|
order: unset;
|
|
35
35
|
width: 100%;
|
|
36
36
|
margin: 0;
|
|
@@ -44,7 +44,7 @@ const StyledSectionLink = styled(Link)<{
|
|
|
44
44
|
}>\`
|
|
45
45
|
\${({ theme }) => styledText(theme)};
|
|
46
46
|
text-decoration: none;
|
|
47
|
-
padding: 16px
|
|
47
|
+
padding: 16px 10px;
|
|
48
48
|
white-space: nowrap;
|
|
49
49
|
font-weight: \${({ $isActive }) => ($isActive ? "600" : "500")};
|
|
50
50
|
color: \${({ theme, $isActive }) =>
|
package/package.json
CHANGED