@docsector/docsector-reader 1.3.1 β†’ 1.4.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
@@ -29,6 +29,7 @@ Transform Markdown content into beautiful, navigable documentation sites β€” wit
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
31
  - 🧭 **Content Signals** β€” Optional `Content-Signal` directive for declaring AI usage policy (`ai-train`, `search`, `ai-input`) in `robots.txt`
32
+ - 🧩 **Agent Skills Discovery Index** β€” Optional `/.well-known/agent-skills/index.json` with RFC v0.2.0 schema and SHA-256 digests
32
33
  - πŸ”— **Homepage Link Headers** β€” Auto-generated `Link` response headers for agent discovery (`api-catalog`, `service-doc`, `service-desc`, `describedby`) per RFC 8288 / RFC 9727
33
34
  - πŸ”Œ **MCP Server** β€” Auto-generated [MCP](https://modelcontextprotocol.io) server at `/mcp` for AI assistant integration (Claude Desktop, VS Code, etc.)
34
35
  - πŸ“„ **llms.txt / llms-full.txt** β€” Auto-generated [llms.txt](https://llmstxt.org) index and full-content file for LLM discovery (requires `siteUrl` in config)
@@ -333,6 +334,68 @@ Check `checks.botAccessControl.contentSignals.status` equals `"pass"`.
333
334
 
334
335
  ---
335
336
 
337
+ ## 🧩 Agent Skills Discovery Index
338
+
339
+ Docsector Reader can publish a discovery index at:
340
+
341
+ - `/.well-known/agent-skills/index.json`
342
+
343
+ The generated payload follows Agent Skills Discovery RFC v0.2.0 and includes:
344
+
345
+ - `$schema`
346
+ - `skills[]` entries with `name`, `type`, `description`, `url`, `digest`
347
+
348
+ When `digest` is omitted in config, Docsector computes it automatically from the referenced local artifact and writes it as:
349
+
350
+ - `sha256:{hex}`
351
+
352
+ ### Configure
353
+
354
+ ```javascript
355
+ export default {
356
+ // ...other config
357
+
358
+ agentSkills: {
359
+ enabled: true,
360
+ path: '/.well-known/agent-skills/index.json',
361
+ schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
362
+ skills: [
363
+ {
364
+ name: 'my-docs-mcp',
365
+ type: 'skill-md',
366
+ description: 'Search and fetch docs pages via MCP.',
367
+ url: '/.well-known/agent-skills/my-docs-mcp/SKILL.md'
368
+ }
369
+ ]
370
+ }
371
+ }
372
+ ```
373
+
374
+ Notes:
375
+
376
+ - `name` must be lowercase alphanumeric plus hyphens.
377
+ - `type` must be `skill-md` or `archive`.
378
+ - `url` should point to a locally published artifact when auto-digest is used.
379
+
380
+ ### Validate
381
+
382
+ ```bash
383
+ npx docsector build
384
+ cat dist/spa/.well-known/agent-skills/index.json
385
+ ```
386
+
387
+ External validation:
388
+
389
+ ```bash
390
+ curl -X POST https://isitagentready.com/api/scan \
391
+ -H 'Content-Type: application/json' \
392
+ -d '{"url":"https://YOUR-SITE.com"}'
393
+ ```
394
+
395
+ Check `checks.discovery.agentSkills.status` equals `"pass"`.
396
+
397
+ ---
398
+
336
399
  ## οΏ½πŸš€ Quick Start
337
400
 
338
401
  ### πŸ“¦ Install
@@ -496,6 +559,20 @@ export default {
496
559
  applyToAllBlocks: false
497
560
  },
498
561
 
562
+ agentSkills: {
563
+ enabled: true,
564
+ path: '/.well-known/agent-skills/index.json',
565
+ schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
566
+ skills: [
567
+ {
568
+ name: 'my-docs-mcp',
569
+ type: 'skill-md',
570
+ description: 'Search and fetch docs pages via MCP.',
571
+ url: '/.well-known/agent-skills/my-docs-mcp/SKILL.md'
572
+ }
573
+ ]
574
+ },
575
+
499
576
  languages: [
500
577
  { image: '/images/flags/united-states-of-america.png', label: 'English (US)', value: 'en-US' },
501
578
  { 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.3.1'
26
+ const VERSION = '1.4.0'
27
27
 
28
28
  const HELP = `
29
29
  Docsector Reader v${VERSION}
@@ -181,6 +181,23 @@ export default {
181
181
  // applyToAllBlocks: false
182
182
  // },
183
183
 
184
+ // @ Agent Skills discovery index (optional)
185
+ // Publishes /.well-known/agent-skills/index.json (RFC v0.2.0)
186
+ // and computes sha256 digests from local artifacts.
187
+ // agentSkills: {
188
+ // enabled: true,
189
+ // path: '/.well-known/agent-skills/index.json',
190
+ // schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
191
+ // skills: [
192
+ // {
193
+ // name: 'my-docs-mcp',
194
+ // type: 'skill-md',
195
+ // description: 'Search and read docs pages through MCP.',
196
+ // url: '/.well-known/agent-skills/my-docs-mcp/SKILL.md'
197
+ // }
198
+ // ]
199
+ // },
200
+
184
201
  // @ Languages
185
202
  languages: [
186
203
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "1.3.1",
3
+ "version": "1.4.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
@@ -73,6 +73,11 @@
73
73
  * @param {'yes'|'no'|boolean} [config.contentSignals.aiInput='yes'] - Permission for AI input/inference-time consumption
74
74
  * @param {string} [config.contentSignals.userAgent='*'] - Target User-agent block for directive injection
75
75
  * @param {boolean} [config.contentSignals.applyToAllBlocks=false] - When true, applies directive to every User-agent block
76
+ * @param {Object} [config.agentSkills] - Agent Skills discovery index settings
77
+ * @param {boolean} [config.agentSkills.enabled=false] - Enables generation of Agent Skills discovery index
78
+ * @param {string} [config.agentSkills.path='/.well-known/agent-skills/index.json'] - Output URI path for Agent Skills index
79
+ * @param {string} [config.agentSkills.schema='https://schemas.agentskills.io/discovery/0.2.0/schema.json'] - JSON Schema identifier for index payload
80
+ * @param {Array<{name:string,type:'skill-md'|'archive',description:string,url:string,digest?:string}>} [config.agentSkills.skills=[]] - Skills to publish in discovery index
76
81
  * @returns {Object} Resolved Docsector configuration
77
82
  */
78
83
  export function createDocsector (config = {}) {
@@ -156,6 +161,14 @@ export function createDocsector (config = {}) {
156
161
  userAgent: '*',
157
162
  applyToAllBlocks: false,
158
163
  ...config.contentSignals
164
+ },
165
+
166
+ agentSkills: {
167
+ enabled: false,
168
+ path: '/.well-known/agent-skills/index.json',
169
+ schema: 'https://schemas.agentskills.io/discovery/0.2.0/schema.json',
170
+ skills: [],
171
+ ...config.agentSkills
159
172
  }
160
173
  }
161
174
  }
@@ -755,6 +755,21 @@ function createMarkdownBuildPlugin (projectRoot) {
755
755
  const webBotAuthSignatureLabel = webBotAuthConfig.signatureLabel || 'sig1'
756
756
  const contentSignalsConfig = config.contentSignals || {}
757
757
  const contentSignalsEnabled = contentSignalsConfig.enabled === true
758
+ const agentSkillsConfig = config.agentSkills || {}
759
+ const agentSkillsEnabled = agentSkillsConfig.enabled === true
760
+
761
+ const toUrl = (href) => {
762
+ if (!href) return null
763
+ if (/^https?:\/\//i.test(href)) return href
764
+ const normalizedHref = href.startsWith('/') ? href : `/${href}`
765
+ return siteUrl ? `${siteUrl}${normalizedHref}` : normalizedHref
766
+ }
767
+
768
+ const normalizeLocalPath = (href) => {
769
+ if (!href || /^https?:\/\//i.test(href)) return null
770
+ const path = href.startsWith('/') ? href.slice(1) : href
771
+ return path || null
772
+ }
758
773
 
759
774
  if (markdownNegotiationEnabled || webBotAuthEnabled) {
760
775
  const functionsDir = resolve(projectRoot, 'functions')
@@ -1022,6 +1037,81 @@ export async function onRequest (context) {
1022
1037
  }
1023
1038
  }
1024
1039
 
1040
+ if (agentSkillsEnabled) {
1041
+ const agentSkillsPath = agentSkillsConfig.path || '/.well-known/agent-skills/index.json'
1042
+ const agentSkillsSchema = agentSkillsConfig.schema || 'https://schemas.agentskills.io/discovery/0.2.0/schema.json'
1043
+ const indexDistPath = normalizeLocalPath(agentSkillsPath)
1044
+
1045
+ if (!indexDistPath) {
1046
+ console.warn(`\x1b[33m[docsector]\x1b[0m Skipped Agent Skills index generation: path must be a local URI path, got "${agentSkillsPath}"`)
1047
+ } else {
1048
+ const indexHref = agentSkillsPath.startsWith('/') ? agentSkillsPath : `/${agentSkillsPath}`
1049
+ const configuredSkills = Array.isArray(agentSkillsConfig.skills)
1050
+ ? agentSkillsConfig.skills
1051
+ : []
1052
+
1053
+ const normalizedSkills = configuredSkills.map((skill, index) => {
1054
+ if (!skill || typeof skill !== 'object') {
1055
+ throw new Error(`[docsector] agentSkills.skills[${index}] must be an object`)
1056
+ }
1057
+
1058
+ const name = String(skill.name || '').trim().toLowerCase()
1059
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
1060
+ throw new Error(`[docsector] agentSkills.skills[${index}].name must be lowercase alphanumeric and hyphen-separated`)
1061
+ }
1062
+
1063
+ const type = normalizeAgentSkillType(skill.type, index)
1064
+ const description = String(skill.description || '').trim()
1065
+ if (!description) {
1066
+ throw new Error(`[docsector] agentSkills.skills[${index}].description is required`)
1067
+ }
1068
+
1069
+ const url = toUrl(skill.url)
1070
+ if (!url) {
1071
+ throw new Error(`[docsector] agentSkills.skills[${index}].url is required`)
1072
+ }
1073
+
1074
+ let digest = normalizeAgentSkillDigest(skill.digest)
1075
+ if (!digest) {
1076
+ const artifactPath = resolveAgentSkillArtifactPath(url, { siteUrl, distDir })
1077
+ if (!artifactPath || !existsSync(artifactPath)) {
1078
+ throw new Error(`[docsector] Unable to compute digest for agentSkills.skills[${index}] (${name}). Artifact not found at ${url}`)
1079
+ }
1080
+ const artifactContents = readFileSync(artifactPath)
1081
+ digest = `sha256:${createHash('sha256').update(artifactContents).digest('hex')}`
1082
+ }
1083
+
1084
+ return {
1085
+ name,
1086
+ type,
1087
+ description,
1088
+ url,
1089
+ digest
1090
+ }
1091
+ })
1092
+
1093
+ const agentSkillsIndex = {
1094
+ $schema: agentSkillsSchema,
1095
+ skills: normalizedSkills
1096
+ }
1097
+
1098
+ const indexDir = resolve(distDir, indexDistPath, '..')
1099
+ mkdirSync(indexDir, { recursive: true })
1100
+ writeFileSync(
1101
+ resolve(distDir, indexDistPath),
1102
+ JSON.stringify(agentSkillsIndex, null, 2) + '\n'
1103
+ )
1104
+ console.log(`\x1b[36m[docsector]\x1b[0m Generated ${indexHref}`)
1105
+
1106
+ const headersWithSkills = readFileSync(headersPath, 'utf-8')
1107
+ if (!headersWithSkills.includes(indexHref)) {
1108
+ const skillsHeaders = `${indexHref}\n Content-Type: application/json; charset=utf-8\n`
1109
+ writeFileSync(headersPath, headersWithSkills.trimEnd() + '\n\n' + skillsHeaders)
1110
+ console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for ${indexHref}`)
1111
+ }
1112
+ }
1113
+ }
1114
+
1025
1115
  console.log(`\x1b[36m[docsector]\x1b[0m Added _headers rule for .md files`)
1026
1116
 
1027
1117
  // Add homepage Link headers for agent discovery (RFC 8288 / RFC 9727)
@@ -1081,19 +1171,6 @@ export async function onRequest (context) {
1081
1171
  const apiCatalogEnabled = apiCatalogConfig.enabled !== false
1082
1172
  const apiCatalogPath = (apiCatalogConfig.path || apiCatalogHref || '/.well-known/api-catalog')
1083
1173
 
1084
- const toUrl = (href) => {
1085
- if (!href) return null
1086
- if (/^https?:\/\//i.test(href)) return href
1087
- const normalizedHref = href.startsWith('/') ? href : `/${href}`
1088
- return siteUrl ? `${siteUrl}${normalizedHref}` : normalizedHref
1089
- }
1090
-
1091
- const normalizeLocalPath = (href) => {
1092
- if (!href || /^https?:\/\//i.test(href)) return null
1093
- const path = href.startsWith('/') ? href.slice(1) : href
1094
- return path || null
1095
- }
1096
-
1097
1174
  if (apiCatalogEnabled && apiCatalogPath) {
1098
1175
  const catalogDistPath = normalizeLocalPath(apiCatalogPath)
1099
1176
 
@@ -1376,6 +1453,57 @@ function applyContentSignalsToRobots (robotsContent, { contentSignalLine, userAg
1376
1453
  return updated.join('\n').replace(/\n+$/g, '') + '\n'
1377
1454
  }
1378
1455
 
1456
+ function normalizeAgentSkillType (type, index) {
1457
+ const normalizedType = String(type || '').trim()
1458
+ if (normalizedType !== 'skill-md' && normalizedType !== 'archive') {
1459
+ throw new Error(`[docsector] agentSkills.skills[${index}].type must be "skill-md" or "archive"`)
1460
+ }
1461
+ return normalizedType
1462
+ }
1463
+
1464
+ function normalizeAgentSkillDigest (digest) {
1465
+ if (digest === null || digest === undefined || digest === '') return null
1466
+ const normalizedDigest = String(digest).trim().toLowerCase()
1467
+ if (!/^sha256:[a-f0-9]{64}$/.test(normalizedDigest)) {
1468
+ throw new Error('[docsector] agentSkills.skills[*].digest must follow "sha256:{hex}"')
1469
+ }
1470
+ return normalizedDigest
1471
+ }
1472
+
1473
+ function resolveAgentSkillArtifactPath (artifactUrl, { siteUrl, distDir }) {
1474
+ if (!artifactUrl || typeof artifactUrl !== 'string') {
1475
+ return null
1476
+ }
1477
+
1478
+ let pathname = null
1479
+
1480
+ if (/^https?:\/\//i.test(artifactUrl)) {
1481
+ if (!siteUrl) return null
1482
+
1483
+ let artifact
1484
+ let base
1485
+ try {
1486
+ artifact = new URL(artifactUrl)
1487
+ base = new URL(siteUrl)
1488
+ } catch {
1489
+ return null
1490
+ }
1491
+
1492
+ if (artifact.origin !== base.origin) {
1493
+ return null
1494
+ }
1495
+
1496
+ pathname = artifact.pathname
1497
+ } else {
1498
+ pathname = artifactUrl.startsWith('/') ? artifactUrl : `/${artifactUrl}`
1499
+ }
1500
+
1501
+ const relativePath = pathname.replace(/^\/+/, '')
1502
+ if (!relativePath) return null
1503
+
1504
+ return resolve(distDir, relativePath)
1505
+ }
1506
+
1379
1507
  /**
1380
1508
  * Create a complete Quasar configuration for a docsector-reader consumer project.
1381
1509
  *