@agiflowai/scaffold-mcp 1.1.0 → 1.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.
package/README.md CHANGED
@@ -121,6 +121,25 @@ npx @agiflowai/scaffold-mcp scaffold list ./apps/my-app
121
121
  npx @agiflowai/scaffold-mcp scaffold add scaffold-nextjs-page \
122
122
  --project ./apps/my-app \
123
123
  --vars '{"pageTitle":"About","nextjsPagePath":"/about"}'
124
+
125
+ # Create template authoring entries (CLI equivalents for admin MCP tools)
126
+ npx @agiflowai/scaffold-mcp boilerplate generate scaffold-vite-app \
127
+ --template vite-react \
128
+ --description "React Vite starter" \
129
+ --target-folder apps \
130
+ --variables '[{"name":"appName","description":"Application name","type":"string","required":true}]'
131
+
132
+ npx @agiflowai/scaffold-mcp scaffold generate scaffold-service \
133
+ --template typescript-lib \
134
+ --description "Generate a service class and unit test" \
135
+ --variables '[{"name":"serviceName","description":"Service name","type":"string","required":true}]'
136
+
137
+ npx @agiflowai/scaffold-mcp template file create src/index.ts \
138
+ --template typescript-lib \
139
+ --content 'export const {{ name | camelCase }} = "{{ name }}";'
140
+
141
+ npx @agiflowai/scaffold-mcp file write .env.local \
142
+ --content 'NEXT_PUBLIC_API_URL=https://api.example.com'
124
143
  ```
125
144
 
126
145
  ---
@@ -284,6 +303,20 @@ Hooks let scaffold-mcp proactively suggest templates when your AI agent creates
284
303
 
285
304
  When Claude tries to write a new file, the hook shows available scaffolding methods that match, so Claude can use templates instead of writing from scratch.
286
305
 
306
+ Hooks are also available for **Gemini CLI** and **OpenAI Codex CLI** (`--type codex.preToolUse`, configured in `.codex/config.toml`); see the hooks docs for setup.
307
+
308
+ **Relaxing enforcement:** to let some files (docs, content, generated code) be written directly, add exclude globs — workspace-wide via `scaffold-mcp.hook.excludeGlobs` in `.toolkit/settings.yaml`, or per-template via a top-level `exclude` in `scaffold.yaml`:
309
+
310
+ ```yaml
311
+ # .toolkit/settings.yaml
312
+ scaffold-mcp:
313
+ hook:
314
+ excludeGlobs:
315
+ - '**/*.md'
316
+ - '**/*.mdx'
317
+ - '**/src/content/**'
318
+ ```
319
+
287
320
  See [Hooks Documentation](./docs/hooks.md) for details.
288
321
 
289
322
  ---
@@ -174,6 +174,7 @@ const ScaffoldConfigEntrySchema = z.object({
174
174
  patterns: z.array(z.string()).optional()
175
175
  });
176
176
  const ScaffoldYamlSchema = z.object({
177
+ exclude: z.array(z.string()).optional(),
177
178
  boilerplate: z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]).optional(),
178
179
  features: z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]).optional()
179
180
  }).catchall(z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]));
@@ -761,6 +762,7 @@ var ScaffoldingMethodsService = class {
761
762
  if (!await this.fileSystem.pathExists(scaffoldYamlPath)) throw new Error(`scaffold.yaml not found at ${scaffoldYamlPath}`);
762
763
  const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
763
764
  const architectConfig = yaml.load(scaffoldContent);
765
+ const excludeGlobs = Array.isArray(architectConfig.exclude) ? architectConfig.exclude : [];
764
766
  const methods = [];
765
767
  if (architectConfig.features && Array.isArray(architectConfig.features)) architectConfig.features.forEach((feature) => {
766
768
  const featureName = feature.name || `scaffold-${templateName}`;
@@ -779,16 +781,18 @@ var ScaffoldingMethodsService = class {
779
781
  });
780
782
  return {
781
783
  templatePath,
782
- methods
784
+ methods,
785
+ excludeGlobs
783
786
  };
784
787
  }
785
788
  async listScaffoldingMethodsByTemplate(templateName, cursor) {
786
- const { templatePath, methods } = await this.collectAllMethodsByTemplate(templateName);
789
+ const { templatePath, methods, excludeGlobs } = await this.collectAllMethodsByTemplate(templateName);
787
790
  const paginatedResult = PaginationHelper.paginate(methods, cursor);
788
791
  return {
789
792
  sourceTemplate: templateName,
790
793
  templatePath,
791
794
  methods: paginatedResult.items,
795
+ excludeGlobs,
792
796
  nextCursor: paginatedResult.nextCursor,
793
797
  _meta: paginatedResult._meta
794
798
  };
@@ -198,6 +198,7 @@ const ScaffoldConfigEntrySchema = zod.z.object({
198
198
  patterns: zod.z.array(zod.z.string()).optional()
199
199
  });
200
200
  const ScaffoldYamlSchema = zod.z.object({
201
+ exclude: zod.z.array(zod.z.string()).optional(),
201
202
  boilerplate: zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]).optional(),
202
203
  features: zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]).optional()
203
204
  }).catchall(zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]));
@@ -785,6 +786,7 @@ var ScaffoldingMethodsService = class {
785
786
  if (!await this.fileSystem.pathExists(scaffoldYamlPath)) throw new Error(`scaffold.yaml not found at ${scaffoldYamlPath}`);
786
787
  const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
787
788
  const architectConfig = js_yaml.default.load(scaffoldContent);
789
+ const excludeGlobs = Array.isArray(architectConfig.exclude) ? architectConfig.exclude : [];
788
790
  const methods = [];
789
791
  if (architectConfig.features && Array.isArray(architectConfig.features)) architectConfig.features.forEach((feature) => {
790
792
  const featureName = feature.name || `scaffold-${templateName}`;
@@ -803,16 +805,18 @@ var ScaffoldingMethodsService = class {
803
805
  });
804
806
  return {
805
807
  templatePath,
806
- methods
808
+ methods,
809
+ excludeGlobs
807
810
  };
808
811
  }
809
812
  async listScaffoldingMethodsByTemplate(templateName, cursor) {
810
- const { templatePath, methods } = await this.collectAllMethodsByTemplate(templateName);
813
+ const { templatePath, methods, excludeGlobs } = await this.collectAllMethodsByTemplate(templateName);
811
814
  const paginatedResult = PaginationHelper.paginate(methods, cursor);
812
815
  return {
813
816
  sourceTemplate: templateName,
814
817
  templatePath,
815
818
  methods: paginatedResult.items,
819
+ excludeGlobs,
816
820
  nextCursor: paginatedResult.nextCursor,
817
821
  _meta: paginatedResult._meta
818
822
  };
@@ -1,6 +1,6 @@
1
- import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-DxdrPlcb.mjs";
2
- import "./tools-Cl06aoBi.mjs";
3
- import { n as resolveNewFileWriteTarget, t as formatScaffoldMethodsHookMessage } from "./shared-BVYIN3Is.mjs";
1
+ import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
2
+ import "./tools-BR1vQd_Y.mjs";
3
+ import { n as getGlobalExcludeGlobs, o as resolveNewFileWriteTarget, r as matchesExcludeGlob, t as formatScaffoldMethodsHookMessage } from "./shared-BJjclypj.mjs";
4
4
  import { ProjectFinderService, TemplatesManagerService } from "@agiflowai/aicode-utils";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
@@ -152,6 +152,7 @@ var PhantomCodeCheckHook = class {
152
152
  function isScaffoldMethodsResponse(value) {
153
153
  if (typeof value !== "object" || value === null) return false;
154
154
  if ("methods" in value && !Array.isArray(value.methods)) return false;
155
+ if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
155
156
  if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
156
157
  return true;
157
158
  }
@@ -189,6 +190,10 @@ var UseScaffoldMethodHook = class {
189
190
  decision: DECISION_SKIP,
190
191
  message: "Not a new file write operation"
191
192
  };
193
+ if (matchesExcludeGlob(absoluteFilePath, await getGlobalExcludeGlobs(context.cwd))) return {
194
+ decision: DECISION_ALLOW,
195
+ message: "File matches configured excludeGlobs - writing directly."
196
+ };
192
197
  const executionLog = new ExecutionLogService(context.session_id);
193
198
  if (await executionLog.hasExecuted({
194
199
  filePath,
@@ -225,6 +230,10 @@ var UseScaffoldMethodHook = class {
225
230
  message: "⚠️ Unexpected response shape from scaffolding methods tool"
226
231
  };
227
232
  const data = parsed;
233
+ if (matchesExcludeGlob(absoluteFilePath, data.excludeGlobs)) return {
234
+ decision: DECISION_ALLOW,
235
+ message: "File matches template exclude globs - writing directly."
236
+ };
228
237
  if (!data.methods || data.methods.length === 0) {
229
238
  await executionLog.logExecution({
230
239
  filePath,
@@ -1,6 +1,6 @@
1
- const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-CYZtpU-W.cjs");
2
- require("./tools-CVSZSirE.cjs");
3
- const require_shared = require("./shared-QxPXh-L-.cjs");
1
+ const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
2
+ require("./tools-CV6GWs6f.cjs");
3
+ const require_shared = require("./shared-B0H-6AUG.cjs");
4
4
  let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
5
5
  let node_path = require("node:path");
6
6
  node_path = require_ListScaffoldingMethodsTool.__toESM(node_path, 1);
@@ -155,6 +155,7 @@ var PhantomCodeCheckHook = class {
155
155
  function isScaffoldMethodsResponse(value) {
156
156
  if (typeof value !== "object" || value === null) return false;
157
157
  if ("methods" in value && !Array.isArray(value.methods)) return false;
158
+ if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
158
159
  if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
159
160
  return true;
160
161
  }
@@ -192,6 +193,10 @@ var UseScaffoldMethodHook = class {
192
193
  decision: _agiflowai_hooks_adapter.DECISION_SKIP,
193
194
  message: "Not a new file write operation"
194
195
  };
196
+ if (require_shared.matchesExcludeGlob(absoluteFilePath, await require_shared.getGlobalExcludeGlobs(context.cwd))) return {
197
+ decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
198
+ message: "File matches configured excludeGlobs - writing directly."
199
+ };
195
200
  const executionLog = new _agiflowai_hooks_adapter.ExecutionLogService(context.session_id);
196
201
  if (await executionLog.hasExecuted({
197
202
  filePath,
@@ -228,6 +233,10 @@ var UseScaffoldMethodHook = class {
228
233
  message: "⚠️ Unexpected response shape from scaffolding methods tool"
229
234
  };
230
235
  const data = parsed;
236
+ if (require_shared.matchesExcludeGlob(absoluteFilePath, data.excludeGlobs)) return {
237
+ decision: _agiflowai_hooks_adapter.DECISION_ALLOW,
238
+ message: "File matches template exclude globs - writing directly."
239
+ };
231
240
  if (!data.methods || data.methods.length === 0) {
232
241
  await executionLog.logExecution({
233
242
  filePath,
package/dist/cli.cjs CHANGED
@@ -1,13 +1,54 @@
1
1
  #!/usr/bin/env node
2
- const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-CYZtpU-W.cjs");
3
- const require_src = require("./src-oF_UdSBu.cjs");
4
- const require_tools = require("./tools-CVSZSirE.cjs");
2
+ const require_ListScaffoldingMethodsTool = require("./ListScaffoldingMethodsTool-rX9Hxn2d.cjs");
3
+ const require_src = require("./src-CRT1Er93.cjs");
4
+ const require_tools = require("./tools-CV6GWs6f.cjs");
5
5
  let _agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
6
6
  let node_path = require("node:path");
7
7
  node_path = require_ListScaffoldingMethodsTool.__toESM(node_path, 1);
8
8
  let commander = require("commander");
9
9
  let _agiflowai_coding_agent_bridge = require("@agiflowai/coding-agent-bridge");
10
10
  let _agiflowai_hooks_adapter = require("@agiflowai/hooks-adapter");
11
+ //#region src/commands/utils.ts
12
+ function parseJsonOption(value, flagName) {
13
+ if (!value) throw new Error(`${flagName} is required`);
14
+ try {
15
+ return JSON.parse(value);
16
+ } catch (error) {
17
+ throw new Error(`Invalid JSON for ${flagName}: ${error instanceof Error ? error.message : String(error)}`);
18
+ }
19
+ }
20
+ async function resolveTemplatesPath() {
21
+ const templatesDir = await _agiflowai_aicode_utils.TemplatesManagerService.findTemplatesPath();
22
+ if (!templatesDir) throw new Error("Templates folder not found. Create a templates folder or specify templatesPath in toolkit.yaml");
23
+ return templatesDir;
24
+ }
25
+ async function detectMonolithMode() {
26
+ try {
27
+ return (await _agiflowai_aicode_utils.ProjectConfigResolver.resolveProjectConfig(process.cwd())).type === "monolith";
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ async function loadTextOption(config) {
33
+ const { value, filePath, valueFlag, fileFlag, required = false } = config;
34
+ if (value !== void 0 && filePath !== void 0) throw new Error(`Use only one of ${valueFlag} or ${fileFlag}`);
35
+ if (filePath !== void 0) return (0, _agiflowai_aicode_utils.readFile)(filePath, "utf-8");
36
+ if (value !== void 0) return value;
37
+ if (required) throw new Error(`${valueFlag} or ${fileFlag} is required`);
38
+ }
39
+ function collectOption(value, previous = []) {
40
+ return [...previous, value];
41
+ }
42
+ function firstTextContent(result) {
43
+ const firstContent = result.content[0];
44
+ return firstContent?.type === "text" ? firstContent.text : "";
45
+ }
46
+ function assertToolSuccess(result) {
47
+ const text = firstTextContent(result);
48
+ if (result.isError) throw new Error(text || "Tool execution failed");
49
+ return text;
50
+ }
51
+ //#endregion
11
52
  //#region src/commands/boilerplate.ts
12
53
  /**
13
54
  * Boilerplate CLI command
@@ -44,7 +85,7 @@ boilerplateCommand.command("list").description("List all available boilerplate t
44
85
  process.exit(1);
45
86
  }
46
87
  });
47
- boilerplateCommand.command("create <boilerplateName>").description("Create a new project from a boilerplate template").option("-v, --vars <json>", "JSON string containing variables for the boilerplate").option("-m, --monolith", "Create as monolith project at workspace root with toolkit.yaml (default: false, creates as monorepo with project.json)").option("-t, --target-folder <path>", "Override target folder (defaults to boilerplate targetFolder for monorepo, workspace root for monolith)").option("--verbose", "Enable verbose logging").action(async (boilerplateName, options) => {
88
+ boilerplateCommand.command("create <boilerplateName>").description("Create a new project from a boilerplate template").option("-v, --vars <json>", "JSON string containing variables for the boilerplate").option("-m, --monolith", "Create as monolith project at workspace root with toolkit.yaml (default: false, creates as monorepo with project.json)").option("-t, --target-folder <path>", "Override target folder (defaults to boilerplate targetFolder for monorepo, workspace root for monolith)").option("--marker <tag>", "Custom scaffold marker tag to inject into generated code files").option("--verbose", "Enable verbose logging").action(async (boilerplateName, options) => {
48
89
  try {
49
90
  const templatesDir = await _agiflowai_aicode_utils.TemplatesManagerService.findTemplatesPath();
50
91
  if (!templatesDir) {
@@ -94,7 +135,8 @@ boilerplateCommand.command("create <boilerplateName>").description("Create a new
94
135
  boilerplateName,
95
136
  variables,
96
137
  monolith: options.monolith,
97
- targetFolderOverride: options.targetFolder
138
+ targetFolderOverride: options.targetFolder,
139
+ marker: options.marker
98
140
  });
99
141
  if (result.success) {
100
142
  _agiflowai_aicode_utils.messages.success("Project created successfully!");
@@ -121,6 +163,42 @@ boilerplateCommand.command("create <boilerplateName>").description("Create a new
121
163
  process.exit(1);
122
164
  }
123
165
  });
166
+ boilerplateCommand.command("generate <boilerplateName>").description("Create a new boilerplate configuration in a template's scaffold.yaml").option("-t, --template <name>", "Template name (optional in monolith mode)").option("--description <text>", "Boilerplate description").option("--description-file <path>", "Read boilerplate description from a file").option("--instruction <text>", "Detailed boilerplate instructions").option("--instruction-file <path>", "Read detailed boilerplate instructions from a file").option("--target-folder <path>", "Target folder for generated projects").option("--variables <json>", "JSON array of variable definitions: [{\"name\":\"appName\",\"description\":\"App name\",\"type\":\"string\",\"required\":true}]").option("-i, --include <path>", "Template include path (repeatable)", collectOption, []).action(async (boilerplateName, options) => {
167
+ try {
168
+ const templatesPath = await resolveTemplatesPath();
169
+ const isMonolith = await detectMonolithMode();
170
+ const description = await loadTextOption({
171
+ value: options.description,
172
+ filePath: options.descriptionFile,
173
+ valueFlag: "--description",
174
+ fileFlag: "--description-file",
175
+ required: true
176
+ });
177
+ const instruction = await loadTextOption({
178
+ value: options.instruction,
179
+ filePath: options.instructionFile,
180
+ valueFlag: "--instruction",
181
+ fileFlag: "--instruction-file"
182
+ });
183
+ const variables = parseJsonOption(options.variables, "--variables");
184
+ if (!Array.isArray(variables)) throw new Error("--variables must be a JSON array");
185
+ const targetFolder = options.targetFolder ?? (isMonolith ? "." : void 0);
186
+ if (!targetFolder) throw new Error("--target-folder is required outside monolith mode");
187
+ const result = await new require_tools.GenerateBoilerplateTool(templatesPath, isMonolith).execute({
188
+ templateName: options.template,
189
+ boilerplateName,
190
+ description: description ?? "",
191
+ instruction,
192
+ targetFolder,
193
+ variables,
194
+ includes: options.include ?? []
195
+ });
196
+ _agiflowai_aicode_utils.print.info(assertToolSuccess(result));
197
+ } catch (error) {
198
+ _agiflowai_aicode_utils.print.error("Error generating boilerplate:", error instanceof Error ? error.message : String(error));
199
+ process.exit(1);
200
+ }
201
+ });
124
202
  boilerplateCommand.command("info <boilerplateName>").description("Show detailed information about a boilerplate template").action(async (boilerplateName) => {
125
203
  try {
126
204
  const templatesDir = await _agiflowai_aicode_utils.TemplatesManagerService.findTemplatesPath();
@@ -146,6 +224,28 @@ boilerplateCommand.command("info <boilerplateName>").description("Show detailed
146
224
  }
147
225
  });
148
226
  //#endregion
227
+ //#region src/commands/file.ts
228
+ const fileCommand$1 = new commander.Command("file").description("Workspace file utilities");
229
+ fileCommand$1.command("write <filePath>").description("Write content to a file inside the current workspace").option("--content <text>", "Content to write").option("--content-file <path>", "Read content to write from a file").action(async (filePath, options) => {
230
+ try {
231
+ const content = await loadTextOption({
232
+ value: options.content,
233
+ filePath: options.contentFile,
234
+ valueFlag: "--content",
235
+ fileFlag: "--content-file",
236
+ required: true
237
+ });
238
+ const result = await new require_tools.WriteToFileTool().execute({
239
+ file_path: filePath,
240
+ content
241
+ });
242
+ _agiflowai_aicode_utils.print.info(assertToolSuccess(result));
243
+ } catch (error) {
244
+ _agiflowai_aicode_utils.print.error("Error writing file:", error instanceof Error ? error.message : String(error));
245
+ process.exit(1);
246
+ }
247
+ });
248
+ //#endregion
149
249
  //#region src/commands/mcp-serve.ts
150
250
  /**
151
251
  * MCP Serve Command
@@ -299,13 +399,6 @@ const mcpServeCommand = new commander.Command("mcp-serve").description("Start MC
299
399
  const scaffoldCommand = new commander.Command("scaffold").description("Add features to existing projects");
300
400
  scaffoldCommand.command("list [projectPath]").description("List available scaffolding methods for a project or template").option("-t, --template <name>", "Template name (e.g., nextjs-15, typescript-mcp-package)").option("-c, --cursor <cursor>", "Pagination cursor for next page").action(async (projectPath, options) => {
301
401
  try {
302
- if (!projectPath && !options.template) {
303
- _agiflowai_aicode_utils.messages.error("Either projectPath or --template option must be provided");
304
- _agiflowai_aicode_utils.messages.hint("Examples:");
305
- _agiflowai_aicode_utils.print.debug(" scaffold-mcp scaffold list ./my-project");
306
- _agiflowai_aicode_utils.print.debug(" scaffold-mcp scaffold list --template nextjs-15");
307
- process.exit(1);
308
- }
309
402
  const templatesDir = await _agiflowai_aicode_utils.TemplatesManagerService.findTemplatesPath();
310
403
  if (!templatesDir) {
311
404
  _agiflowai_aicode_utils.messages.error("Templates folder not found. Create a templates folder or specify templatesPath in toolkit.yaml");
@@ -314,15 +407,16 @@ scaffoldCommand.command("list [projectPath]").description("List available scaffo
314
407
  const scaffoldingMethodsService = new require_ListScaffoldingMethodsTool.ScaffoldingMethodsService(new require_ListScaffoldingMethodsTool.FileSystemService(), templatesDir);
315
408
  let result;
316
409
  let displayName;
317
- if (projectPath) {
318
- const absolutePath = node_path.default.resolve(projectPath);
410
+ if (projectPath || !options.template) {
411
+ const inputProjectPath = projectPath ?? process.cwd();
412
+ const absolutePath = node_path.default.resolve(inputProjectPath);
319
413
  if (!await _agiflowai_aicode_utils.ProjectConfigResolver.hasConfiguration(absolutePath)) {
320
414
  _agiflowai_aicode_utils.messages.error(`No project configuration found in ${absolutePath}`);
321
415
  _agiflowai_aicode_utils.messages.hint("For monorepo: ensure project.json exists with sourceTemplate field\nFor monolith: ensure toolkit.yaml exists at workspace root\nOr use --template option to list methods for a specific template");
322
416
  process.exit(1);
323
417
  }
324
418
  result = await scaffoldingMethodsService.listScaffoldingMethods(absolutePath, options.cursor);
325
- displayName = projectPath;
419
+ displayName = projectPath ?? absolutePath;
326
420
  } else {
327
421
  result = await scaffoldingMethodsService.listScaffoldingMethodsByTemplate(options.template, options.cursor);
328
422
  displayName = `template: ${options.template}`;
@@ -351,7 +445,7 @@ scaffoldCommand.command("list [projectPath]").description("List available scaffo
351
445
  process.exit(1);
352
446
  }
353
447
  });
354
- scaffoldCommand.command("add <featureName>").description("Add a feature to an existing project").option("-p, --project <path>", "Project path", process.cwd()).option("-v, --vars <json>", "JSON string containing variables for the feature").option("--verbose", "Enable verbose logging").action(async (featureName, options) => {
448
+ scaffoldCommand.command("add <featureName>").description("Add a feature to an existing project").option("-p, --project <path>", "Project path", process.cwd()).option("-v, --vars <json>", "JSON string containing variables for the feature").option("--marker <tag>", "Custom scaffold marker tag to inject into generated code files").option("--verbose", "Enable verbose logging").action(async (featureName, options) => {
355
449
  try {
356
450
  const projectPath = node_path.default.resolve(options.project);
357
451
  if (!await _agiflowai_aicode_utils.ProjectConfigResolver.hasConfiguration(projectPath)) {
@@ -408,9 +502,17 @@ scaffoldCommand.command("add <featureName>").description("Add a feature to an ex
408
502
  const result = await scaffoldingMethodsService.useScaffoldMethod({
409
503
  projectPath,
410
504
  scaffold_feature_name: featureName,
411
- variables
505
+ variables,
506
+ marker: options.marker
412
507
  });
413
508
  if (result.success) {
509
+ const scaffoldId = (0, _agiflowai_aicode_utils.generateStableId)(6);
510
+ if (result.createdFiles && result.createdFiles.length > 0) await require_tools.writePendingScaffoldLog({
511
+ scaffoldId,
512
+ projectPath,
513
+ featureName,
514
+ generatedFiles: result.createdFiles
515
+ });
414
516
  _agiflowai_aicode_utils.messages.success("✅ Feature added successfully!");
415
517
  if (result.createdFiles && result.createdFiles.length > 0) {
416
518
  _agiflowai_aicode_utils.print.header("\n📁 Created files:");
@@ -424,6 +526,7 @@ scaffoldCommand.command("add <featureName>").description("Add a feature to an ex
424
526
  _agiflowai_aicode_utils.print.debug(` - ${warning}`);
425
527
  });
426
528
  }
529
+ _agiflowai_aicode_utils.print.debug(`\nSCAFFOLD_ID:${scaffoldId}`);
427
530
  _agiflowai_aicode_utils.print.header("\n📋 Next steps:");
428
531
  _agiflowai_aicode_utils.print.debug(" - Review the generated files");
429
532
  _agiflowai_aicode_utils.print.debug(" - Update imports if necessary");
@@ -437,6 +540,40 @@ scaffoldCommand.command("add <featureName>").description("Add a feature to an ex
437
540
  process.exit(1);
438
541
  }
439
542
  });
543
+ scaffoldCommand.command("generate <featureName>").description("Create a new feature scaffold configuration in a template's scaffold.yaml").option("-t, --template <name>", "Template name (optional in monolith mode)").option("--description <text>", "Feature description").option("--description-file <path>", "Read feature description from a file").option("--instruction <text>", "Detailed feature instructions").option("--instruction-file <path>", "Read detailed feature instructions from a file").option("--variables <json>", "JSON array of variable definitions: [{\"name\":\"featureName\",\"description\":\"Feature name\",\"type\":\"string\",\"required\":true}]").option("-i, --include <path>", "Template include path (repeatable)", collectOption, []).option("--pattern <glob>", "File pattern this scaffold applies to (repeatable)", collectOption, []).action(async (featureName, options) => {
544
+ try {
545
+ const templatesPath = await resolveTemplatesPath();
546
+ const isMonolith = await detectMonolithMode();
547
+ const description = await loadTextOption({
548
+ value: options.description,
549
+ filePath: options.descriptionFile,
550
+ valueFlag: "--description",
551
+ fileFlag: "--description-file",
552
+ required: true
553
+ });
554
+ const instruction = await loadTextOption({
555
+ value: options.instruction,
556
+ filePath: options.instructionFile,
557
+ valueFlag: "--instruction",
558
+ fileFlag: "--instruction-file"
559
+ });
560
+ const variables = parseJsonOption(options.variables, "--variables");
561
+ if (!Array.isArray(variables)) throw new Error("--variables must be a JSON array");
562
+ const result = await new require_tools.GenerateFeatureScaffoldTool(templatesPath, isMonolith).execute({
563
+ templateName: options.template,
564
+ featureName,
565
+ description: description ?? "",
566
+ instruction,
567
+ variables,
568
+ includes: options.include ?? [],
569
+ patterns: options.pattern ?? []
570
+ });
571
+ _agiflowai_aicode_utils.print.info(assertToolSuccess(result));
572
+ } catch (error) {
573
+ _agiflowai_aicode_utils.print.error("Error generating feature scaffold:", error instanceof Error ? error.message : String(error));
574
+ process.exit(1);
575
+ }
576
+ });
440
577
  scaffoldCommand.command("info <featureName>").description("Show detailed information about a scaffold method").option("-p, --project <path>", "Project path").option("-t, --template <name>", "Template name (e.g., nextjs-15, typescript-mcp-package)").action(async (featureName, options) => {
441
578
  try {
442
579
  if (!options.project && !options.template) {
@@ -575,7 +712,7 @@ const hookCommand = new commander.Command("hook").description("Execute scaffold
575
712
  const resolvedAdapterConfig = Object.keys(adapterConfig).length > 0 ? adapterConfig : void 0;
576
713
  if (!isHookMethod(hookMethod)) process.exit(0);
577
714
  if (agent === _agiflowai_coding_agent_bridge.CLAUDE_CODE) {
578
- const hookModule = await Promise.resolve().then(() => require("./claudeCode-BxcEboyM.cjs"));
715
+ const hookModule = await Promise.resolve().then(() => require("./claudeCode-DZnJyFQt.cjs"));
579
716
  const claudeCallbacks = [];
580
717
  if (hookModule.UseScaffoldMethodHook) {
581
718
  const hookInstance = new hookModule.UseScaffoldMethodHook();
@@ -590,7 +727,7 @@ const hookCommand = new commander.Command("hook").description("Execute scaffold
590
727
  if (claudeCallbacks.length === 0) process.exit(0);
591
728
  await new _agiflowai_hooks_adapter.ClaudeCodeAdapter().executeMultiple(claudeCallbacks, resolvedAdapterConfig);
592
729
  } else if (agent === _agiflowai_coding_agent_bridge.GEMINI_CLI) {
593
- const hookModule = await Promise.resolve().then(() => require("./geminiCli--s1Qy7ZP.cjs"));
730
+ const hookModule = await Promise.resolve().then(() => require("./geminiCli-wTHUG-A6.cjs"));
594
731
  const geminiCallbacks = [];
595
732
  if (hookModule.UseScaffoldMethodHook) {
596
733
  const hookInstance = new hookModule.UseScaffoldMethodHook();
@@ -599,13 +736,62 @@ const hookCommand = new commander.Command("hook").description("Execute scaffold
599
736
  }
600
737
  if (geminiCallbacks.length === 0) process.exit(0);
601
738
  await new _agiflowai_hooks_adapter.GeminiCliAdapter().executeMultiple(geminiCallbacks, resolvedAdapterConfig);
602
- } else throw new Error(`Unsupported agent: ${agent}. Supported: ${_agiflowai_coding_agent_bridge.CLAUDE_CODE}, ${_agiflowai_coding_agent_bridge.GEMINI_CLI}`);
739
+ } else if (agent === _agiflowai_coding_agent_bridge.CODEX) {
740
+ const hookModule = await Promise.resolve().then(() => require("./codex-CfOJFx29.cjs"));
741
+ const codexCallbacks = [];
742
+ if (hookModule.UseScaffoldMethodHook) {
743
+ const hookInstance = new hookModule.UseScaffoldMethodHook();
744
+ const hookFn = hookInstance[hookMethod];
745
+ if (hookFn) codexCallbacks.push(hookFn.bind(hookInstance));
746
+ }
747
+ if (codexCallbacks.length === 0) process.exit(0);
748
+ await new _agiflowai_hooks_adapter.CodexAdapter().executeMultiple(codexCallbacks, resolvedAdapterConfig);
749
+ } else throw new Error(`Unsupported agent: ${agent}. Supported: ${_agiflowai_coding_agent_bridge.CLAUDE_CODE}, ${_agiflowai_coding_agent_bridge.GEMINI_CLI}, ${_agiflowai_coding_agent_bridge.CODEX}`);
603
750
  } catch (error) {
604
751
  _agiflowai_aicode_utils.print.error(`Hook error: ${error instanceof Error ? error.message : String(error)}`);
605
752
  process.exit(1);
606
753
  }
607
754
  });
608
755
  //#endregion
756
+ //#region src/commands/template.ts
757
+ const templateCommand = new commander.Command("template").description("Manage scaffold template authoring assets");
758
+ const fileCommand = new commander.Command("file").description("Manage template files");
759
+ fileCommand.command("create <filePath>").description("Create or update a Liquid template file for a boilerplate or feature").option("-t, --template <name>", "Template name (optional in monolith mode)").option("--content <text>", "Template file content").option("--content-file <path>", "Read template file content from a file").option("--source-file <path>", "Copy content from an existing source file").option("--header <text>", "Header comment to prepend to the template file").option("--header-file <path>", "Read header comment from a file").action(async (filePath, options) => {
760
+ try {
761
+ if ([
762
+ options.content !== void 0,
763
+ options.contentFile !== void 0,
764
+ options.sourceFile !== void 0
765
+ ].filter(Boolean).length !== 1) throw new Error("Use exactly one of --content, --content-file, or --source-file");
766
+ const templatesPath = await resolveTemplatesPath();
767
+ const isMonolith = await detectMonolithMode();
768
+ const content = await loadTextOption({
769
+ value: options.content,
770
+ filePath: options.contentFile,
771
+ valueFlag: "--content",
772
+ fileFlag: "--content-file"
773
+ });
774
+ const header = await loadTextOption({
775
+ value: options.header,
776
+ filePath: options.headerFile,
777
+ valueFlag: "--header",
778
+ fileFlag: "--header-file"
779
+ });
780
+ const result = await new require_tools.GenerateBoilerplateFileTool(templatesPath, isMonolith).execute({
781
+ templateName: options.template,
782
+ filePath,
783
+ content,
784
+ sourceFile: options.sourceFile,
785
+ header
786
+ });
787
+ _agiflowai_aicode_utils.print.info(assertToolSuccess(result));
788
+ } catch (error) {
789
+ _agiflowai_aicode_utils.print.error("Error creating template file:", error instanceof Error ? error.message : String(error));
790
+ process.exit(1);
791
+ }
792
+ });
793
+ templateCommand.addCommand(fileCommand);
794
+ //#endregion
609
795
  //#region src/cli.ts
610
796
  /**
611
797
  * Main entry point
@@ -616,6 +802,8 @@ async function main() {
616
802
  program.addCommand(mcpServeCommand);
617
803
  program.addCommand(boilerplateCommand);
618
804
  program.addCommand(scaffoldCommand);
805
+ program.addCommand(templateCommand);
806
+ program.addCommand(fileCommand$1);
619
807
  program.addCommand(hookCommand);
620
808
  await program.parseAsync(process.argv);
621
809
  }