@sewadah/selaras 0.3.2 → 0.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 +6 -0
- package/agent/catalog.mjs +73 -0
- package/agent/mcp.mjs +15 -0
- package/agent/server.mjs +58 -0
- package/dist/agent/catalog.json +1 -0
- package/dist/module.json +1 -1
- package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +29 -21
- package/dist/runtime/internal/ComboboxSelectBase.vue +138 -61
- package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +29 -21
- package/dist/runtime/internal/ComboboxSelectBody.d.vue.ts +1 -0
- package/dist/runtime/internal/ComboboxSelectBody.vue +2 -2
- package/dist/runtime/internal/ComboboxSelectBody.vue.d.ts +1 -0
- package/dist/runtime/theme/select.js +1 -4
- package/package.json +15 -5
- package/skills/selaras/SKILL.md +40 -0
- package/skills/selaras/agents/openai.yaml +4 -0
package/README.md
CHANGED
|
@@ -37,6 +37,12 @@ matter of overriding variables, not rebuilding the library.
|
|
|
37
37
|
wrappers for `@nuxt/content`.
|
|
38
38
|
- Components auto-import with an `S` prefix (`SButton`, `SSelect`, ...).
|
|
39
39
|
|
|
40
|
+
## Agent support
|
|
41
|
+
|
|
42
|
+
Use the [Selaras skill](skills/selaras/SKILL.md) to guide component selection and customization. The package also includes `selaras-mcp`, a local stdio MCP server with versioned documentation search, component discovery, and Vue examples. It needs no hosted service or API key.
|
|
43
|
+
|
|
44
|
+
See the [agent support guides](https://sewadahapp.github.io/selaras/overview/agent-support/overview) for skill installation, MCP client configuration, and the public `llms.txt` exports.
|
|
45
|
+
|
|
40
46
|
## Quick Setup
|
|
41
47
|
|
|
42
48
|
Install the module to your Nuxt application with one command:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
export async function loadCatalog(url = new URL('../dist/agent/catalog.json', import.meta.url)) {
|
|
4
|
+
const catalog = JSON.parse(await readFile(url, 'utf8'))
|
|
5
|
+
if (typeof catalog.version !== 'string' || !Array.isArray(catalog.documents) || !Array.isArray(catalog.examples))
|
|
6
|
+
throw new Error('Invalid Selaras documentation catalog. Reinstall the package or run agent:build in the repository.')
|
|
7
|
+
return catalog
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function documentSummary(document) {
|
|
11
|
+
return {
|
|
12
|
+
path: document.path,
|
|
13
|
+
title: document.title,
|
|
14
|
+
description: document.description,
|
|
15
|
+
category: document.category,
|
|
16
|
+
component: document.component,
|
|
17
|
+
examples: document.examples,
|
|
18
|
+
uri: `selaras://docs${document.path}`,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function findDocument(catalog, path) {
|
|
23
|
+
const normalized = `/${path.replace(/^\/+/, '').replace(/\.md$/, '')}`
|
|
24
|
+
return catalog.documents.find(document => document.path === normalized
|
|
25
|
+
|| document.component?.toLowerCase() === path.toLowerCase())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function searchDocuments(catalog, query, limit = 10) {
|
|
29
|
+
const words = query.toLowerCase().match(/[\p{L}\p{N}_-]+/gu) ?? []
|
|
30
|
+
if (!words.length)
|
|
31
|
+
return []
|
|
32
|
+
return catalog.documents.map((document) => {
|
|
33
|
+
const title = `${document.title} ${document.component ?? ''}`.toLowerCase()
|
|
34
|
+
const description = document.description.toLowerCase()
|
|
35
|
+
const content = document.content.toLowerCase()
|
|
36
|
+
const score = words.reduce((total, word) => total
|
|
37
|
+
+ (title.includes(word) ? 10 : 0)
|
|
38
|
+
+ (description.includes(word) ? 4 : 0)
|
|
39
|
+
+ (document.path.includes(word) ? 2 : 0)
|
|
40
|
+
+ (content.includes(word) ? 1 : 0), 0)
|
|
41
|
+
return { document, score }
|
|
42
|
+
}).filter(result => result.score > 0).sort((a, b) => b.score - a.score || a.document.path.localeCompare(b.document.path)).slice(0, limit).map(({ document }) => documentSummary(document))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function readSection(content, section) {
|
|
46
|
+
if (!section)
|
|
47
|
+
return content
|
|
48
|
+
const headings = []
|
|
49
|
+
let fence
|
|
50
|
+
let offset = 0
|
|
51
|
+
for (const line of content.split('\n')) {
|
|
52
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/)
|
|
53
|
+
if (fence) {
|
|
54
|
+
if (marker && marker[1][0] === fence[0] && marker[1].length >= fence.length && !line.slice(marker[0].length).trim())
|
|
55
|
+
fence = undefined
|
|
56
|
+
}
|
|
57
|
+
else if (marker) {
|
|
58
|
+
fence = marker[1]
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const heading = line.match(/^(#{1,6})[ \t]+(\S.*)$/)
|
|
62
|
+
if (heading)
|
|
63
|
+
headings.push({ level: heading[1].length, title: heading[2], offset })
|
|
64
|
+
}
|
|
65
|
+
offset += line.length + 1
|
|
66
|
+
}
|
|
67
|
+
const index = headings.findIndex(heading => heading.title.replace(/`/g, '').toLowerCase() === section.toLowerCase())
|
|
68
|
+
if (index < 0)
|
|
69
|
+
return undefined
|
|
70
|
+
const start = headings[index]
|
|
71
|
+
const end = headings.slice(index + 1).find(heading => heading.level <= start.level)
|
|
72
|
+
return content.slice(start.offset, end?.offset ?? content.length).trim()
|
|
73
|
+
}
|
package/agent/mcp.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
4
|
+
import { loadCatalog } from './catalog.mjs'
|
|
5
|
+
import { createServer } from './server.mjs'
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const server = createServer(await loadCatalog())
|
|
9
|
+
await server.connect(new StdioServerTransport())
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
main().catch((error) => {
|
|
13
|
+
console.error(`[selaras-mcp] ${error instanceof Error ? error.message : error}`)
|
|
14
|
+
process.exitCode = 1
|
|
15
|
+
})
|
package/agent/server.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { documentSummary, findDocument, readSection, searchDocuments } from './catalog.mjs'
|
|
4
|
+
|
|
5
|
+
const annotations = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
6
|
+
const text = value => ({ content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] })
|
|
7
|
+
const failure = message => ({ ...text(message), isError: true })
|
|
8
|
+
|
|
9
|
+
export function createServer(catalog) {
|
|
10
|
+
const server = new McpServer({ name: 'selaras', version: catalog.version }, {
|
|
11
|
+
instructions: 'Use the component catalog before implementing UI. Read matching API guides and Vue examples; customize existing Selaras components with props, slots, ui, and shared configuration. This offline catalog matches the installed package version.',
|
|
12
|
+
})
|
|
13
|
+
server.registerTool('list_components', {
|
|
14
|
+
description: 'Discover Selaras components and composites, their documentation paths, and available examples. Filter by category such as forms, overlays, data, or dashboard.',
|
|
15
|
+
inputSchema: { category: z.string().max(100).optional() },
|
|
16
|
+
annotations,
|
|
17
|
+
}, async ({ category }) => text(catalog.documents.filter(document => document.component
|
|
18
|
+
&& (!category || document.category.toLowerCase().includes(category.toLowerCase()))).map(documentSummary)))
|
|
19
|
+
server.registerTool('search_documentation', {
|
|
20
|
+
description: 'Search component APIs, setup, theming, icons, messages, adaptive controls, and composables. Returns matching guide paths to read next.',
|
|
21
|
+
inputSchema: { query: z.string().trim().min(1).max(200), limit: z.number().int().min(1).max(20).default(10) },
|
|
22
|
+
annotations,
|
|
23
|
+
}, async ({ query, limit }) => text(searchDocuments(catalog, query, limit)))
|
|
24
|
+
server.registerTool('get_documentation', {
|
|
25
|
+
description: 'Read a guide by its catalog path or component name (for example SSelect). Contains API tables and complete Vue source for embedded examples. Optionally read one heading section.',
|
|
26
|
+
inputSchema: { path: z.string().min(1).max(200), section: z.string().max(200).optional() },
|
|
27
|
+
annotations,
|
|
28
|
+
}, async ({ path, section }) => {
|
|
29
|
+
const document = findDocument(catalog, path)
|
|
30
|
+
if (!document)
|
|
31
|
+
return failure('Unknown documentation path. Use search_documentation or list_components to find a guide.')
|
|
32
|
+
const content = readSection(document.content, section)
|
|
33
|
+
return content === undefined
|
|
34
|
+
? failure(`Unknown section in ${document.path}. Read the full guide to see available headings.`)
|
|
35
|
+
: text(`# ${document.title}\n\nPackage version: ${catalog.version}\nDocumentation: ${document.path}\n\n${content}`)
|
|
36
|
+
})
|
|
37
|
+
server.registerTool('get_example', {
|
|
38
|
+
description: 'Read the exact Vue source of a named example from a component guide or list_components result.',
|
|
39
|
+
inputSchema: { name: z.string().min(1).max(200) },
|
|
40
|
+
annotations,
|
|
41
|
+
}, async ({ name }) => {
|
|
42
|
+
const example = catalog.examples.find(example => example.name === name)
|
|
43
|
+
return example ? text(example.source) : failure('Unknown example name. Read the examples field in list_components or a component guide.')
|
|
44
|
+
})
|
|
45
|
+
server.registerResource('catalog', 'selaras://catalog', {
|
|
46
|
+
description: 'Guide and component index for this Selaras package version.',
|
|
47
|
+
mimeType: 'application/json',
|
|
48
|
+
}, async uri => ({ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ version: catalog.version, documents: catalog.documents.map(documentSummary) }) }] }))
|
|
49
|
+
server.registerResource('documentation', new ResourceTemplate('selaras://docs/{+path}', {
|
|
50
|
+
list: async () => ({ resources: catalog.documents.map(document => ({ uri: documentSummary(document).uri, name: document.title, description: document.description, mimeType: 'text/markdown' })) }),
|
|
51
|
+
}), { mimeType: 'text/markdown', description: 'Versioned Selaras documentation with Vue examples.' }, async (uri, { path }) => {
|
|
52
|
+
const document = typeof path === 'string' ? findDocument(catalog, path) : undefined
|
|
53
|
+
if (!document)
|
|
54
|
+
throw new Error('Unknown Selaras documentation resource')
|
|
55
|
+
return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: document.content }] }
|
|
56
|
+
})
|
|
57
|
+
return server
|
|
58
|
+
}
|