@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/lib/types/index.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { ZodOptional, z } from "zod";
|
|
3
3
|
import process$1 from "node:process";
|
|
4
|
-
import { detectReactPatterns, diagnoseError, migrateReactCode } from "@pyreon/compiler";
|
|
5
|
-
import * as fs from "node:fs";
|
|
6
|
-
import * as path from "node:path";
|
|
4
|
+
import { detectReactPatterns, diagnoseError, generateContext, migrateReactCode } from "@pyreon/compiler";
|
|
7
5
|
|
|
8
6
|
//#region \0rolldown/runtime.js
|
|
9
7
|
|
|
@@ -304,10 +302,7 @@ function assignProp(target, prop, value) {
|
|
|
304
302
|
}
|
|
305
303
|
function mergeDefs(...defs) {
|
|
306
304
|
const mergedDescriptors = {};
|
|
307
|
-
for (const def of defs)
|
|
308
|
-
const descriptors = Object.getOwnPropertyDescriptors(def);
|
|
309
|
-
Object.assign(mergedDescriptors, descriptors);
|
|
310
|
-
}
|
|
305
|
+
for (const def of defs) Object.assign(mergedDescriptors, Object.getOwnPropertyDescriptors(def));
|
|
311
306
|
return Object.defineProperties({}, mergedDescriptors);
|
|
312
307
|
}
|
|
313
308
|
function esc(str) {
|
|
@@ -3208,168 +3203,6 @@ function serializeMessage(message) {
|
|
|
3208
3203
|
* This transport is only available in Node.js environments.
|
|
3209
3204
|
*/
|
|
3210
3205
|
|
|
3211
|
-
//#endregion
|
|
3212
|
-
//#region src/project-scanner.ts
|
|
3213
|
-
/**
|
|
3214
|
-
* Project scanner — extracts route, component, and island information from source files.
|
|
3215
|
-
*/
|
|
3216
|
-
function generateContext(cwd) {
|
|
3217
|
-
const files = collectSourceFiles(cwd);
|
|
3218
|
-
return {
|
|
3219
|
-
framework: "pyreon",
|
|
3220
|
-
version: readVersion(cwd),
|
|
3221
|
-
generatedAt: (/* @__PURE__ */new Date()).toISOString(),
|
|
3222
|
-
routes: extractRoutes(files, cwd),
|
|
3223
|
-
components: extractComponents(files, cwd),
|
|
3224
|
-
islands: extractIslands(files, cwd)
|
|
3225
|
-
};
|
|
3226
|
-
}
|
|
3227
|
-
function collectSourceFiles(cwd) {
|
|
3228
|
-
const results = [];
|
|
3229
|
-
const extensions = new Set([".tsx", ".jsx", ".ts", ".js"]);
|
|
3230
|
-
const ignoreDirs = new Set(["node_modules", "dist", "lib", ".pyreon", ".git", "build"]);
|
|
3231
|
-
function walk(dir) {
|
|
3232
|
-
let entries;
|
|
3233
|
-
try {
|
|
3234
|
-
entries = fs.readdirSync(dir, {
|
|
3235
|
-
withFileTypes: true
|
|
3236
|
-
});
|
|
3237
|
-
} catch {
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
for (const entry of entries) {
|
|
3241
|
-
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
3242
|
-
if (ignoreDirs.has(entry.name) && entry.isDirectory()) continue;
|
|
3243
|
-
const fullPath = path.join(dir, entry.name);
|
|
3244
|
-
if (entry.isDirectory()) walk(fullPath);else if (entry.isFile() && extensions.has(path.extname(entry.name))) results.push(fullPath);
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
walk(cwd);
|
|
3248
|
-
return results;
|
|
3249
|
-
}
|
|
3250
|
-
function extractRoutes(files, _cwd) {
|
|
3251
|
-
const routes = [];
|
|
3252
|
-
for (const file of files) {
|
|
3253
|
-
let code;
|
|
3254
|
-
try {
|
|
3255
|
-
code = fs.readFileSync(file, "utf-8");
|
|
3256
|
-
} catch {
|
|
3257
|
-
continue;
|
|
3258
|
-
}
|
|
3259
|
-
const routeArrayRe = /(?:createRouter\s*\(\s*\[|(?:const|let)\s+routes\s*(?::\s*RouteRecord\[\])?\s*=\s*\[)([\s\S]*?)\]/g;
|
|
3260
|
-
let match;
|
|
3261
|
-
for (match = routeArrayRe.exec(code); match; match = routeArrayRe.exec(code)) {
|
|
3262
|
-
const block = match[1] ?? "";
|
|
3263
|
-
const routeObjRe = /path\s*:\s*["']([^"']+)["']/g;
|
|
3264
|
-
let routeMatch;
|
|
3265
|
-
for (routeMatch = routeObjRe.exec(block); routeMatch; routeMatch = routeObjRe.exec(block)) {
|
|
3266
|
-
const routePath = routeMatch[1] ?? "";
|
|
3267
|
-
const surroundingStart = Math.max(0, routeMatch.index - 50);
|
|
3268
|
-
const surroundingEnd = Math.min(block.length, routeMatch.index + 200);
|
|
3269
|
-
const surrounding = block.slice(surroundingStart, surroundingEnd);
|
|
3270
|
-
routes.push({
|
|
3271
|
-
path: routePath,
|
|
3272
|
-
name: surrounding.match(/name\s*:\s*["']([^"']+)["']/)?.[1],
|
|
3273
|
-
hasLoader: /loader\s*:/.test(surrounding),
|
|
3274
|
-
hasGuard: /beforeEnter\s*:|beforeLeave\s*:/.test(surrounding),
|
|
3275
|
-
params: extractParams(routePath)
|
|
3276
|
-
});
|
|
3277
|
-
}
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
return routes;
|
|
3281
|
-
}
|
|
3282
|
-
function extractComponents(files, cwd) {
|
|
3283
|
-
const components = [];
|
|
3284
|
-
for (const file of files) {
|
|
3285
|
-
let code;
|
|
3286
|
-
try {
|
|
3287
|
-
code = fs.readFileSync(file, "utf-8");
|
|
3288
|
-
} catch {
|
|
3289
|
-
continue;
|
|
3290
|
-
}
|
|
3291
|
-
const componentRe = /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?::\s*ComponentFn<[^>]+>\s*)?=?\s*\(?(?:\s*\{?\s*([^)]*?)\s*\}?\s*)?\)?\s*(?:=>|{)/g;
|
|
3292
|
-
let match;
|
|
3293
|
-
for (match = componentRe.exec(code); match; match = componentRe.exec(code)) {
|
|
3294
|
-
const name = match[1] ?? "Unknown";
|
|
3295
|
-
const props = (match[2] ?? "").split(/[,;]/).map(p => p.trim().replace(/[{}]/g, "").trim().split(":")[0]?.split("=")[0]?.trim() ?? "").filter(p => p && p !== "props");
|
|
3296
|
-
const bodyStart = match.index + match[0].length;
|
|
3297
|
-
const body = code.slice(bodyStart, Math.min(code.length, bodyStart + 2e3));
|
|
3298
|
-
const signalNames = [];
|
|
3299
|
-
const signalRe = /(?:const|let)\s+(\w+)\s*=\s*signal\s*[<(]/g;
|
|
3300
|
-
let sigMatch;
|
|
3301
|
-
for (sigMatch = signalRe.exec(body); sigMatch; sigMatch = signalRe.exec(body)) if (sigMatch[1]) signalNames.push(sigMatch[1]);
|
|
3302
|
-
components.push({
|
|
3303
|
-
name,
|
|
3304
|
-
file: path.relative(cwd, file),
|
|
3305
|
-
hasSignals: signalNames.length > 0,
|
|
3306
|
-
signalNames,
|
|
3307
|
-
props
|
|
3308
|
-
});
|
|
3309
|
-
}
|
|
3310
|
-
}
|
|
3311
|
-
return components;
|
|
3312
|
-
}
|
|
3313
|
-
function extractIslands(files, cwd) {
|
|
3314
|
-
const islands = [];
|
|
3315
|
-
for (const file of files) {
|
|
3316
|
-
let code;
|
|
3317
|
-
try {
|
|
3318
|
-
code = fs.readFileSync(file, "utf-8");
|
|
3319
|
-
} catch {
|
|
3320
|
-
continue;
|
|
3321
|
-
}
|
|
3322
|
-
const islandRe = /island\s*\(\s*\(\)\s*=>\s*import\(.+?\)\s*,\s*\{[^}]*name\s*:\s*["']([^"']+)["'][^}]*?(?:hydrate\s*:\s*["']([^"']+)["'])?[^}]*\}/g;
|
|
3323
|
-
let match;
|
|
3324
|
-
for (match = islandRe.exec(code); match; match = islandRe.exec(code)) if (match[1]) islands.push({
|
|
3325
|
-
name: match[1],
|
|
3326
|
-
file: path.relative(cwd, file),
|
|
3327
|
-
hydrate: match[2] ?? "load"
|
|
3328
|
-
});
|
|
3329
|
-
}
|
|
3330
|
-
return islands;
|
|
3331
|
-
}
|
|
3332
|
-
function extractParams(routePath) {
|
|
3333
|
-
const params = [];
|
|
3334
|
-
const paramRe = /:(\w+)\??/g;
|
|
3335
|
-
let match;
|
|
3336
|
-
for (match = paramRe.exec(routePath); match; match = paramRe.exec(routePath)) if (match[1]) params.push(match[1]);
|
|
3337
|
-
return params;
|
|
3338
|
-
}
|
|
3339
|
-
function readVersion(cwd) {
|
|
3340
|
-
try {
|
|
3341
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"));
|
|
3342
|
-
const deps = {
|
|
3343
|
-
...pkg.dependencies,
|
|
3344
|
-
...pkg.devDependencies
|
|
3345
|
-
};
|
|
3346
|
-
for (const [name, ver] of Object.entries(deps)) if (name.startsWith("@pyreon/") && typeof ver === "string") return ver.replace(/^[\^~]/, "");
|
|
3347
|
-
return pkg.version || "unknown";
|
|
3348
|
-
} catch {
|
|
3349
|
-
return "unknown";
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
|
|
3353
|
-
//#endregion
|
|
3354
|
-
//#region src/index.ts
|
|
3355
|
-
/**
|
|
3356
|
-
* @pyreon/mcp — Model Context Protocol server for Pyreon
|
|
3357
|
-
*
|
|
3358
|
-
* Exposes tools that AI coding assistants (Claude Code, Cursor, etc.) can use
|
|
3359
|
-
* to generate, validate, and migrate Pyreon code.
|
|
3360
|
-
*
|
|
3361
|
-
* Tools:
|
|
3362
|
-
* get_api — Look up any Pyreon API: signature, usage, common mistakes
|
|
3363
|
-
* validate — Check a code snippet for Pyreon anti-patterns
|
|
3364
|
-
* migrate_react — Convert React code to idiomatic Pyreon
|
|
3365
|
-
* diagnose — Parse an error message into structured fix information
|
|
3366
|
-
* get_routes — List all routes in the current project
|
|
3367
|
-
* get_components — List all components with their props and signals
|
|
3368
|
-
*
|
|
3369
|
-
* Usage:
|
|
3370
|
-
* bunx @pyreon/mcp # stdio transport (for IDE integration)
|
|
3371
|
-
*/
|
|
3372
|
-
|
|
3373
3206
|
function getContext() {
|
|
3374
3207
|
if (!cachedContext || contextCwd !== process.cwd()) {
|
|
3375
3208
|
contextCwd = process.cwd();
|