create-mcp-use-app 0.9.4 → 0.10.0-canary.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 (3) hide show
  1. package/README.md +28 -0
  2. package/dist/index.js +155 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -112,6 +112,11 @@ npx create-mcp-use-app my-project
112
112
  npx create-mcp-use-app my-project --template apps-sdk
113
113
  npx create-mcp-use-app my-project --template mcp-ui
114
114
 
115
+ # Use a GitHub repository as a template
116
+ npx create-mcp-use-app my-project --template owner/repo
117
+ npx create-mcp-use-app my-project --template https://github.com/owner/repo
118
+ npx create-mcp-use-app my-project --template owner/repo#branch-name
119
+
115
120
  # Use a specific package manager
116
121
  npx create-mcp-use-app my-project --npm
117
122
  npx create-mcp-use-app my-project --yarn
@@ -159,6 +164,29 @@ The mcp-ui template includes:
159
164
 
160
165
  Best for building MCP servers with rich interactive UI components.
161
166
 
167
+ ### GitHub Repository Templates
168
+
169
+ You can use any GitHub repository as a template by providing the repository URL:
170
+
171
+ ```bash
172
+ # Short format (owner/repo)
173
+ npx create-mcp-use-app my-project --template owner/repo
174
+
175
+ # Full URL format
176
+ npx create-mcp-use-app my-project --template https://github.com/owner/repo
177
+
178
+ # With specific branch
179
+ npx create-mcp-use-app my-project --template owner/repo#branch-name
180
+ npx create-mcp-use-app my-project --template https://github.com/owner/repo#branch-name
181
+ ```
182
+
183
+ The repository will be cloned and its contents will be used to initialize your project. This is useful for:
184
+ - Using community templates
185
+ - Sharing custom templates within your organization
186
+ - Creating projects from existing repositories
187
+
188
+ **Note:** Git must be installed and available in your PATH to use GitHub repository templates.
189
+
162
190
  ---
163
191
 
164
192
  ## 🏗️ What Gets Installed
package/dist/index.js CHANGED
@@ -6,11 +6,12 @@ import { Command } from "commander";
6
6
  import { render } from "ink";
7
7
  import SelectInput from "ink-select-input";
8
8
  import TextInput from "ink-text-input";
9
- import { execSync, spawn } from "child_process";
9
+ import { execSync, spawn, spawnSync } from "child_process";
10
10
  import {
11
11
  copyFileSync,
12
12
  existsSync,
13
13
  mkdirSync,
14
+ mkdtempSync,
14
15
  readdirSync,
15
16
  readFileSync,
16
17
  rmSync,
@@ -18,6 +19,7 @@ import {
18
19
  } from "fs";
19
20
  import { dirname, join, resolve } from "path";
20
21
  import { fileURLToPath } from "url";
22
+ import { tmpdir } from "os";
21
23
  import ora from "ora";
22
24
  import React, { useState } from "react";
23
25
  import { Box, Text } from "ink";
@@ -279,7 +281,7 @@ function listTemplates() {
279
281
  }
280
282
  program.name("create-mcp-use-app").description("Create a new MCP server project").version(packageJson.version).argument("[project-name]", "Name of the MCP server project").option(
281
283
  "-t, --template <template>",
282
- "Template to use (starter, mcp-ui, apps-sdk)",
284
+ "Template to use (starter, mcp-ui, apps-sdk) or GitHub repo URL (owner/repo or https://github.com/owner/repo)",
283
285
  "starter"
284
286
  ).option("--list-templates", "List all available templates").option("--install", "Install dependencies after creating project").option("--no-git", "Skip initializing a git repository").option("--dev", "Use workspace dependencies for development").option("--canary", "Use canary versions of packages").option("--yarn", "Use yarn as package manager").option("--npm", "Use npm as package manager").option("--pnpm", "Use pnpm as package manager").action(
285
287
  async (projectName, options) => {
@@ -479,7 +481,7 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
479
481
  );
480
482
  }
481
483
  }
482
- if (options.git) {
484
+ if (options.git && !isGitHubRepoUrl(validatedTemplate)) {
483
485
  tryGitInit(projectPath);
484
486
  }
485
487
  console.log("");
@@ -554,8 +556,127 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
554
556
  }
555
557
  }
556
558
  );
559
+ function parseGitHubRepoUrl(url) {
560
+ const trimmed = url.trim();
561
+ let match = trimmed.match(
562
+ /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:\/tree\/([^/]+))?(?:#(.+))?$/
563
+ );
564
+ if (match) {
565
+ const result = {
566
+ owner: match[1],
567
+ repo: match[2],
568
+ branch: match[3] || match[4] || void 0
569
+ };
570
+ return validateGitHubRepoInfo(result) ? result : null;
571
+ }
572
+ match = trimmed.match(
573
+ /^(?:www\.)?github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:#(.+))?$/
574
+ );
575
+ if (match) {
576
+ const result = {
577
+ owner: match[1],
578
+ repo: match[2],
579
+ branch: match[3] || void 0
580
+ };
581
+ return validateGitHubRepoInfo(result) ? result : null;
582
+ }
583
+ match = trimmed.match(/^([^/#]+)\/([^/#]+?)(?:#(.+))?$/);
584
+ if (match) {
585
+ const result = {
586
+ owner: match[1],
587
+ repo: match[2],
588
+ branch: match[3] || void 0
589
+ };
590
+ return validateGitHubRepoInfo(result) ? result : null;
591
+ }
592
+ return null;
593
+ }
594
+ function validateGitHubRepoInfo(info) {
595
+ const validIdentifier = /^[a-zA-Z0-9_-]+$/;
596
+ const validBranch = /^[a-zA-Z0-9_./-]+$/;
597
+ if (!validIdentifier.test(info.owner) || !validIdentifier.test(info.repo)) {
598
+ return false;
599
+ }
600
+ if (info.branch && !validBranch.test(info.branch)) {
601
+ return false;
602
+ }
603
+ return true;
604
+ }
605
+ function isGitHubRepoUrl(template) {
606
+ return parseGitHubRepoUrl(template) !== null;
607
+ }
608
+ async function cloneGitHubRepo(repoInfo, tempDir) {
609
+ const versionCheck = spawnSync("git", ["--version"], {
610
+ stdio: "ignore",
611
+ shell: false
612
+ });
613
+ if (versionCheck.status !== 0 || versionCheck.error) {
614
+ console.error(
615
+ chalk.red("\u274C Git is not installed or not available in PATH")
616
+ );
617
+ console.error(
618
+ chalk.yellow(" Please install Git to use GitHub repository templates")
619
+ );
620
+ process.exit(1);
621
+ }
622
+ const repoUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}.git`;
623
+ const branch = repoInfo.branch || "main";
624
+ const spinner = ora(`Cloning repository from GitHub...`).start();
625
+ const cloneWithBranch = (branchName) => {
626
+ const result = spawnSync(
627
+ "git",
628
+ ["clone", "--depth", "1", "--branch", branchName, repoUrl, tempDir],
629
+ {
630
+ stdio: "pipe",
631
+ shell: false
632
+ }
633
+ );
634
+ if (result.status !== 0) {
635
+ const errorMessage2 = result.stderr?.toString() || result.stdout?.toString() || "Unknown error";
636
+ return {
637
+ success: false,
638
+ error: new Error(errorMessage2)
639
+ };
640
+ }
641
+ return { success: true };
642
+ };
643
+ const cloneResult = cloneWithBranch(branch);
644
+ if (cloneResult.success) {
645
+ spinner.succeed("Repository cloned successfully");
646
+ return tempDir;
647
+ }
648
+ if (!repoInfo.branch) {
649
+ spinner.text = `Branch "${branch}" not found, trying "main"...`;
650
+ const mainResult = cloneWithBranch("main");
651
+ if (mainResult.success) {
652
+ spinner.succeed("Repository cloned successfully (using main branch)");
653
+ return tempDir;
654
+ }
655
+ spinner.text = `Branch "main" not found, trying "master"...`;
656
+ const masterResult = cloneWithBranch("master");
657
+ if (masterResult.success) {
658
+ spinner.succeed("Repository cloned successfully (using master branch)");
659
+ return tempDir;
660
+ }
661
+ }
662
+ spinner.fail("Failed to clone repository");
663
+ console.error(chalk.red(`\u274C Error cloning repository: ${repoUrl}`));
664
+ if (repoInfo.branch) {
665
+ console.error(chalk.yellow(` Branch "${repoInfo.branch}" may not exist`));
666
+ }
667
+ const errorMessage = cloneResult.error?.message || "Unknown error";
668
+ if (errorMessage.includes("not found")) {
669
+ console.error(chalk.yellow(` Repository may not exist or is private`));
670
+ } else {
671
+ console.error(chalk.yellow(` ${errorMessage}`));
672
+ }
673
+ throw cloneResult.error || new Error("Failed to clone repository");
674
+ }
557
675
  function validateTemplateName(template) {
558
676
  const sanitized = template.trim();
677
+ if (isGitHubRepoUrl(sanitized)) {
678
+ return sanitized;
679
+ }
559
680
  if (sanitized.includes("..") || sanitized.includes("/") || sanitized.includes("\\")) {
560
681
  console.error(chalk.red("\u274C Invalid template name"));
561
682
  console.error(
@@ -575,6 +696,31 @@ function validateTemplateName(template) {
575
696
  return sanitized;
576
697
  }
577
698
  async function copyTemplate(projectPath, template, versions, isDevelopment = false, useCanary = false) {
699
+ const repoInfo = parseGitHubRepoUrl(template);
700
+ if (repoInfo) {
701
+ const tempDir = mkdtempSync(join(tmpdir(), "create-mcp-use-app-"));
702
+ try {
703
+ await cloneGitHubRepo(repoInfo, tempDir);
704
+ copyDirectoryWithProcessing(
705
+ tempDir,
706
+ projectPath,
707
+ versions,
708
+ isDevelopment,
709
+ useCanary
710
+ );
711
+ } finally {
712
+ try {
713
+ rmSync(tempDir, { recursive: true, force: true });
714
+ } catch (error) {
715
+ if (process.env.NODE_ENV === "development") {
716
+ console.warn(
717
+ `Warning: Failed to clean up temp directory: ${tempDir}`
718
+ );
719
+ }
720
+ }
721
+ }
722
+ return;
723
+ }
578
724
  const templatePath = join(__dirname, "templates", template);
579
725
  if (!existsSync(templatePath)) {
580
726
  console.error(chalk.red(`\u274C Template "${template}" not found!`));
@@ -596,6 +742,9 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
596
742
  console.log(
597
743
  '\u{1F4A1} Tip: Use "apps-sdk" template for a MCP server with OpenAI Apps SDK integration'
598
744
  );
745
+ console.log(
746
+ '\u{1F4A1} Tip: Use a GitHub repo URL like "owner/repo" or "https://github.com/owner/repo" to use a custom template'
747
+ );
599
748
  process.exit(1);
600
749
  }
601
750
  copyDirectoryWithProcessing(
@@ -609,6 +758,9 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
609
758
  function copyDirectoryWithProcessing(src, dest, versions, isDevelopment, useCanary = false) {
610
759
  const entries = readdirSync(src, { withFileTypes: true });
611
760
  for (const entry of entries) {
761
+ if (entry.name === ".git") {
762
+ continue;
763
+ }
612
764
  const srcPath = join(src, entry.name);
613
765
  const destPath = join(dest, entry.name);
614
766
  if (entry.isDirectory()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mcp-use-app",
3
- "version": "0.9.4",
3
+ "version": "0.10.0-canary.0",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",