@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.
Files changed (2) hide show
  1. package/dist/index.js +118 -24
  2. 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*\{([^}]+)\}\s*\)\s*(?:async\s+)?(\w+)/g;
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 decoratorBody = match[1];
62
- const methodName = match[2];
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
- const resourceUri = `ui://${servicePrefix}/${methodName}`;
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
- await fs2.writeFile(entryJs, `
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
- manifest[app.resourceUri] = result.htmlPath;
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
- manifest[app.resourceUri] = result.htmlPath;
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) watcher.close();
565
- devServer.kill("SIGTERM");
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.on("SIGINT", cleanup);
568
- process.on("SIGTERM", cleanup);
569
- devServer.on("exit", (code) => {
570
- if (watcher) watcher.close();
571
- process.exit(code ?? 0);
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] = result.htmlPath;
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
- manifest[app.resourceUri] = result.htmlPath;
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.kill("SIGTERM");
820
+ if (!isWindows && !server.killed) {
821
+ server.kill("SIGTERM");
822
+ }
734
823
  }, "cleanup");
735
- process.on("SIGINT", cleanup);
736
- process.on("SIGTERM", cleanup);
737
- server.on("exit", (code) => {
738
- process.exit(code ?? 0);
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
6
  "leanmcp": "bin/leanmcp.js"