@agiflowai/scaffold-mcp 0.2.0 → 0.3.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 +54 -11
  2. package/dist/index.js +160 -36
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -154,15 +154,32 @@ Use scaffold-mcp as a standalone CLI tool for template management and scaffoldin
154
154
  #### Template Management
155
155
 
156
156
  ```bash
157
- # Initialize templates folder
157
+ # Initialize templates folder and auto-download official templates
158
158
  scaffold-mcp init
159
- scaffold-mcp init --path /path/to/templates
160
159
 
161
- # Add templates from repositories
160
+ # Initialize at custom path
161
+ scaffold-mcp init --path ./custom-templates
162
+
163
+ # Initialize without downloading templates
164
+ scaffold-mcp init --no-download
165
+
166
+ # Add templates from repositories (full or subdirectory)
162
167
  scaffold-mcp add --name my-template --url https://github.com/user/template
163
- scaffold-mcp add --name my-scaffold --url https://github.com/user/scaffold --type scaffold
168
+ scaffold-mcp add --name nextjs-custom --url https://github.com/user/repo/tree/main/templates/nextjs
164
169
  ```
165
170
 
171
+ **What `init` does:**
172
+ 1. Creates `templates/` folder in your workspace root
173
+ 2. Automatically downloads official templates from [AgiFlow/aicode-toolkit](https://github.com/AgiFlow/aicode-toolkit/tree/main/templates)
174
+ 3. Creates a README.md with usage instructions
175
+ 4. Skips templates that already exist (safe to re-run)
176
+
177
+ **What `add` does:**
178
+ 1. Parses GitHub URL to detect full repository vs subdirectory
179
+ 2. Downloads template using git clone (full repo) or sparse checkout (subdirectory)
180
+ 3. Validates template has required configuration files (scaffold.yaml)
181
+ 4. Saves template to your templates folder
182
+
166
183
  #### Boilerplate Commands
167
184
 
168
185
  ```bash
@@ -205,24 +222,50 @@ scaffold-mcp scaffold add scaffold-nextjs-page \
205
222
 
206
223
  ### 1. Initialize Templates
207
224
 
225
+ The `init` command sets up your templates folder and **automatically downloads official templates** from the AgiFlow repository:
226
+
208
227
  ```bash
209
- # Initialize templates folder in your project
228
+ # Initialize templates folder and download official templates
210
229
  scaffold-mcp init
211
230
 
212
231
  # Or specify a custom path
213
232
  scaffold-mcp init --path ./my-templates
233
+
234
+ # Skip auto-download if you want to add templates manually
235
+ scaffold-mcp init --no-download
214
236
  ```
215
237
 
216
- ### 2. Add Templates
238
+ **What gets downloaded:**
239
+ - ✅ `nextjs-15-drizzle` - Next.js 15 with App Router, TypeScript, Tailwind CSS 4, Storybook, and optional Drizzle ORM
240
+ - ✅ More templates coming soon...
217
241
 
218
- ```bash
219
- # Add a boilerplate template from a repository
220
- scaffold-mcp add --name nextjs-15 --url https://github.com/yourorg/nextjs-15-template
242
+ All templates from [github.com/AgiFlow/aicode-toolkit/templates](https://github.com/AgiFlow/aicode-toolkit/tree/main/templates) are automatically pulled into your workspace.
243
+
244
+ ### 2. Add Custom Templates
245
+
246
+ Add additional templates from GitHub repositories or subdirectories:
221
247
 
222
- # Add a scaffold template
223
- scaffold-mcp add --name react-component --url https://github.com/yourorg/react-component-scaffold --type scaffold
248
+ ```bash
249
+ # Add a template from a full repository
250
+ scaffold-mcp add --name my-template --url https://github.com/yourorg/nextjs-template
251
+
252
+ # Add a template from a repository subdirectory (NEW!)
253
+ scaffold-mcp add \
254
+ --name nextjs-15-drizzle \
255
+ --url https://github.com/AgiFlow/aicode-toolkit/tree/main/templates/nextjs-15-drizzle
256
+
257
+ # Add to a specific type folder
258
+ scaffold-mcp add \
259
+ --name react-component \
260
+ --url https://github.com/yourorg/react-component-scaffold \
261
+ --type scaffold
224
262
  ```
225
263
 
264
+ **Supported URL formats:**
265
+ - Full repository: `https://github.com/user/repo`
266
+ - Subdirectory: `https://github.com/user/repo/tree/branch/path/to/template`
267
+ - With `.git` extension: `https://github.com/user/repo.git`
268
+
226
269
  ### 3. Create a New Project
227
270
 
228
271
  ```bash
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import { ScaffoldConfigLoader } from "./ScaffoldConfigLoader-CI0T6zdG.js";
4
4
  import { TemplateService } from "./TemplateService-CnxvhRVW.js";
5
5
  import { VariableReplacementService } from "./VariableReplacementService-Bq0GDhTo.js";
6
6
  import { Command } from "commander";
7
- import { exec } from "node:child_process";
8
7
  import * as path$1 from "node:path";
9
8
  import path from "node:path";
10
- import { promisify } from "node:util";
11
9
  import * as fs$1 from "fs-extra";
12
10
  import fs from "fs-extra";
13
11
  import chalk from "chalk";
12
+ import { exec } from "node:child_process";
13
+ import { promisify } from "node:util";
14
14
  import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
15
15
  import * as yaml$1 from "js-yaml";
16
16
  import yaml from "js-yaml";
@@ -82,7 +82,8 @@ const icons = {
82
82
  download: "=�",
83
83
  upload: "=�",
84
84
  gear: "�",
85
- clipboard: "=�"
85
+ clipboard: "=�",
86
+ skip: "⏭"
86
87
  };
87
88
  /**
88
89
  * Themed message helpers
@@ -136,9 +137,92 @@ const sections = {
136
137
  };
137
138
 
138
139
  //#endregion
139
- //#region src/cli/add.ts
140
+ //#region src/utils/git.ts
140
141
  const execAsync = promisify(exec);
141
142
  /**
143
+ * Parse GitHub URL to detect if it's a subdirectory
144
+ * Supports formats:
145
+ * - https://github.com/user/repo
146
+ * - https://github.com/user/repo/tree/branch/path/to/dir
147
+ * - https://github.com/user/repo/tree/main/path/to/dir
148
+ */
149
+ function parseGitHubUrl(url) {
150
+ const treeMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/);
151
+ const blobMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/);
152
+ const rootMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
153
+ if (treeMatch || blobMatch) {
154
+ const match = treeMatch || blobMatch;
155
+ return {
156
+ owner: match[1],
157
+ repo: match[2],
158
+ repoUrl: `https://github.com/${match[1]}/${match[2]}.git`,
159
+ branch: match[3],
160
+ subdirectory: match[4],
161
+ isSubdirectory: true
162
+ };
163
+ }
164
+ if (rootMatch) return {
165
+ owner: rootMatch[1],
166
+ repo: rootMatch[2],
167
+ repoUrl: `https://github.com/${rootMatch[1]}/${rootMatch[2]}.git`,
168
+ isSubdirectory: false
169
+ };
170
+ return {
171
+ repoUrl: url,
172
+ isSubdirectory: false
173
+ };
174
+ }
175
+ /**
176
+ * Clone a subdirectory from a git repository using sparse checkout
177
+ */
178
+ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
179
+ const tempFolder = `${targetFolder}.tmp`;
180
+ try {
181
+ await execAsync(`git init "${tempFolder}"`);
182
+ await execAsync(`git -C "${tempFolder}" remote add origin ${repoUrl}`);
183
+ await execAsync(`git -C "${tempFolder}" config core.sparseCheckout true`);
184
+ const sparseCheckoutFile = path.join(tempFolder, ".git", "info", "sparse-checkout");
185
+ await fs$1.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
186
+ await execAsync(`git -C "${tempFolder}" pull --depth=1 origin ${branch}`);
187
+ const sourceDir = path.join(tempFolder, subdirectory);
188
+ if (!await fs$1.pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
189
+ await fs$1.move(sourceDir, targetFolder);
190
+ await fs$1.remove(tempFolder);
191
+ } catch (error) {
192
+ if (await fs$1.pathExists(tempFolder)) await fs$1.remove(tempFolder);
193
+ throw error;
194
+ }
195
+ }
196
+ /**
197
+ * Clone entire repository
198
+ */
199
+ async function cloneRepository(repoUrl, targetFolder) {
200
+ await execAsync(`git clone ${repoUrl} "${targetFolder}"`);
201
+ const gitFolder = path.join(targetFolder, ".git");
202
+ if (await fs$1.pathExists(gitFolder)) await fs$1.remove(gitFolder);
203
+ }
204
+ /**
205
+ * Fetch directory listing from GitHub API
206
+ */
207
+ async function fetchGitHubDirectoryContents(owner, repo, path$2, branch = "main") {
208
+ const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path$2}?ref=${branch}`;
209
+ const response = await fetch(url, { headers: {
210
+ Accept: "application/vnd.github.v3+json",
211
+ "User-Agent": "scaffold-mcp"
212
+ } });
213
+ if (!response.ok) throw new Error(`Failed to fetch directory contents: ${response.statusText}`);
214
+ const data = await response.json();
215
+ if (!Array.isArray(data)) throw new Error("Expected directory but got file");
216
+ return data.map((item) => ({
217
+ name: item.name,
218
+ type: item.type,
219
+ path: item.path
220
+ }));
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/cli/add.ts
225
+ /**
142
226
  * Add command - add a template to templates folder
143
227
  */
144
228
  const addCommand = new Command("add").description("Add a template to templates folder").requiredOption("--name <name>", "Template name").requiredOption("--url <url>", "URL of the template repository to download").option("--path <path>", "Path to templates folder", "./templates").option("--type <type>", "Template type: boilerplate or scaffold", "boilerplate").action(async (options) => {
@@ -158,10 +242,12 @@ const addCommand = new Command("add").description("Add a template to templates f
158
242
  }
159
243
  logger.info(`${icons.download} Downloading template '${templateName}' from ${templateUrl}...`);
160
244
  await fs$1.ensureDir(path.dirname(targetFolder));
245
+ const parsedUrl = parseGitHubUrl(templateUrl);
161
246
  try {
162
- await execAsync(`git clone ${templateUrl} "${targetFolder}"`);
163
- const gitFolder = path.join(targetFolder, ".git");
164
- if (await fs$1.pathExists(gitFolder)) await fs$1.remove(gitFolder);
247
+ if (parsedUrl.isSubdirectory && parsedUrl.branch && parsedUrl.subdirectory) {
248
+ logger.info(`${icons.folder} Detected subdirectory: ${parsedUrl.subdirectory} (branch: ${parsedUrl.branch})`);
249
+ await cloneSubdirectory(parsedUrl.repoUrl, parsedUrl.branch, parsedUrl.subdirectory, targetFolder);
250
+ } else await cloneRepository(parsedUrl.repoUrl, targetFolder);
165
251
  messages.success(`Template '${templateName}' added successfully!`);
166
252
  logger.header(`\n${icons.folder} Template location:`);
167
253
  logger.indent(targetFolder);
@@ -531,27 +617,60 @@ async function findWorkspaceRoot(startPath = process.cwd()) {
531
617
  currentPath = path.dirname(currentPath);
532
618
  }
533
619
  }
620
+ const DEFAULT_TEMPLATE_REPO = {
621
+ owner: "AgiFlow",
622
+ repo: "aicode-toolkit",
623
+ branch: "main",
624
+ path: "templates"
625
+ };
626
+ /**
627
+ * Download templates from GitHub repository
628
+ */
629
+ async function downloadTemplates(templatesPath) {
630
+ try {
631
+ logger.info(`${icons.download} Fetching templates from ${DEFAULT_TEMPLATE_REPO.owner}/${DEFAULT_TEMPLATE_REPO.repo}...`);
632
+ const templateDirs = (await fetchGitHubDirectoryContents(DEFAULT_TEMPLATE_REPO.owner, DEFAULT_TEMPLATE_REPO.repo, DEFAULT_TEMPLATE_REPO.path, DEFAULT_TEMPLATE_REPO.branch)).filter((item) => item.type === "dir");
633
+ if (templateDirs.length === 0) {
634
+ messages.warning("No templates found in repository");
635
+ return;
636
+ }
637
+ logger.info(`${icons.folder} Found ${templateDirs.length} template(s)`);
638
+ for (const template of templateDirs) {
639
+ const targetFolder = path.join(templatesPath, template.name);
640
+ if (await fs$1.pathExists(targetFolder)) {
641
+ logger.info(`${icons.skip} Skipping ${template.name} (already exists)`);
642
+ continue;
643
+ }
644
+ logger.info(`${icons.download} Downloading ${template.name}...`);
645
+ const repoUrl = `https://github.com/${DEFAULT_TEMPLATE_REPO.owner}/${DEFAULT_TEMPLATE_REPO.repo}.git`;
646
+ await cloneSubdirectory(repoUrl, DEFAULT_TEMPLATE_REPO.branch, template.path, targetFolder);
647
+ logger.success(`${icons.check} Downloaded ${template.name}`);
648
+ }
649
+ logger.success(`\n${icons.check} All templates downloaded successfully!`);
650
+ } catch (error) {
651
+ throw new Error(`Failed to download templates: ${error.message}`);
652
+ }
653
+ }
534
654
  /**
535
655
  * Init command - initialize templates folder
536
656
  */
537
- const initCommand = new Command("init").description("Initialize templates folder structure at workspace root").action(async () => {
657
+ const initCommand = new Command("init").description("Initialize templates folder structure at workspace root").option("--no-download", "Skip downloading templates from repository").option("--path <path>", "Custom path for templates folder (relative to workspace root)").action(async (options) => {
538
658
  try {
539
659
  const workspaceRoot = await findWorkspaceRoot();
540
- const templatesPath = path.join(workspaceRoot, "templates");
660
+ const templatesPath = options.path ? path.join(workspaceRoot, options.path) : path.join(workspaceRoot, "templates");
541
661
  logger.info(`${icons.rocket} Initializing templates folder at: ${templatesPath}`);
542
662
  await fs$1.ensureDir(templatesPath);
543
- await fs$1.ensureDir(path.join(templatesPath, "boilerplates"));
544
- await fs$1.ensureDir(path.join(templatesPath, "scaffolds"));
545
663
  await fs$1.writeFile(path.join(templatesPath, "README.md"), `# Templates
546
664
 
547
665
  This folder contains boilerplate templates and scaffolding methods for your projects.
548
666
 
549
- ## Structure
667
+ ## Templates
550
668
 
551
- - \`boilerplates/\` - Full project boilerplate templates
552
- - \`scaffolds/\` - Feature scaffolding methods for existing projects
669
+ Templates are organized by framework/technology and include configuration files (\`scaffold.yaml\`) that define:
670
+ - Boilerplates: Full project starter templates
671
+ - Features: Code scaffolding methods for adding new features to existing projects
553
672
 
554
- ## Adding Templates
673
+ ## Adding More Templates
555
674
 
556
675
  Use the \`add\` command to add templates from remote repositories:
557
676
 
@@ -559,31 +678,38 @@ Use the \`add\` command to add templates from remote repositories:
559
678
  scaffold-mcp add --name my-template --url https://github.com/user/template
560
679
  \`\`\`
561
680
 
562
- ## Creating Custom Templates
681
+ Or add templates from subdirectories:
563
682
 
564
- ### Boilerplate Template Structure
683
+ \`\`\`bash
684
+ scaffold-mcp add --name nextjs-template --url https://github.com/user/repo/tree/main/templates/nextjs
685
+ \`\`\`
565
686
 
566
- Each boilerplate should have:
567
- - \`boilerplate.yaml\` - Configuration file
568
- - Template files with variable placeholders using Liquid syntax (\`{{ variableName }}\`)
687
+ ## Creating Custom Templates
569
688
 
570
- ### Scaffold Method Structure
689
+ Each template should have a \`scaffold.yaml\` configuration file defining:
690
+ - \`boilerplate\`: Array of boilerplate configurations
691
+ - \`features\`: Array of feature scaffold configurations
571
692
 
572
- Each scaffold method should have:
573
- - \`scaffold.yaml\` - Configuration file
574
- - Template files organized by project type
693
+ Template files use Liquid syntax for variable placeholders: \`{{ variableName }}\`
575
694
 
576
- See documentation for more details on template creation.
695
+ See existing templates for examples and documentation for more details.
577
696
  `);
578
- logger.success(`${icons.check} Templates folder initialized successfully!`);
579
- logger.header(`\n${icons.folder} Created structure:`);
580
- logger.indent(`${templatesPath}/`);
581
- logger.indent(`├── boilerplates/`);
582
- logger.indent(`├── scaffolds/`);
583
- logger.indent(`└── README.md`);
584
- sections.nextSteps([`Add templates using: scaffold-mcp add --name <name> --url <url>`, `Or manually create templates in ${templatesPath}/`]);
697
+ logger.success(`${icons.check} Templates folder created!`);
698
+ if (options.download !== false) await downloadTemplates(templatesPath);
699
+ else logger.info(`${icons.skip} Skipping template download (use --download to enable)`);
700
+ logger.header(`\n${icons.folder} Templates location:`);
701
+ logger.indent(templatesPath);
702
+ const nextSteps = [];
703
+ if (options.download === false) {
704
+ nextSteps.push(`Download templates: scaffold-mcp init --download`);
705
+ nextSteps.push(`Add templates manually: scaffold-mcp add --name <name> --url <url>`);
706
+ } else {
707
+ nextSteps.push(`List available boilerplates: scaffold-mcp boilerplate list`);
708
+ nextSteps.push(`Add more templates: scaffold-mcp add --name <name> --url <url>`);
709
+ }
710
+ sections.nextSteps(nextSteps);
585
711
  } catch (error) {
586
- logger.error(`${icons.cross} Error initializing templates folder:`, error);
712
+ messages.error(`Error initializing templates folder: ${error.message}`);
587
713
  process.exit(1);
588
714
  }
589
715
  });
@@ -1759,9 +1885,7 @@ Best practices:
1759
1885
  - Use conditional includes with ?variableName=value for optional files
1760
1886
  - Use path mapping with -> when source and target paths differ
1761
1887
  - Use {{ variableName }} in target paths for dynamic file placement
1762
- - Avoid wildcards unless you have a good reason
1763
-
1764
- See templates/nextjs-15/scaffold.yaml features section for examples.`,
1888
+ - Avoid wildcards unless you have a good reason`,
1765
1889
  items: { type: "string" }
1766
1890
  },
1767
1891
  patterns: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agiflowai/scaffold-mcp",
3
3
  "description": "MCP server for scaffolding applications with boilerplate templates",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "AgiflowIO",
7
7
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "js-yaml": "4.1.0",
43
43
  "liquidjs": "10.21.1",
44
44
  "zod": "3.25.76",
45
- "@agiflowai/scaffold-generator": "0.2.0"
45
+ "@agiflowai/scaffold-generator": "0.3.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/express": "^5.0.0",