@pyreon/mcp 0.5.0 → 0.5.2
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 +329 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +2 -160
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +2 -169
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/project-scanner.ts +8 -213
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "MCP server for Pyreon — AI-powered framework assistance",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"prepublishOnly": "bun run build"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@pyreon/compiler": "
|
|
45
|
+
"@pyreon/compiler": "0.5.2",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
47
47
|
"zod": "^3.25.76"
|
|
48
48
|
},
|
package/src/project-scanner.ts
CHANGED
|
@@ -1,215 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Re-export the unified project scanner from @pyreon/compiler.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
component?: string | undefined
|
|
12
|
-
hasLoader: boolean
|
|
13
|
-
hasGuard: boolean
|
|
14
|
-
params: string[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ComponentInfo {
|
|
18
|
-
name: string
|
|
19
|
-
file: string
|
|
20
|
-
hasSignals: boolean
|
|
21
|
-
signalNames: string[]
|
|
22
|
-
props: string[]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface IslandInfo {
|
|
26
|
-
name: string
|
|
27
|
-
file: string
|
|
28
|
-
hydrate: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ProjectContext {
|
|
32
|
-
framework: "pyreon"
|
|
33
|
-
version: string
|
|
34
|
-
generatedAt: string
|
|
35
|
-
routes: RouteInfo[]
|
|
36
|
-
components: ComponentInfo[]
|
|
37
|
-
islands: IslandInfo[]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function generateContext(cwd: string): ProjectContext {
|
|
41
|
-
const files = collectSourceFiles(cwd)
|
|
42
|
-
const version = readVersion(cwd)
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
framework: "pyreon",
|
|
46
|
-
version,
|
|
47
|
-
generatedAt: new Date().toISOString(),
|
|
48
|
-
routes: extractRoutes(files, cwd),
|
|
49
|
-
components: extractComponents(files, cwd),
|
|
50
|
-
islands: extractIslands(files, cwd),
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function collectSourceFiles(cwd: string): string[] {
|
|
55
|
-
const results: string[] = []
|
|
56
|
-
const extensions = new Set([".tsx", ".jsx", ".ts", ".js"])
|
|
57
|
-
const ignoreDirs = new Set(["node_modules", "dist", "lib", ".pyreon", ".git", "build"])
|
|
58
|
-
|
|
59
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: simple recursive walker
|
|
60
|
-
function walk(dir: string): void {
|
|
61
|
-
let entries: fs.Dirent[]
|
|
62
|
-
try {
|
|
63
|
-
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
64
|
-
} catch {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (entry.name.startsWith(".") && entry.isDirectory()) continue
|
|
69
|
-
if (ignoreDirs.has(entry.name) && entry.isDirectory()) continue
|
|
70
|
-
const fullPath = path.join(dir, entry.name)
|
|
71
|
-
if (entry.isDirectory()) {
|
|
72
|
-
walk(fullPath)
|
|
73
|
-
} else if (entry.isFile() && extensions.has(path.extname(entry.name))) {
|
|
74
|
-
results.push(fullPath)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
walk(cwd)
|
|
80
|
-
return results
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function extractRoutes(files: string[], _cwd: string): RouteInfo[] {
|
|
84
|
-
const routes: RouteInfo[] = []
|
|
85
|
-
|
|
86
|
-
for (const file of files) {
|
|
87
|
-
let code: string
|
|
88
|
-
try {
|
|
89
|
-
code = fs.readFileSync(file, "utf-8")
|
|
90
|
-
} catch {
|
|
91
|
-
continue
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const routeArrayRe =
|
|
95
|
-
/(?:createRouter\s*\(\s*\[|(?:const|let)\s+routes\s*(?::\s*RouteRecord\[\])?\s*=\s*\[)([\s\S]*?)\]/g
|
|
96
|
-
let match: RegExpExecArray | null
|
|
97
|
-
for (match = routeArrayRe.exec(code); match; match = routeArrayRe.exec(code)) {
|
|
98
|
-
const block = match[1] ?? ""
|
|
99
|
-
const routeObjRe = /path\s*:\s*["']([^"']+)["']/g
|
|
100
|
-
let routeMatch: RegExpExecArray | null
|
|
101
|
-
for (routeMatch = routeObjRe.exec(block); routeMatch; routeMatch = routeObjRe.exec(block)) {
|
|
102
|
-
const routePath = routeMatch[1] ?? ""
|
|
103
|
-
const surroundingStart = Math.max(0, routeMatch.index - 50)
|
|
104
|
-
const surroundingEnd = Math.min(block.length, routeMatch.index + 200)
|
|
105
|
-
const surrounding = block.slice(surroundingStart, surroundingEnd)
|
|
106
|
-
|
|
107
|
-
routes.push({
|
|
108
|
-
path: routePath,
|
|
109
|
-
name: surrounding.match(/name\s*:\s*["']([^"']+)["']/)?.[1],
|
|
110
|
-
hasLoader: /loader\s*:/.test(surrounding),
|
|
111
|
-
hasGuard: /beforeEnter\s*:|beforeLeave\s*:/.test(surrounding),
|
|
112
|
-
params: extractParams(routePath),
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return routes
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function extractComponents(files: string[], cwd: string): ComponentInfo[] {
|
|
122
|
-
const components: ComponentInfo[] = []
|
|
123
|
-
|
|
124
|
-
for (const file of files) {
|
|
125
|
-
let code: string
|
|
126
|
-
try {
|
|
127
|
-
code = fs.readFileSync(file, "utf-8")
|
|
128
|
-
} catch {
|
|
129
|
-
continue
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const componentRe =
|
|
133
|
-
/(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?::\s*ComponentFn<[^>]+>\s*)?=?\s*\(?(?:\s*\{?\s*([^)]*?)\s*\}?\s*)?\)?\s*(?:=>|{)/g
|
|
134
|
-
let match: RegExpExecArray | null
|
|
135
|
-
|
|
136
|
-
for (match = componentRe.exec(code); match; match = componentRe.exec(code)) {
|
|
137
|
-
const name = match[1] ?? "Unknown"
|
|
138
|
-
const propsStr = match[2] ?? ""
|
|
139
|
-
const props = propsStr
|
|
140
|
-
.split(/[,;]/)
|
|
141
|
-
.map((p) => p.trim().replace(/[{}]/g, "").trim().split(":")[0]?.split("=")[0]?.trim() ?? "")
|
|
142
|
-
.filter((p) => p && p !== "props")
|
|
143
|
-
|
|
144
|
-
const bodyStart = match.index + match[0].length
|
|
145
|
-
const body = code.slice(bodyStart, Math.min(code.length, bodyStart + 2000))
|
|
146
|
-
const signalNames: string[] = []
|
|
147
|
-
const signalRe = /(?:const|let)\s+(\w+)\s*=\s*signal\s*[<(]/g
|
|
148
|
-
let sigMatch: RegExpExecArray | null
|
|
149
|
-
for (sigMatch = signalRe.exec(body); sigMatch; sigMatch = signalRe.exec(body)) {
|
|
150
|
-
if (sigMatch[1]) signalNames.push(sigMatch[1])
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
components.push({
|
|
154
|
-
name,
|
|
155
|
-
file: path.relative(cwd, file),
|
|
156
|
-
hasSignals: signalNames.length > 0,
|
|
157
|
-
signalNames,
|
|
158
|
-
props,
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return components
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function extractIslands(files: string[], cwd: string): IslandInfo[] {
|
|
167
|
-
const islands: IslandInfo[] = []
|
|
168
|
-
|
|
169
|
-
for (const file of files) {
|
|
170
|
-
let code: string
|
|
171
|
-
try {
|
|
172
|
-
code = fs.readFileSync(file, "utf-8")
|
|
173
|
-
} catch {
|
|
174
|
-
continue
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const islandRe =
|
|
178
|
-
/island\s*\(\s*\(\)\s*=>\s*import\(.+?\)\s*,\s*\{[^}]*name\s*:\s*["']([^"']+)["'][^}]*?(?:hydrate\s*:\s*["']([^"']+)["'])?[^}]*\}/g
|
|
179
|
-
let match: RegExpExecArray | null
|
|
180
|
-
for (match = islandRe.exec(code); match; match = islandRe.exec(code)) {
|
|
181
|
-
if (match[1]) {
|
|
182
|
-
islands.push({
|
|
183
|
-
name: match[1],
|
|
184
|
-
file: path.relative(cwd, file),
|
|
185
|
-
hydrate: match[2] ?? "load",
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return islands
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function extractParams(routePath: string): string[] {
|
|
195
|
-
const params: string[] = []
|
|
196
|
-
const paramRe = /:(\w+)\??/g
|
|
197
|
-
let match: RegExpExecArray | null
|
|
198
|
-
for (match = paramRe.exec(routePath); match; match = paramRe.exec(routePath)) {
|
|
199
|
-
if (match[1]) params.push(match[1])
|
|
200
|
-
}
|
|
201
|
-
return params
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function readVersion(cwd: string): string {
|
|
205
|
-
try {
|
|
206
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"))
|
|
207
|
-
const deps: Record<string, unknown> = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
208
|
-
for (const [name, ver] of Object.entries(deps)) {
|
|
209
|
-
if (name.startsWith("@pyreon/") && typeof ver === "string") return ver.replace(/^[\^~]/, "")
|
|
210
|
-
}
|
|
211
|
-
return (pkg.version as string) || "unknown"
|
|
212
|
-
} catch {
|
|
213
|
-
return "unknown"
|
|
214
|
-
}
|
|
215
|
-
}
|
|
4
|
+
export {
|
|
5
|
+
type ComponentInfo,
|
|
6
|
+
generateContext,
|
|
7
|
+
type IslandInfo,
|
|
8
|
+
type ProjectContext,
|
|
9
|
+
type RouteInfo,
|
|
10
|
+
} from "@pyreon/compiler"
|