@docsector/docsector-reader 1.2.4 β 1.3.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.
- package/README.md +62 -0
- package/bin/docsector.js +13 -1
- package/docsector.config.js +5 -2
- package/package.json +1 -1
- package/src/components/DPageMeta.vue +25 -2
- package/src/index.js +17 -0
- package/src/quasar.factory.js +102 -0
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Transform Markdown content into beautiful, navigable documentation sites β wit
|
|
|
28
28
|
- π€ **LLM Bot Detection** β Automatically serves raw Markdown to known AI crawlers (GPTBot, ClaudeBot, PerplexityBot, GrokBot, and others)
|
|
29
29
|
- πΊοΈ **Sitemap Generation** β Automatic `sitemap.xml` generation at build time with all page URLs (requires `siteUrl` in config)
|
|
30
30
|
- π€ **AI-Friendly robots.txt** β Scaffold includes a `robots.txt` explicitly allowing 23 AI crawlers (GPTBot, ClaudeBot, PerplexityBot, GrokBot, etc.)
|
|
31
|
+
- π§ **Content Signals** β Optional `Content-Signal` directive for declaring AI usage policy (`ai-train`, `search`, `ai-input`) in `robots.txt`
|
|
31
32
|
- π **Homepage Link Headers** β Auto-generated `Link` response headers for agent discovery (`api-catalog`, `service-doc`, `service-desc`, `describedby`) per RFC 8288 / RFC 9727
|
|
32
33
|
- π **MCP Server** β Auto-generated [MCP](https://modelcontextprotocol.io) server at `/mcp` for AI assistant integration (Claude Desktop, VS Code, etc.)
|
|
33
34
|
- π **llms.txt / llms-full.txt** β Auto-generated [llms.txt](https://llmstxt.org) index and full-content file for LLM discovery (requires `siteUrl` in config)
|
|
@@ -46,10 +47,12 @@ Transform Markdown content into beautiful, navigable documentation sites β wit
|
|
|
46
47
|
- π± **Responsive** β Mobile-friendly with collapsible sidebar and drawers
|
|
47
48
|
- π·οΈ **Status Badges** β Mark pages as `done`, `draft`, or `empty` with visual indicators
|
|
48
49
|
- βοΈ **Edit on GitHub** β Direct links to edit pages on your repository
|
|
50
|
+
- π§ **Robust Edit Link Mapping** β Normalizes route paths (including trailing slashes) into `page.subpage.locale.md` source files for reliable GitHub edit URLs
|
|
49
51
|
- π
**Last Updated Date** β Automatic per-page "last updated" date from git commit history, locale-formatted
|
|
50
52
|
- π **Translation Progress** β Automatic translation percentage based on header coverage
|
|
51
53
|
- π§ **Markdown Negotiation** β Responds with Markdown when clients send `Accept: text/markdown`, while keeping HTML as browser default
|
|
52
54
|
- π **Web Bot Auth** β Can publish a signed HTTP message signatures directory and includes helpers to sign outbound bot requests
|
|
55
|
+
- π§ **Content Signals** β Injects `Content-Signal` policy in `robots.txt` with deterministic, idempotent build output
|
|
53
56
|
- π **Markdown Home at Root** β Homepage is rendered from `src/pages/Homepage.{lang}.md` directly at `/`
|
|
54
57
|
- π§ **Quick Links Custom Element** β Use `<d-quick-links>` and `<d-quick-link>` in Markdown to render rich home navigation cards
|
|
55
58
|
- ποΈ **API Catalog Well-Known** β Auto-generates `/.well-known/api-catalog` as Linkset JSON for machine-readable API discovery
|
|
@@ -280,6 +283,56 @@ Check `checks.discoverability.linkHeaders.status` equals `"pass"`.
|
|
|
280
283
|
|
|
281
284
|
---
|
|
282
285
|
|
|
286
|
+
## π§ Content Signals
|
|
287
|
+
|
|
288
|
+
Docsector Reader can declare AI usage preferences in `robots.txt` via `Content-Signal`.
|
|
289
|
+
|
|
290
|
+
When enabled, build output ensures a deterministic directive format:
|
|
291
|
+
|
|
292
|
+
- `Content-Signal: ai-train=..., search=..., ai-input=...`
|
|
293
|
+
|
|
294
|
+
### Configure
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
export default {
|
|
298
|
+
// ...other config
|
|
299
|
+
|
|
300
|
+
contentSignals: {
|
|
301
|
+
enabled: true,
|
|
302
|
+
aiTrain: 'yes',
|
|
303
|
+
search: 'yes',
|
|
304
|
+
aiInput: 'yes',
|
|
305
|
+
userAgent: '*',
|
|
306
|
+
applyToAllBlocks: false
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Notes:
|
|
312
|
+
|
|
313
|
+
- `aiTrain`, `search`, and `aiInput` accept `yes` / `no` (or booleans).
|
|
314
|
+
- Default scope is only `User-agent: *`.
|
|
315
|
+
- Build patch is idempotent: repeated builds do not duplicate `Content-Signal` lines.
|
|
316
|
+
|
|
317
|
+
### Validate
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx docsector build
|
|
321
|
+
cat dist/spa/robots.txt
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Optional external validation:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
curl -X POST https://isitagentready.com/api/scan \
|
|
328
|
+
-H 'Content-Type: application/json' \
|
|
329
|
+
-d '{"url":"https://YOUR-SITE.com"}'
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Check `checks.botAccessControl.contentSignals.status` equals `"pass"`.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
283
336
|
## οΏ½π Quick Start
|
|
284
337
|
|
|
285
338
|
### π¦ Install
|
|
@@ -434,6 +487,15 @@ export default {
|
|
|
434
487
|
agentFallback: true
|
|
435
488
|
},
|
|
436
489
|
|
|
490
|
+
contentSignals: {
|
|
491
|
+
enabled: true,
|
|
492
|
+
aiTrain: 'yes',
|
|
493
|
+
search: 'yes',
|
|
494
|
+
aiInput: 'yes',
|
|
495
|
+
userAgent: '*',
|
|
496
|
+
applyToAllBlocks: false
|
|
497
|
+
},
|
|
498
|
+
|
|
437
499
|
languages: [
|
|
438
500
|
{ image: '/images/flags/united-states-of-america.png', label: 'English (US)', value: 'en-US' },
|
|
439
501
|
{ 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.
|
|
26
|
+
const VERSION = '1.3.1'
|
|
27
27
|
|
|
28
28
|
const HELP = `
|
|
29
29
|
Docsector Reader v${VERSION}
|
|
@@ -170,6 +170,17 @@ export default {
|
|
|
170
170
|
// signatureLabel: 'sig1'
|
|
171
171
|
// },
|
|
172
172
|
|
|
173
|
+
// @ Content Signals (optional)
|
|
174
|
+
// Declares AI usage preferences in robots.txt.
|
|
175
|
+
// contentSignals: {
|
|
176
|
+
// enabled: true,
|
|
177
|
+
// aiTrain: 'yes',
|
|
178
|
+
// search: 'yes',
|
|
179
|
+
// aiInput: 'yes',
|
|
180
|
+
// userAgent: '*',
|
|
181
|
+
// applyToAllBlocks: false
|
|
182
|
+
// },
|
|
183
|
+
|
|
173
184
|
// @ Languages
|
|
174
185
|
languages: [
|
|
175
186
|
{
|
|
@@ -763,6 +774,7 @@ const TEMPLATE_MARKDOWNLINT = `\
|
|
|
763
774
|
const TEMPLATE_ROBOTS_TXT = `\
|
|
764
775
|
User-agent: *
|
|
765
776
|
Allow: /
|
|
777
|
+
Content-Signal: ai-train=yes, search=yes, ai-input=yes
|
|
766
778
|
|
|
767
779
|
# Explicitly allow AI crawlers
|
|
768
780
|
# OpenAI
|
package/docsector.config.js
CHANGED
|
@@ -24,7 +24,7 @@ export default {
|
|
|
24
24
|
discussions: 'https://github.com/docsector/docsector-reader/discussions',
|
|
25
25
|
chat: null, // e.g., Discord/Slack invite URL
|
|
26
26
|
email: null, // e.g., 'mailto:contact@example.com'
|
|
27
|
-
changelog: '/
|
|
27
|
+
changelog: 'https://github.com/docsector/docsector-reader/releases',
|
|
28
28
|
roadmap: null, // e.g., external roadmap URL
|
|
29
29
|
sponsor: null, // e.g., GitHub Sponsors URL
|
|
30
30
|
explore: null // e.g., URL to explore related repos
|
|
@@ -58,5 +58,8 @@ export default {
|
|
|
58
58
|
],
|
|
59
59
|
|
|
60
60
|
// @ Default language
|
|
61
|
-
defaultLanguage: 'en-US'
|
|
61
|
+
defaultLanguage: 'en-US',
|
|
62
|
+
contentSignals: {
|
|
63
|
+
enabled: true
|
|
64
|
+
}
|
|
62
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|
|
@@ -12,11 +12,34 @@ const route = useRoute()
|
|
|
12
12
|
const router = useRouter()
|
|
13
13
|
const { t, locale, availableLocales, te, tm } = useI18n()
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
function normalizeEditBaseUrl (url = '') {
|
|
16
|
+
const normalized = String(url).trim().replace(/\/+$/, '')
|
|
17
|
+
return normalized.replace(/(github\.com\/[^/]+\/[^/]+)\/(blob|tree)\//, '$1/edit/')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const base = normalizeEditBaseUrl(docsectorConfig.github?.editBaseUrl || '')
|
|
21
|
+
|
|
22
|
+
function routePathToSourcePath (path = '') {
|
|
23
|
+
const cleanPath = String(path)
|
|
24
|
+
.replace(/\/index\.html$/, '')
|
|
25
|
+
.replace(/\/+$/, '')
|
|
26
|
+
|
|
27
|
+
if (cleanPath === '' || cleanPath === '/') {
|
|
28
|
+
return '/Homepage'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const segments = cleanPath.replace(/^\//, '').split('/').filter(Boolean)
|
|
32
|
+
if (segments.length < 2) {
|
|
33
|
+
return `/${segments.join('/')}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const subpage = segments.pop()
|
|
37
|
+
return `/${segments.join('/')}.${subpage}`
|
|
38
|
+
}
|
|
16
39
|
|
|
17
40
|
const status = computed(() => route.meta.status)
|
|
18
41
|
const URL = computed(() => {
|
|
19
|
-
const path = route.path
|
|
42
|
+
const path = routePathToSourcePath(route.path)
|
|
20
43
|
return `${base}${path}.${locale.value}.md`
|
|
21
44
|
})
|
|
22
45
|
const color = computed(() => {
|
package/src/index.js
CHANGED
|
@@ -66,6 +66,13 @@
|
|
|
66
66
|
* @param {string|null} [config.webBotAuth.keyId=null] - Optional static fallback key identifier when env var is absent
|
|
67
67
|
* @param {number} [config.webBotAuth.signatureMaxAge=300] - Signature validity window in seconds for directory responses
|
|
68
68
|
* @param {string} [config.webBotAuth.signatureLabel='sig1'] - Signature label used in Signature and Signature-Input headers
|
|
69
|
+
* @param {Object} [config.contentSignals] - Content Signals policy for robots.txt
|
|
70
|
+
* @param {boolean} [config.contentSignals.enabled=false] - Enables Content-Signal injection in robots.txt during build
|
|
71
|
+
* @param {'yes'|'no'|boolean} [config.contentSignals.aiTrain='yes'] - Permission for AI model training consumption
|
|
72
|
+
* @param {'yes'|'no'|boolean} [config.contentSignals.search='yes'] - Permission for AI search indexing/discovery consumption
|
|
73
|
+
* @param {'yes'|'no'|boolean} [config.contentSignals.aiInput='yes'] - Permission for AI input/inference-time consumption
|
|
74
|
+
* @param {string} [config.contentSignals.userAgent='*'] - Target User-agent block for directive injection
|
|
75
|
+
* @param {boolean} [config.contentSignals.applyToAllBlocks=false] - When true, applies directive to every User-agent block
|
|
69
76
|
* @returns {Object} Resolved Docsector configuration
|
|
70
77
|
*/
|
|
71
78
|
export function createDocsector (config = {}) {
|
|
@@ -139,6 +146,16 @@ export function createDocsector (config = {}) {
|
|
|
139
146
|
signatureMaxAge: 300,
|
|
140
147
|
signatureLabel: 'sig1',
|
|
141
148
|
...config.webBotAuth
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
contentSignals: {
|
|
152
|
+
enabled: false,
|
|
153
|
+
aiTrain: 'yes',
|
|
154
|
+
search: 'yes',
|
|
155
|
+
aiInput: 'yes',
|
|
156
|
+
userAgent: '*',
|
|
157
|
+
applyToAllBlocks: false,
|
|
158
|
+
...config.contentSignals
|
|
142
159
|
}
|
|
143
160
|
}
|
|
144
161
|
}
|
package/src/quasar.factory.js
CHANGED
|
@@ -753,6 +753,8 @@ function createMarkdownBuildPlugin (projectRoot) {
|
|
|
753
753
|
? Math.max(30, Number(webBotAuthConfig.signatureMaxAge))
|
|
754
754
|
: 300
|
|
755
755
|
const webBotAuthSignatureLabel = webBotAuthConfig.signatureLabel || 'sig1'
|
|
756
|
+
const contentSignalsConfig = config.contentSignals || {}
|
|
757
|
+
const contentSignalsEnabled = contentSignalsConfig.enabled === true
|
|
756
758
|
|
|
757
759
|
if (markdownNegotiationEnabled || webBotAuthEnabled) {
|
|
758
760
|
const functionsDir = resolve(projectRoot, 'functions')
|
|
@@ -1001,6 +1003,25 @@ export async function onRequest (context) {
|
|
|
1001
1003
|
writeFileSync(resolve(functionsDir, '_middleware.js'), middlewareCode)
|
|
1002
1004
|
console.log(`\x1b[36m[docsector]\x1b[0m Generated runtime middleware at functions/_middleware.js`)
|
|
1003
1005
|
}
|
|
1006
|
+
|
|
1007
|
+
if (contentSignalsEnabled) {
|
|
1008
|
+
const robotsPath = resolve(distDir, 'robots.txt')
|
|
1009
|
+
const existingRobots = existsSync(robotsPath)
|
|
1010
|
+
? readFileSync(robotsPath, 'utf-8')
|
|
1011
|
+
: 'User-agent: *\nAllow: /\n'
|
|
1012
|
+
const contentSignalLine = buildContentSignalLine(contentSignalsConfig)
|
|
1013
|
+
const patchedRobots = applyContentSignalsToRobots(existingRobots, {
|
|
1014
|
+
contentSignalLine,
|
|
1015
|
+
userAgent: contentSignalsConfig.userAgent || '*',
|
|
1016
|
+
applyToAllBlocks: contentSignalsConfig.applyToAllBlocks === true
|
|
1017
|
+
})
|
|
1018
|
+
|
|
1019
|
+
if (patchedRobots !== existingRobots) {
|
|
1020
|
+
writeFileSync(robotsPath, patchedRobots)
|
|
1021
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Updated robots.txt with Content-Signal policy`)
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1004
1025
|
console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for .md files`)
|
|
1005
1026
|
|
|
1006
1027
|
// Add homepage Link headers for agent discovery (RFC 8288 / RFC 9727)
|
|
@@ -1274,6 +1295,87 @@ export async function onRequest (context) {
|
|
|
1274
1295
|
}
|
|
1275
1296
|
}
|
|
1276
1297
|
|
|
1298
|
+
function normalizeContentSignalValue (value, fallback = 'yes') {
|
|
1299
|
+
if (typeof value === 'boolean') return value ? 'yes' : 'no'
|
|
1300
|
+
if (typeof value === 'string') {
|
|
1301
|
+
const normalized = value.trim().toLowerCase()
|
|
1302
|
+
if (normalized === 'yes' || normalized === 'no') return normalized
|
|
1303
|
+
}
|
|
1304
|
+
return fallback
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function buildContentSignalLine (contentSignalsConfig = {}) {
|
|
1308
|
+
const aiTrain = normalizeContentSignalValue(contentSignalsConfig.aiTrain, 'yes')
|
|
1309
|
+
const search = normalizeContentSignalValue(contentSignalsConfig.search, 'yes')
|
|
1310
|
+
const aiInput = normalizeContentSignalValue(contentSignalsConfig.aiInput, 'yes')
|
|
1311
|
+
return `Content-Signal: ai-train=${aiTrain}, search=${search}, ai-input=${aiInput}`
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function ensureContentSignalInBlock (blockLines, contentSignalLine) {
|
|
1315
|
+
const cleanedBlock = blockLines.filter((line, index) => {
|
|
1316
|
+
if (index === 0) return true
|
|
1317
|
+
return !/^\s*Content-Signal\s*:/i.test(line)
|
|
1318
|
+
})
|
|
1319
|
+
|
|
1320
|
+
let insertAt = cleanedBlock.findIndex(line => /^\s*Allow\s*:/i.test(line))
|
|
1321
|
+
if (insertAt === -1) {
|
|
1322
|
+
insertAt = 0
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
cleanedBlock.splice(insertAt + 1, 0, contentSignalLine)
|
|
1326
|
+
return cleanedBlock
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function applyContentSignalsToRobots (robotsContent, { contentSignalLine, userAgent = '*', applyToAllBlocks = false }) {
|
|
1330
|
+
const input = typeof robotsContent === 'string' ? robotsContent : ''
|
|
1331
|
+
const lines = input.replace(/\r\n/g, '\n').split('\n')
|
|
1332
|
+
const userAgentIndexes = []
|
|
1333
|
+
|
|
1334
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1335
|
+
if (/^\s*User-agent\s*:/i.test(lines[i])) {
|
|
1336
|
+
userAgentIndexes.push(i)
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
if (userAgentIndexes.length === 0) {
|
|
1341
|
+
const scaffold = [
|
|
1342
|
+
`User-agent: ${userAgent}`,
|
|
1343
|
+
'Allow: /',
|
|
1344
|
+
contentSignalLine,
|
|
1345
|
+
''
|
|
1346
|
+
].join('\n')
|
|
1347
|
+
return scaffold.endsWith('\n') ? scaffold : scaffold + '\n'
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const targetIndexes = []
|
|
1351
|
+
for (const startIndex of userAgentIndexes) {
|
|
1352
|
+
const uaValue = lines[startIndex].split(':').slice(1).join(':').trim()
|
|
1353
|
+
if (applyToAllBlocks || uaValue.toLowerCase() === String(userAgent).toLowerCase()) {
|
|
1354
|
+
targetIndexes.push(startIndex)
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (targetIndexes.length === 0) {
|
|
1359
|
+
targetIndexes.push(userAgentIndexes[0])
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
let updated = []
|
|
1363
|
+
let cursor = 0
|
|
1364
|
+
|
|
1365
|
+
for (const startIndex of targetIndexes) {
|
|
1366
|
+
const nextUserAgent = userAgentIndexes.find(index => index > startIndex)
|
|
1367
|
+
const endIndex = nextUserAgent === undefined ? lines.length : nextUserAgent
|
|
1368
|
+
|
|
1369
|
+
updated = updated.concat(lines.slice(cursor, startIndex))
|
|
1370
|
+
const currentBlock = lines.slice(startIndex, endIndex)
|
|
1371
|
+
updated = updated.concat(ensureContentSignalInBlock(currentBlock, contentSignalLine))
|
|
1372
|
+
cursor = endIndex
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
updated = updated.concat(lines.slice(cursor))
|
|
1376
|
+
return updated.join('\n').replace(/\n+$/g, '') + '\n'
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1277
1379
|
/**
|
|
1278
1380
|
* Create a complete Quasar configuration for a docsector-reader consumer project.
|
|
1279
1381
|
*
|