@docsector/docsector-reader 1.0.0 β†’ 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ Transform Markdown content into beautiful, navigable documentation sites β€” wit
26
26
  - πŸ€– **LLM Bot Detection** β€” Automatically serves raw Markdown to known AI crawlers (GPTBot, ClaudeBot, PerplexityBot, GrokBot, and others)
27
27
  - πŸ—ΊοΈ **Sitemap Generation** β€” Automatic `sitemap.xml` generation at build time with all page URLs (requires `siteUrl` in config)
28
28
  - πŸ€– **AI-Friendly robots.txt** β€” Scaffold includes a `robots.txt` explicitly allowing 23 AI crawlers (GPTBot, ClaudeBot, PerplexityBot, GrokBot, etc.)
29
- - πŸ”— **Homepage Link Headers** β€” Auto-generated `Link` response headers for agent discovery (`service-doc`, `service-desc`, `describedby`) per RFC 8288 / RFC 9727
29
+ - πŸ”— **Homepage Link Headers** β€” Auto-generated `Link` response headers for agent discovery (`api-catalog`, `service-doc`, `service-desc`, `describedby`) per RFC 8288 / RFC 9727
30
30
  - πŸ”Œ **MCP Server** β€” Auto-generated [MCP](https://modelcontextprotocol.io) server at `/mcp` for AI assistant integration (Claude Desktop, VS Code, etc.)
31
31
  - πŸ“„ **llms.txt / llms-full.txt** β€” Auto-generated [llms.txt](https://llmstxt.org) index and full-content file for LLM discovery (requires `siteUrl` in config)
32
32
 
@@ -48,6 +48,7 @@ Transform Markdown content into beautiful, navigable documentation sites β€” wit
48
48
  - πŸ“Š **Translation Progress** β€” Automatic translation percentage based on header coverage
49
49
  - 🏠 **Markdown Home at Root** β€” Homepage is rendered from `src/pages/Homepage.{lang}.md` directly at `/`
50
50
  - 🧭 **Quick Links Custom Element** β€” Use `<d-quick-links>` and `<d-quick-link>` in Markdown to render rich home navigation cards
51
+ - πŸ—‚οΈ **API Catalog Well-Known** β€” Auto-generates `/.well-known/api-catalog` as Linkset JSON for machine-readable API discovery
51
52
  - βš™οΈ **Single Config File** β€” Customize branding, links, and languages via `docsector.config.js`
52
53
 
53
54
  ---
@@ -162,6 +163,7 @@ Docsector Reader adds homepage `Link` response headers at build time for agent d
162
163
 
163
164
  Default relations emitted on homepage (`/` and `/index.html`):
164
165
 
166
+ - `rel="api-catalog"` β†’ `</.well-known/api-catalog>`
165
167
  - `rel="service-doc"` β†’ `</>`
166
168
  - `rel="service-desc"` β†’ `</mcp>` (only when `mcp` is enabled)
167
169
  - `rel="describedby"` β†’ `</llms.txt>` (only when `siteUrl` is configured, i.e. `llms.txt` is generated)
@@ -169,6 +171,7 @@ Default relations emitted on homepage (`/` and `/index.html`):
169
171
  Generated in:
170
172
 
171
173
  - `dist/spa/_headers`
174
+ - `dist/spa/.well-known/api-catalog` (Linkset JSON)
172
175
 
173
176
  ### Optional configuration
174
177
 
@@ -178,9 +181,19 @@ export default {
178
181
 
179
182
  linkHeaders: {
180
183
  enabled: true,
184
+ apiCatalog: '/.well-known/api-catalog',
181
185
  serviceDoc: '/',
182
186
  serviceDesc: '/mcp',
183
187
  describedBy: '/llms.txt'
188
+ },
189
+
190
+ apiCatalog: {
191
+ enabled: true,
192
+ path: '/.well-known/api-catalog',
193
+ items: [
194
+ '/mcp',
195
+ 'https://api.example.com/openapi.json'
196
+ ]
184
197
  }
185
198
  }
186
199
  ```
@@ -192,6 +205,7 @@ Set any target to `null` or `false` to disable that relation.
192
205
  ```bash
193
206
  npx docsector build
194
207
  cat dist/spa/_headers
208
+ cat dist/spa/.well-known/api-catalog
195
209
  ```
196
210
 
197
211
  Or scan discoverability:
@@ -343,11 +357,18 @@ export default {
343
357
 
344
358
  linkHeaders: {
345
359
  enabled: true,
360
+ apiCatalog: '/.well-known/api-catalog',
346
361
  serviceDoc: '/',
347
362
  serviceDesc: '/mcp',
348
363
  describedBy: '/llms.txt'
349
364
  },
350
365
 
366
+ apiCatalog: {
367
+ enabled: true,
368
+ path: '/.well-known/api-catalog',
369
+ items: []
370
+ },
371
+
351
372
  languages: [
352
373
  { image: '/images/flags/united-states-of-america.png', label: 'English (US)', value: 'en-US' },
353
374
  { image: '/images/flags/brazil.png', label: 'PortuguΓͺs (BR)', value: 'pt-BR' }
package/bin/docsector.js CHANGED
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
23
23
  const args = process.argv.slice(2)
24
24
  const command = args[0]
25
25
 
26
- const VERSION = '1.0.0'
26
+ const VERSION = '1.1.0'
27
27
 
28
28
  const HELP = `
29
29
  Docsector Reader v${VERSION}
@@ -136,11 +136,19 @@ export default {
136
136
  // @ Homepage Link headers for agent discovery (optional)
137
137
  // linkHeaders: {
138
138
  // enabled: true,
139
+ // apiCatalog: '/.well-known/api-catalog',
139
140
  // serviceDoc: '/',
140
141
  // serviceDesc: '/mcp',
141
142
  // describedBy: '/llms.txt'
142
143
  // },
143
144
 
145
+ // @ API catalog artifact (RFC 9727) (optional)
146
+ // apiCatalog: {
147
+ // enabled: true,
148
+ // path: '/.well-known/api-catalog',
149
+ // items: ['/mcp']
150
+ // },
151
+
144
152
  // @ Languages
145
153
  languages: [
146
154
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
5
5
  "productName": "Docsector Reader",
6
6
  "author": "Rodrigo de Araujo Vieira",
package/src/index.js CHANGED
@@ -47,8 +47,13 @@
47
47
  * @param {Object} [config.linkHeaders] - Homepage Link headers for agent discovery
48
48
  * @param {boolean} [config.linkHeaders.enabled=true] - Enables homepage Link headers generation
49
49
  * @param {string|null|false} [config.linkHeaders.serviceDoc='/'] - Target URI for rel="service-doc"
50
+ * @param {string|null|false} [config.linkHeaders.apiCatalog='/.well-known/api-catalog'] - Target URI for rel="api-catalog"
50
51
  * @param {string|null|false} [config.linkHeaders.serviceDesc='/mcp'] - Target URI for rel="service-desc" (only emitted when MCP is enabled)
51
52
  * @param {string|null|false} [config.linkHeaders.describedBy='/llms.txt'] - Target URI for rel="describedby" (only emitted when llms.txt is generated)
53
+ * @param {Object} [config.apiCatalog] - API catalog generation settings
54
+ * @param {boolean} [config.apiCatalog.enabled=true] - Enables generation of API catalog artifact
55
+ * @param {string} [config.apiCatalog.path='/.well-known/api-catalog'] - Output URI path for API catalog artifact
56
+ * @param {Array<string|{href: string}>} [config.apiCatalog.items=[]] - Additional API endpoint links to include as item relations
52
57
  * @returns {Object} Resolved Docsector configuration
53
58
  */
54
59
  export function createDocsector (config = {}) {
@@ -93,9 +98,17 @@ export function createDocsector (config = {}) {
93
98
  linkHeaders: {
94
99
  enabled: true,
95
100
  serviceDoc: '/',
101
+ apiCatalog: '/.well-known/api-catalog',
96
102
  serviceDesc: '/mcp',
97
103
  describedBy: '/llms.txt',
98
104
  ...config.linkHeaders
105
+ },
106
+
107
+ apiCatalog: {
108
+ enabled: true,
109
+ path: '/.well-known/api-catalog',
110
+ items: [],
111
+ ...config.apiCatalog
99
112
  }
100
113
  }
101
114
  }
@@ -679,6 +679,13 @@ function createMarkdownBuildPlugin (projectRoot) {
679
679
  homepageLinks.push({ rel: 'service-doc', href: serviceDocHref })
680
680
  }
681
681
 
682
+ const apiCatalogHref = linkHeadersConfig.apiCatalog === undefined
683
+ ? '/.well-known/api-catalog'
684
+ : linkHeadersConfig.apiCatalog
685
+ if (apiCatalogHref) {
686
+ homepageLinks.push({ rel: 'api-catalog', href: apiCatalogHref })
687
+ }
688
+
682
689
  const serviceDescHref = linkHeadersConfig.serviceDesc === undefined
683
690
  ? '/mcp'
684
691
  : linkHeadersConfig.serviceDesc
@@ -694,19 +701,106 @@ function createMarkdownBuildPlugin (projectRoot) {
694
701
  }
695
702
 
696
703
  if (homepageLinks.length > 0) {
697
- const linkLines = homepageLinks.map(({ rel, href }) => ` Link: <${href}>; rel="${rel}"`).join('\n')
698
- const homepageRule = ['/','/index.html']
699
- .map(path => `${path}\n${linkLines}`)
700
- .join('\n\n') + '\n'
701
-
702
704
  const currentHeaders = readFileSync(headersPath, 'utf-8')
703
- const hasAgentLinks = currentHeaders.includes('rel="service-doc"')
704
- || currentHeaders.includes('rel="service-desc"')
705
- || currentHeaders.includes('rel="describedby"')
705
+ const missingHomepageLinks = homepageLinks.filter(({ rel }) => !currentHeaders.includes(`rel="${rel}"`))
706
+
707
+ if (missingHomepageLinks.length > 0) {
708
+ const linkLines = missingHomepageLinks
709
+ .map(({ rel, href }) => ` Link: <${href}>; rel="${rel}"`)
710
+ .join('\n')
711
+ const homepageRule = ['/', '/index.html']
712
+ .map(path => `${path}\n${linkLines}`)
713
+ .join('\n\n') + '\n'
706
714
 
707
- if (!hasAgentLinks) {
708
715
  writeFileSync(headersPath, currentHeaders.trimEnd() + '\n\n' + homepageRule)
709
- console.log(`\x1b[36m[docsector]\x1b[0m Added homepage Link headers for agent discovery`)
716
+ console.log(`\x1b[36m[docsector]\x1b[0m Added homepage Link headers for agent discovery (${missingHomepageLinks.length} relation(s))`)
717
+ }
718
+ }
719
+
720
+ // Generate /.well-known/api-catalog Linkset document (RFC 9727)
721
+ const apiCatalogConfig = config.apiCatalog || {}
722
+ const apiCatalogEnabled = apiCatalogConfig.enabled !== false
723
+ const apiCatalogPath = (apiCatalogConfig.path || apiCatalogHref || '/.well-known/api-catalog')
724
+
725
+ const toUrl = (href) => {
726
+ if (!href) return null
727
+ if (/^https?:\/\//i.test(href)) return href
728
+ const normalizedHref = href.startsWith('/') ? href : `/${href}`
729
+ return siteUrl ? `${siteUrl}${normalizedHref}` : normalizedHref
730
+ }
731
+
732
+ const normalizeLocalPath = (href) => {
733
+ if (!href || /^https?:\/\//i.test(href)) return null
734
+ const path = href.startsWith('/') ? href.slice(1) : href
735
+ return path || null
736
+ }
737
+
738
+ if (apiCatalogEnabled && apiCatalogPath) {
739
+ const catalogDistPath = normalizeLocalPath(apiCatalogPath)
740
+
741
+ if (catalogDistPath) {
742
+ const catalogHref = apiCatalogPath.startsWith('/') ? apiCatalogPath : `/${apiCatalogPath}`
743
+ const catalogEntry = {
744
+ anchor: toUrl(catalogHref)
745
+ }
746
+
747
+ const catalogServiceDoc = toUrl(serviceDocHref)
748
+ if (catalogServiceDoc) {
749
+ catalogEntry['service-doc'] = [{ href: catalogServiceDoc }]
750
+ }
751
+
752
+ const catalogServiceDesc = config.mcp ? toUrl(serviceDescHref) : null
753
+ if (catalogServiceDesc) {
754
+ catalogEntry['service-desc'] = [{ href: catalogServiceDesc }]
755
+ }
756
+
757
+ const catalogDescribedBy = siteUrl ? toUrl(describedByHref) : null
758
+ if (catalogDescribedBy) {
759
+ catalogEntry.describedby = [{ href: catalogDescribedBy }]
760
+ }
761
+
762
+ const customItems = Array.isArray(apiCatalogConfig.items)
763
+ ? apiCatalogConfig.items
764
+ : []
765
+ const itemHrefs = new Set()
766
+
767
+ if (catalogServiceDesc) {
768
+ itemHrefs.add(catalogServiceDesc)
769
+ }
770
+
771
+ for (const item of customItems) {
772
+ if (typeof item === 'string') {
773
+ const href = toUrl(item)
774
+ if (href) itemHrefs.add(href)
775
+ continue
776
+ }
777
+
778
+ if (item && typeof item === 'object' && typeof item.href === 'string') {
779
+ const href = toUrl(item.href)
780
+ if (href) itemHrefs.add(href)
781
+ }
782
+ }
783
+
784
+ if (itemHrefs.size > 0) {
785
+ catalogEntry.item = [...itemHrefs].map(href => ({ href }))
786
+ }
787
+
788
+ const catalogDir = resolve(distDir, catalogDistPath, '..')
789
+ mkdirSync(catalogDir, { recursive: true })
790
+ writeFileSync(
791
+ resolve(distDir, catalogDistPath),
792
+ JSON.stringify({ linkset: [catalogEntry] }, null, 2) + '\n'
793
+ )
794
+ console.log(`\x1b[36m[docsector]\x1b[0m Generated ${catalogHref}`)
795
+
796
+ const headersWithLinks = readFileSync(headersPath, 'utf-8')
797
+ if (!headersWithLinks.includes(catalogHref)) {
798
+ const apiCatalogHeaders = `${catalogHref}\n Content-Type: application/linkset+json; profile=\"https://www.rfc-editor.org/info/rfc9727\"\n`
799
+ writeFileSync(headersPath, headersWithLinks.trimEnd() + '\n\n' + apiCatalogHeaders)
800
+ console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for ${catalogHref}`)
801
+ }
802
+ } else {
803
+ console.warn(`\x1b[33m[docsector]\x1b[0m Skipped API catalog generation: path must be a local URI path, got \"${apiCatalogPath}\"`)
710
804
  }
711
805
  }
712
806
  }