@leanmcp/cli 0.3.1 → 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/dist/index.js +118 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,7 +41,7 @@ async function scanUIApp(projectDir) {
|
|
|
41
41
|
for (const relativeFile of tsFiles) {
|
|
42
42
|
const filePath = path.join(mcpDir, relativeFile);
|
|
43
43
|
const content = await fs.readFile(filePath, "utf-8");
|
|
44
|
-
if (!content.includes("@UIApp") || !content.includes("@leanmcp/ui")) {
|
|
44
|
+
if (!content.includes("@UIApp") && !content.includes("@GPTApp") || !content.includes("@leanmcp/ui")) {
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
47
|
const uiApps = parseUIAppDecorators(content, filePath);
|
|
@@ -55,11 +55,13 @@ function parseUIAppDecorators(content, filePath) {
|
|
|
55
55
|
const classMatch = content.match(/export\s+class\s+(\w+)/);
|
|
56
56
|
const serviceName = classMatch ? classMatch[1] : "Unknown";
|
|
57
57
|
const importMap = parseImports(content, filePath);
|
|
58
|
-
const uiAppRegex = /@UIApp\s*\(\s*\{([
|
|
58
|
+
const uiAppRegex = /@(UIApp|GPTApp)\s*\(\s*\{([\s\S]+?)\}\s*\)\s*(?:async\s+)?(\w+)/g;
|
|
59
59
|
let match;
|
|
60
60
|
while ((match = uiAppRegex.exec(content)) !== null) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
61
|
+
const decoratorName = match[1];
|
|
62
|
+
const decoratorBody = match[2];
|
|
63
|
+
const methodName = match[3];
|
|
64
|
+
const isGPTApp = decoratorName === "GPTApp";
|
|
63
65
|
let componentPath;
|
|
64
66
|
let componentName;
|
|
65
67
|
const stringMatch = decoratorBody.match(/component\s*:\s*['"]([^'"]+)['"]/);
|
|
@@ -88,14 +90,32 @@ function parseUIAppDecorators(content, filePath) {
|
|
|
88
90
|
}
|
|
89
91
|
if (!componentPath) continue;
|
|
90
92
|
const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
|
|
91
|
-
|
|
93
|
+
let resourceUri = `ui://${servicePrefix}/${methodName}`;
|
|
94
|
+
const uriMatch = decoratorBody.match(/uri\s*:\s*['"]([^'"]+)['"]/);
|
|
95
|
+
if (uriMatch) {
|
|
96
|
+
resourceUri = uriMatch[1];
|
|
97
|
+
}
|
|
98
|
+
let gptOptions = void 0;
|
|
99
|
+
if (isGPTApp) {
|
|
100
|
+
gptOptions = {};
|
|
101
|
+
if (decoratorBody.includes("widgetAccessible: true")) gptOptions.widgetAccessible = true;
|
|
102
|
+
if (decoratorBody.includes("prefersBorder: true")) gptOptions.prefersBorder = true;
|
|
103
|
+
const visibilityMatch = decoratorBody.match(/visibility\s*:\s*['"](public|private)['"]/);
|
|
104
|
+
if (visibilityMatch) gptOptions.visibility = visibilityMatch[1];
|
|
105
|
+
const domainMatch = decoratorBody.match(/widgetDomain\s*:\s*['"]([^'"]+)['"]/);
|
|
106
|
+
if (domainMatch) gptOptions.widgetDomain = domainMatch[1];
|
|
107
|
+
const descriptionMatch = decoratorBody.match(/widgetDescription\s*:\s*['"]([^'"]+)['"]/);
|
|
108
|
+
if (descriptionMatch) gptOptions.widgetDescription = descriptionMatch[1];
|
|
109
|
+
}
|
|
92
110
|
results.push({
|
|
93
111
|
servicePath: filePath,
|
|
94
112
|
componentPath,
|
|
95
113
|
componentName,
|
|
96
114
|
resourceUri,
|
|
97
115
|
methodName,
|
|
98
|
-
serviceName
|
|
116
|
+
serviceName,
|
|
117
|
+
isGPTApp,
|
|
118
|
+
gptOptions
|
|
99
119
|
});
|
|
100
120
|
}
|
|
101
121
|
return results;
|
|
@@ -313,7 +333,30 @@ module.exports = {
|
|
|
313
333
|
}
|
|
314
334
|
`);
|
|
315
335
|
const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
|
|
316
|
-
|
|
336
|
+
const isGPTApp = uiApp.isGPTApp;
|
|
337
|
+
const entryContent = isGPTApp ? `
|
|
338
|
+
import React, { StrictMode } from 'react';
|
|
339
|
+
import { createRoot } from 'react-dom/client';
|
|
340
|
+
import { GPTAppProvider, Toaster } from '@leanmcp/ui';
|
|
341
|
+
import '@leanmcp/ui/styles.css';
|
|
342
|
+
import './styles.css';
|
|
343
|
+
import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
|
|
344
|
+
|
|
345
|
+
function App() {
|
|
346
|
+
return (
|
|
347
|
+
<GPTAppProvider appName="${componentName}">
|
|
348
|
+
<${componentName} />
|
|
349
|
+
<Toaster />
|
|
350
|
+
</GPTAppProvider>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
createRoot(document.getElementById('root')!).render(
|
|
355
|
+
<StrictMode>
|
|
356
|
+
<App />
|
|
357
|
+
</StrictMode>
|
|
358
|
+
);
|
|
359
|
+
` : `
|
|
317
360
|
import React, { StrictMode } from 'react';
|
|
318
361
|
import { createRoot } from 'react-dom/client';
|
|
319
362
|
import { AppProvider, Toaster } from '@leanmcp/ui';
|
|
@@ -340,7 +383,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
340
383
|
<App />
|
|
341
384
|
</StrictMode>
|
|
342
385
|
);
|
|
343
|
-
|
|
386
|
+
`;
|
|
387
|
+
await fs2.writeFile(entryJs, entryContent);
|
|
344
388
|
try {
|
|
345
389
|
const reactPath = resolveReactDependency(projectDir, "react");
|
|
346
390
|
const reactDomPath = resolveReactDependency(projectDir, "react-dom");
|
|
@@ -477,7 +521,15 @@ async function devCommand() {
|
|
|
477
521
|
for (const app of uiApps) {
|
|
478
522
|
const result = await buildUIComponent(app, cwd, true);
|
|
479
523
|
if (result.success) {
|
|
480
|
-
|
|
524
|
+
if (app.isGPTApp) {
|
|
525
|
+
manifest[app.resourceUri] = {
|
|
526
|
+
htmlPath: result.htmlPath,
|
|
527
|
+
isGPTApp: true,
|
|
528
|
+
gptMeta: app.gptOptions
|
|
529
|
+
};
|
|
530
|
+
} else {
|
|
531
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
532
|
+
}
|
|
481
533
|
} else {
|
|
482
534
|
errors.push(`${app.componentName}: ${result.error}`);
|
|
483
535
|
}
|
|
@@ -543,7 +595,15 @@ async function devCommand() {
|
|
|
543
595
|
console.log(chalk.cyan(`${action} ${app.componentName}...`));
|
|
544
596
|
const result = await buildUIComponent(app, cwd, true);
|
|
545
597
|
if (result.success) {
|
|
546
|
-
|
|
598
|
+
if (app.isGPTApp) {
|
|
599
|
+
manifest[app.resourceUri] = {
|
|
600
|
+
htmlPath: result.htmlPath,
|
|
601
|
+
isGPTApp: true,
|
|
602
|
+
gptMeta: app.gptOptions
|
|
603
|
+
};
|
|
604
|
+
} else {
|
|
605
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
606
|
+
}
|
|
547
607
|
if (await fs3.pathExists(app.componentPath)) {
|
|
548
608
|
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
549
609
|
}
|
|
@@ -556,19 +616,33 @@ async function devCommand() {
|
|
|
556
616
|
previousUIApps = currentUIApps;
|
|
557
617
|
}, 150);
|
|
558
618
|
});
|
|
619
|
+
const isWindows = process.platform === "win32";
|
|
559
620
|
let isCleaningUp = false;
|
|
560
621
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
561
622
|
if (isCleaningUp) return;
|
|
562
623
|
isCleaningUp = true;
|
|
563
624
|
console.log(chalk.gray("\nShutting down..."));
|
|
564
|
-
if (watcher)
|
|
565
|
-
|
|
625
|
+
if (watcher) {
|
|
626
|
+
watcher.close();
|
|
627
|
+
watcher = null;
|
|
628
|
+
}
|
|
629
|
+
if (!isWindows && !devServer.killed) {
|
|
630
|
+
devServer.kill("SIGTERM");
|
|
631
|
+
}
|
|
566
632
|
}, "cleanup");
|
|
567
|
-
process.
|
|
568
|
-
process.
|
|
569
|
-
devServer.on("
|
|
570
|
-
|
|
571
|
-
|
|
633
|
+
process.once("SIGINT", cleanup);
|
|
634
|
+
process.once("SIGTERM", cleanup);
|
|
635
|
+
devServer.on("error", (err) => {
|
|
636
|
+
console.error(chalk.red(`Dev server error: ${err.message}`));
|
|
637
|
+
});
|
|
638
|
+
devServer.on("exit", (code, signal) => {
|
|
639
|
+
if (watcher) {
|
|
640
|
+
watcher.close();
|
|
641
|
+
watcher = null;
|
|
642
|
+
}
|
|
643
|
+
setImmediate(() => {
|
|
644
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
645
|
+
});
|
|
572
646
|
});
|
|
573
647
|
}
|
|
574
648
|
__name(devCommand, "devCommand");
|
|
@@ -601,7 +675,11 @@ async function buildCommand() {
|
|
|
601
675
|
for (const app of uiApps) {
|
|
602
676
|
const result = await buildUIComponent(app, cwd, false);
|
|
603
677
|
if (result.success) {
|
|
604
|
-
manifest[app.resourceUri] =
|
|
678
|
+
manifest[app.resourceUri] = {
|
|
679
|
+
htmlPath: result.htmlPath,
|
|
680
|
+
isGPTApp: app.isGPTApp,
|
|
681
|
+
gptMeta: app.gptOptions
|
|
682
|
+
};
|
|
605
683
|
} else {
|
|
606
684
|
errors.push(`${app.componentName}: ${result.error}`);
|
|
607
685
|
}
|
|
@@ -676,7 +754,15 @@ async function startCommand() {
|
|
|
676
754
|
for (const app of uiApps) {
|
|
677
755
|
const result = await buildUIComponent(app, cwd, false);
|
|
678
756
|
if (result.success) {
|
|
679
|
-
|
|
757
|
+
if (app.isGPTApp) {
|
|
758
|
+
manifest[app.resourceUri] = {
|
|
759
|
+
htmlPath: result.htmlPath,
|
|
760
|
+
isGPTApp: true,
|
|
761
|
+
gptMeta: app.gptOptions
|
|
762
|
+
};
|
|
763
|
+
} else {
|
|
764
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
765
|
+
}
|
|
680
766
|
} else {
|
|
681
767
|
errors.push(`${app.componentName}: ${result.error}`);
|
|
682
768
|
}
|
|
@@ -725,17 +811,25 @@ async function startCommand() {
|
|
|
725
811
|
stdio: "inherit",
|
|
726
812
|
shell: true
|
|
727
813
|
});
|
|
814
|
+
const isWindows = process.platform === "win32";
|
|
728
815
|
let isCleaningUp = false;
|
|
729
816
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
730
817
|
if (isCleaningUp) return;
|
|
731
818
|
isCleaningUp = true;
|
|
732
819
|
console.log(chalk3.gray("\nShutting down..."));
|
|
733
|
-
server.
|
|
820
|
+
if (!isWindows && !server.killed) {
|
|
821
|
+
server.kill("SIGTERM");
|
|
822
|
+
}
|
|
734
823
|
}, "cleanup");
|
|
735
|
-
process.
|
|
736
|
-
process.
|
|
737
|
-
server.on("
|
|
738
|
-
|
|
824
|
+
process.once("SIGINT", cleanup);
|
|
825
|
+
process.once("SIGTERM", cleanup);
|
|
826
|
+
server.on("error", (err) => {
|
|
827
|
+
console.error(chalk3.red(`Server error: ${err.message}`));
|
|
828
|
+
});
|
|
829
|
+
server.on("exit", (code, signal) => {
|
|
830
|
+
setImmediate(() => {
|
|
831
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
832
|
+
});
|
|
739
833
|
});
|
|
740
834
|
}
|
|
741
835
|
__name(startCommand, "startCommand");
|