create-mcp-use-app 0.9.4 → 0.10.0-canary.1

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 +162 -7
  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
@@ -3,24 +3,25 @@
3
3
  // src/index.tsx
4
4
  import chalk from "chalk";
5
5
  import { Command } from "commander";
6
- import { render } from "ink";
6
+ import { Box, render, Text } 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,
17
18
  writeFileSync
18
19
  } from "fs";
20
+ import { tmpdir } from "os";
19
21
  import { dirname, join, resolve } from "path";
20
22
  import { fileURLToPath } from "url";
21
23
  import ora from "ora";
22
24
  import React, { useState } from "react";
23
- import { Box, Text } from "ink";
24
25
  var __filename = fileURLToPath(import.meta.url);
25
26
  var __dirname = dirname(__filename);
26
27
  function runPackageManager(packageManager, args, cwd) {
@@ -279,8 +280,7 @@ function listTemplates() {
279
280
  }
280
281
  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
282
  "-t, --template <template>",
282
- "Template to use (starter, mcp-ui, apps-sdk)",
283
- "starter"
283
+ "Template to use (starter, mcp-ui, apps-sdk) or GitHub repo URL (owner/repo or https://github.com/owner/repo)"
284
284
  ).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
285
  async (projectName, options) => {
286
286
  try {
@@ -304,7 +304,12 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
304
304
  console.log("");
305
305
  projectName = await promptForProjectName();
306
306
  console.log("");
307
- selectedTemplate = await promptForTemplate();
307
+ if (!options.template) {
308
+ selectedTemplate = await promptForTemplate();
309
+ }
310
+ }
311
+ if (!selectedTemplate) {
312
+ selectedTemplate = "starter";
308
313
  }
309
314
  const sanitizedProjectName = projectName.trim();
310
315
  if (!sanitizedProjectName) {
@@ -479,7 +484,7 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
479
484
  );
480
485
  }
481
486
  }
482
- if (options.git) {
487
+ if (options.git && !isGitHubRepoUrl(validatedTemplate)) {
483
488
  tryGitInit(projectPath);
484
489
  }
485
490
  console.log("");
@@ -554,8 +559,127 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
554
559
  }
555
560
  }
556
561
  );
562
+ function parseGitHubRepoUrl(url) {
563
+ const trimmed = url.trim();
564
+ let match = trimmed.match(
565
+ /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:\/tree\/([^/]+))?(?:#(.+))?$/
566
+ );
567
+ if (match) {
568
+ const result = {
569
+ owner: match[1],
570
+ repo: match[2],
571
+ branch: match[3] || match[4] || void 0
572
+ };
573
+ return validateGitHubRepoInfo(result) ? result : null;
574
+ }
575
+ match = trimmed.match(
576
+ /^(?:www\.)?github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:#(.+))?$/
577
+ );
578
+ if (match) {
579
+ const result = {
580
+ owner: match[1],
581
+ repo: match[2],
582
+ branch: match[3] || void 0
583
+ };
584
+ return validateGitHubRepoInfo(result) ? result : null;
585
+ }
586
+ match = trimmed.match(/^([^/#]+)\/([^/#]+?)(?:#(.+))?$/);
587
+ if (match) {
588
+ const result = {
589
+ owner: match[1],
590
+ repo: match[2],
591
+ branch: match[3] || void 0
592
+ };
593
+ return validateGitHubRepoInfo(result) ? result : null;
594
+ }
595
+ return null;
596
+ }
597
+ function validateGitHubRepoInfo(info) {
598
+ const validIdentifier = /^[a-zA-Z0-9_-]+$/;
599
+ const validBranch = /^[a-zA-Z0-9_./-]+$/;
600
+ if (!validIdentifier.test(info.owner) || !validIdentifier.test(info.repo)) {
601
+ return false;
602
+ }
603
+ if (info.branch && !validBranch.test(info.branch)) {
604
+ return false;
605
+ }
606
+ return true;
607
+ }
608
+ function isGitHubRepoUrl(template) {
609
+ return parseGitHubRepoUrl(template) !== null;
610
+ }
611
+ async function cloneGitHubRepo(repoInfo, tempDir) {
612
+ const versionCheck = spawnSync("git", ["--version"], {
613
+ stdio: "ignore",
614
+ shell: false
615
+ });
616
+ if (versionCheck.status !== 0 || versionCheck.error) {
617
+ console.error(
618
+ chalk.red("\u274C Git is not installed or not available in PATH")
619
+ );
620
+ console.error(
621
+ chalk.yellow(" Please install Git to use GitHub repository templates")
622
+ );
623
+ process.exit(1);
624
+ }
625
+ const repoUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}.git`;
626
+ const branch = repoInfo.branch || "main";
627
+ const spinner = ora(`Cloning repository from GitHub...`).start();
628
+ const cloneWithBranch = (branchName) => {
629
+ const result = spawnSync(
630
+ "git",
631
+ ["clone", "--depth", "1", "--branch", branchName, repoUrl, tempDir],
632
+ {
633
+ stdio: "pipe",
634
+ shell: false
635
+ }
636
+ );
637
+ if (result.status !== 0) {
638
+ const errorMessage2 = result.stderr?.toString() || result.stdout?.toString() || "Unknown error";
639
+ return {
640
+ success: false,
641
+ error: new Error(errorMessage2)
642
+ };
643
+ }
644
+ return { success: true };
645
+ };
646
+ const cloneResult = cloneWithBranch(branch);
647
+ if (cloneResult.success) {
648
+ spinner.succeed("Repository cloned successfully");
649
+ return tempDir;
650
+ }
651
+ if (!repoInfo.branch) {
652
+ spinner.text = `Branch "${branch}" not found, trying "main"...`;
653
+ const mainResult = cloneWithBranch("main");
654
+ if (mainResult.success) {
655
+ spinner.succeed("Repository cloned successfully (using main branch)");
656
+ return tempDir;
657
+ }
658
+ spinner.text = `Branch "main" not found, trying "master"...`;
659
+ const masterResult = cloneWithBranch("master");
660
+ if (masterResult.success) {
661
+ spinner.succeed("Repository cloned successfully (using master branch)");
662
+ return tempDir;
663
+ }
664
+ }
665
+ spinner.fail("Failed to clone repository");
666
+ console.error(chalk.red(`\u274C Error cloning repository: ${repoUrl}`));
667
+ if (repoInfo.branch) {
668
+ console.error(chalk.yellow(` Branch "${repoInfo.branch}" may not exist`));
669
+ }
670
+ const errorMessage = cloneResult.error?.message || "Unknown error";
671
+ if (errorMessage.includes("not found")) {
672
+ console.error(chalk.yellow(` Repository may not exist or is private`));
673
+ } else {
674
+ console.error(chalk.yellow(` ${errorMessage}`));
675
+ }
676
+ throw cloneResult.error || new Error("Failed to clone repository");
677
+ }
557
678
  function validateTemplateName(template) {
558
679
  const sanitized = template.trim();
680
+ if (isGitHubRepoUrl(sanitized)) {
681
+ return sanitized;
682
+ }
559
683
  if (sanitized.includes("..") || sanitized.includes("/") || sanitized.includes("\\")) {
560
684
  console.error(chalk.red("\u274C Invalid template name"));
561
685
  console.error(
@@ -575,6 +699,31 @@ function validateTemplateName(template) {
575
699
  return sanitized;
576
700
  }
577
701
  async function copyTemplate(projectPath, template, versions, isDevelopment = false, useCanary = false) {
702
+ const repoInfo = parseGitHubRepoUrl(template);
703
+ if (repoInfo) {
704
+ const tempDir = mkdtempSync(join(tmpdir(), "create-mcp-use-app-"));
705
+ try {
706
+ await cloneGitHubRepo(repoInfo, tempDir);
707
+ copyDirectoryWithProcessing(
708
+ tempDir,
709
+ projectPath,
710
+ versions,
711
+ isDevelopment,
712
+ useCanary
713
+ );
714
+ } finally {
715
+ try {
716
+ rmSync(tempDir, { recursive: true, force: true });
717
+ } catch (error) {
718
+ if (process.env.NODE_ENV === "development") {
719
+ console.warn(
720
+ `Warning: Failed to clean up temp directory: ${tempDir}`
721
+ );
722
+ }
723
+ }
724
+ }
725
+ return;
726
+ }
578
727
  const templatePath = join(__dirname, "templates", template);
579
728
  if (!existsSync(templatePath)) {
580
729
  console.error(chalk.red(`\u274C Template "${template}" not found!`));
@@ -596,6 +745,9 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
596
745
  console.log(
597
746
  '\u{1F4A1} Tip: Use "apps-sdk" template for a MCP server with OpenAI Apps SDK integration'
598
747
  );
748
+ console.log(
749
+ '\u{1F4A1} Tip: Use a GitHub repo URL like "owner/repo" or "https://github.com/owner/repo" to use a custom template'
750
+ );
599
751
  process.exit(1);
600
752
  }
601
753
  copyDirectoryWithProcessing(
@@ -609,6 +761,9 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
609
761
  function copyDirectoryWithProcessing(src, dest, versions, isDevelopment, useCanary = false) {
610
762
  const entries = readdirSync(src, { withFileTypes: true });
611
763
  for (const entry of entries) {
764
+ if (entry.name === ".git") {
765
+ continue;
766
+ }
612
767
  const srcPath = join(src, entry.name);
613
768
  const destPath = join(dest, entry.name);
614
769
  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.1",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",