@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 +33 -0
- package/dist/{ListScaffoldingMethodsTool-DxdrPlcb.mjs → ListScaffoldingMethodsTool-dHaM4QXT.mjs} +6 -2
- package/dist/{ListScaffoldingMethodsTool-CYZtpU-W.cjs → ListScaffoldingMethodsTool-rX9Hxn2d.cjs} +6 -2
- package/dist/{claudeCode-DNgNiLlL.mjs → claudeCode-CyHppxA_.mjs} +12 -3
- package/dist/{claudeCode-BxcEboyM.cjs → claudeCode-DZnJyFQt.cjs} +12 -3
- package/dist/cli.cjs +208 -20
- package/dist/cli.mjs +211 -23
- package/dist/codex-7ry557jE.mjs +254 -0
- package/dist/codex-CfOJFx29.cjs +255 -0
- package/dist/{geminiCli-CcrZEqsz.mjs → geminiCli-CgajucCd.mjs} +10 -2
- package/dist/{geminiCli--s1Qy7ZP.cjs → geminiCli-wTHUG-A6.cjs} +10 -2
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +3 -3
- package/dist/shared-B0H-6AUG.cjs +187 -0
- package/dist/shared-BJjclypj.mjs +149 -0
- package/dist/{src-oF_UdSBu.cjs → src-CRT1Er93.cjs} +3 -3
- package/dist/{src-BEwgHhCT.mjs → src-CUQR93cQ.mjs} +3 -3
- package/dist/{tools-Cl06aoBi.mjs → tools-BR1vQd_Y.mjs} +27 -24
- package/dist/{tools-CVSZSirE.cjs → tools-CV6GWs6f.cjs} +31 -23
- package/package.json +7 -7
- package/dist/shared-BVYIN3Is.mjs +0 -38
- package/dist/shared-QxPXh-L-.cjs +0 -52
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as createServer, i as StdioTransportHandler, n as HttpTransportHandler, o as version, r as SseTransportHandler, t as TransportMode } from "./src-
|
|
3
|
-
import { n as ScaffoldingMethodsService, s as FileSystemService } from "./ListScaffoldingMethodsTool-
|
|
4
|
-
import {
|
|
5
|
-
import { ProjectConfigResolver, TemplatesManagerService, icons, messages, print, sections } from "@agiflowai/aicode-utils";
|
|
2
|
+
import { a as createServer, i as StdioTransportHandler, n as HttpTransportHandler, o as version, r as SseTransportHandler, t as TransportMode } from "./src-CUQR93cQ.mjs";
|
|
3
|
+
import { n as ScaffoldingMethodsService, s as FileSystemService } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
4
|
+
import { c as GenerateBoilerplateFileTool, o as GenerateFeatureScaffoldTool, r as writePendingScaffoldLog, s as GenerateBoilerplateTool, t as WriteToFileTool, u as BoilerplateService } from "./tools-BR1vQd_Y.mjs";
|
|
5
|
+
import { ProjectConfigResolver, TemplatesManagerService, generateStableId, icons, messages, print, readFile, sections } from "@agiflowai/aicode-utils";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { CLAUDE_CODE, GEMINI_CLI, SUPPORTED_LLM_TOOLS, isValidLlmTool } from "@agiflowai/coding-agent-bridge";
|
|
9
|
-
import { ClaudeCodeAdapter, GeminiCliAdapter, parseHookType } from "@agiflowai/hooks-adapter";
|
|
8
|
+
import { CLAUDE_CODE, CODEX, GEMINI_CLI, SUPPORTED_LLM_TOOLS, isValidLlmTool } from "@agiflowai/coding-agent-bridge";
|
|
9
|
+
import { ClaudeCodeAdapter, CodexAdapter, GeminiCliAdapter, parseHookType } from "@agiflowai/hooks-adapter";
|
|
10
|
+
//#region src/commands/utils.ts
|
|
11
|
+
function parseJsonOption(value, flagName) {
|
|
12
|
+
if (!value) throw new Error(`${flagName} is required`);
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(value);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new Error(`Invalid JSON for ${flagName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function resolveTemplatesPath() {
|
|
20
|
+
const templatesDir = await TemplatesManagerService.findTemplatesPath();
|
|
21
|
+
if (!templatesDir) throw new Error("Templates folder not found. Create a templates folder or specify templatesPath in toolkit.yaml");
|
|
22
|
+
return templatesDir;
|
|
23
|
+
}
|
|
24
|
+
async function detectMonolithMode() {
|
|
25
|
+
try {
|
|
26
|
+
return (await ProjectConfigResolver.resolveProjectConfig(process.cwd())).type === "monolith";
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function loadTextOption(config) {
|
|
32
|
+
const { value, filePath, valueFlag, fileFlag, required = false } = config;
|
|
33
|
+
if (value !== void 0 && filePath !== void 0) throw new Error(`Use only one of ${valueFlag} or ${fileFlag}`);
|
|
34
|
+
if (filePath !== void 0) return readFile(filePath, "utf-8");
|
|
35
|
+
if (value !== void 0) return value;
|
|
36
|
+
if (required) throw new Error(`${valueFlag} or ${fileFlag} is required`);
|
|
37
|
+
}
|
|
38
|
+
function collectOption(value, previous = []) {
|
|
39
|
+
return [...previous, value];
|
|
40
|
+
}
|
|
41
|
+
function firstTextContent(result) {
|
|
42
|
+
const firstContent = result.content[0];
|
|
43
|
+
return firstContent?.type === "text" ? firstContent.text : "";
|
|
44
|
+
}
|
|
45
|
+
function assertToolSuccess(result) {
|
|
46
|
+
const text = firstTextContent(result);
|
|
47
|
+
if (result.isError) throw new Error(text || "Tool execution failed");
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
10
51
|
//#region src/commands/boilerplate.ts
|
|
11
52
|
/**
|
|
12
53
|
* Boilerplate CLI command
|
|
@@ -43,7 +84,7 @@ boilerplateCommand.command("list").description("List all available boilerplate t
|
|
|
43
84
|
process.exit(1);
|
|
44
85
|
}
|
|
45
86
|
});
|
|
46
|
-
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) => {
|
|
87
|
+
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) => {
|
|
47
88
|
try {
|
|
48
89
|
const templatesDir = await TemplatesManagerService.findTemplatesPath();
|
|
49
90
|
if (!templatesDir) {
|
|
@@ -93,7 +134,8 @@ boilerplateCommand.command("create <boilerplateName>").description("Create a new
|
|
|
93
134
|
boilerplateName,
|
|
94
135
|
variables,
|
|
95
136
|
monolith: options.monolith,
|
|
96
|
-
targetFolderOverride: options.targetFolder
|
|
137
|
+
targetFolderOverride: options.targetFolder,
|
|
138
|
+
marker: options.marker
|
|
97
139
|
});
|
|
98
140
|
if (result.success) {
|
|
99
141
|
messages.success("Project created successfully!");
|
|
@@ -120,6 +162,42 @@ boilerplateCommand.command("create <boilerplateName>").description("Create a new
|
|
|
120
162
|
process.exit(1);
|
|
121
163
|
}
|
|
122
164
|
});
|
|
165
|
+
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) => {
|
|
166
|
+
try {
|
|
167
|
+
const templatesPath = await resolveTemplatesPath();
|
|
168
|
+
const isMonolith = await detectMonolithMode();
|
|
169
|
+
const description = await loadTextOption({
|
|
170
|
+
value: options.description,
|
|
171
|
+
filePath: options.descriptionFile,
|
|
172
|
+
valueFlag: "--description",
|
|
173
|
+
fileFlag: "--description-file",
|
|
174
|
+
required: true
|
|
175
|
+
});
|
|
176
|
+
const instruction = await loadTextOption({
|
|
177
|
+
value: options.instruction,
|
|
178
|
+
filePath: options.instructionFile,
|
|
179
|
+
valueFlag: "--instruction",
|
|
180
|
+
fileFlag: "--instruction-file"
|
|
181
|
+
});
|
|
182
|
+
const variables = parseJsonOption(options.variables, "--variables");
|
|
183
|
+
if (!Array.isArray(variables)) throw new Error("--variables must be a JSON array");
|
|
184
|
+
const targetFolder = options.targetFolder ?? (isMonolith ? "." : void 0);
|
|
185
|
+
if (!targetFolder) throw new Error("--target-folder is required outside monolith mode");
|
|
186
|
+
const result = await new GenerateBoilerplateTool(templatesPath, isMonolith).execute({
|
|
187
|
+
templateName: options.template,
|
|
188
|
+
boilerplateName,
|
|
189
|
+
description: description ?? "",
|
|
190
|
+
instruction,
|
|
191
|
+
targetFolder,
|
|
192
|
+
variables,
|
|
193
|
+
includes: options.include ?? []
|
|
194
|
+
});
|
|
195
|
+
print.info(assertToolSuccess(result));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
print.error("Error generating boilerplate:", error instanceof Error ? error.message : String(error));
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
123
201
|
boilerplateCommand.command("info <boilerplateName>").description("Show detailed information about a boilerplate template").action(async (boilerplateName) => {
|
|
124
202
|
try {
|
|
125
203
|
const templatesDir = await TemplatesManagerService.findTemplatesPath();
|
|
@@ -145,6 +223,28 @@ boilerplateCommand.command("info <boilerplateName>").description("Show detailed
|
|
|
145
223
|
}
|
|
146
224
|
});
|
|
147
225
|
//#endregion
|
|
226
|
+
//#region src/commands/file.ts
|
|
227
|
+
const fileCommand$1 = new Command("file").description("Workspace file utilities");
|
|
228
|
+
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) => {
|
|
229
|
+
try {
|
|
230
|
+
const content = await loadTextOption({
|
|
231
|
+
value: options.content,
|
|
232
|
+
filePath: options.contentFile,
|
|
233
|
+
valueFlag: "--content",
|
|
234
|
+
fileFlag: "--content-file",
|
|
235
|
+
required: true
|
|
236
|
+
});
|
|
237
|
+
const result = await new WriteToFileTool().execute({
|
|
238
|
+
file_path: filePath,
|
|
239
|
+
content
|
|
240
|
+
});
|
|
241
|
+
print.info(assertToolSuccess(result));
|
|
242
|
+
} catch (error) {
|
|
243
|
+
print.error("Error writing file:", error instanceof Error ? error.message : String(error));
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
//#endregion
|
|
148
248
|
//#region src/commands/mcp-serve.ts
|
|
149
249
|
/**
|
|
150
250
|
* MCP Serve Command
|
|
@@ -298,13 +398,6 @@ const mcpServeCommand = new Command("mcp-serve").description("Start MCP server w
|
|
|
298
398
|
const scaffoldCommand = new Command("scaffold").description("Add features to existing projects");
|
|
299
399
|
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) => {
|
|
300
400
|
try {
|
|
301
|
-
if (!projectPath && !options.template) {
|
|
302
|
-
messages.error("Either projectPath or --template option must be provided");
|
|
303
|
-
messages.hint("Examples:");
|
|
304
|
-
print.debug(" scaffold-mcp scaffold list ./my-project");
|
|
305
|
-
print.debug(" scaffold-mcp scaffold list --template nextjs-15");
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
401
|
const templatesDir = await TemplatesManagerService.findTemplatesPath();
|
|
309
402
|
if (!templatesDir) {
|
|
310
403
|
messages.error("Templates folder not found. Create a templates folder or specify templatesPath in toolkit.yaml");
|
|
@@ -313,15 +406,16 @@ scaffoldCommand.command("list [projectPath]").description("List available scaffo
|
|
|
313
406
|
const scaffoldingMethodsService = new ScaffoldingMethodsService(new FileSystemService(), templatesDir);
|
|
314
407
|
let result;
|
|
315
408
|
let displayName;
|
|
316
|
-
if (projectPath) {
|
|
317
|
-
const
|
|
409
|
+
if (projectPath || !options.template) {
|
|
410
|
+
const inputProjectPath = projectPath ?? process.cwd();
|
|
411
|
+
const absolutePath = path.resolve(inputProjectPath);
|
|
318
412
|
if (!await ProjectConfigResolver.hasConfiguration(absolutePath)) {
|
|
319
413
|
messages.error(`No project configuration found in ${absolutePath}`);
|
|
320
414
|
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");
|
|
321
415
|
process.exit(1);
|
|
322
416
|
}
|
|
323
417
|
result = await scaffoldingMethodsService.listScaffoldingMethods(absolutePath, options.cursor);
|
|
324
|
-
displayName = projectPath;
|
|
418
|
+
displayName = projectPath ?? absolutePath;
|
|
325
419
|
} else {
|
|
326
420
|
result = await scaffoldingMethodsService.listScaffoldingMethodsByTemplate(options.template, options.cursor);
|
|
327
421
|
displayName = `template: ${options.template}`;
|
|
@@ -350,7 +444,7 @@ scaffoldCommand.command("list [projectPath]").description("List available scaffo
|
|
|
350
444
|
process.exit(1);
|
|
351
445
|
}
|
|
352
446
|
});
|
|
353
|
-
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) => {
|
|
447
|
+
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) => {
|
|
354
448
|
try {
|
|
355
449
|
const projectPath = path.resolve(options.project);
|
|
356
450
|
if (!await ProjectConfigResolver.hasConfiguration(projectPath)) {
|
|
@@ -407,9 +501,17 @@ scaffoldCommand.command("add <featureName>").description("Add a feature to an ex
|
|
|
407
501
|
const result = await scaffoldingMethodsService.useScaffoldMethod({
|
|
408
502
|
projectPath,
|
|
409
503
|
scaffold_feature_name: featureName,
|
|
410
|
-
variables
|
|
504
|
+
variables,
|
|
505
|
+
marker: options.marker
|
|
411
506
|
});
|
|
412
507
|
if (result.success) {
|
|
508
|
+
const scaffoldId = generateStableId(6);
|
|
509
|
+
if (result.createdFiles && result.createdFiles.length > 0) await writePendingScaffoldLog({
|
|
510
|
+
scaffoldId,
|
|
511
|
+
projectPath,
|
|
512
|
+
featureName,
|
|
513
|
+
generatedFiles: result.createdFiles
|
|
514
|
+
});
|
|
413
515
|
messages.success("✅ Feature added successfully!");
|
|
414
516
|
if (result.createdFiles && result.createdFiles.length > 0) {
|
|
415
517
|
print.header("\n📁 Created files:");
|
|
@@ -423,6 +525,7 @@ scaffoldCommand.command("add <featureName>").description("Add a feature to an ex
|
|
|
423
525
|
print.debug(` - ${warning}`);
|
|
424
526
|
});
|
|
425
527
|
}
|
|
528
|
+
print.debug(`\nSCAFFOLD_ID:${scaffoldId}`);
|
|
426
529
|
print.header("\n📋 Next steps:");
|
|
427
530
|
print.debug(" - Review the generated files");
|
|
428
531
|
print.debug(" - Update imports if necessary");
|
|
@@ -436,6 +539,40 @@ scaffoldCommand.command("add <featureName>").description("Add a feature to an ex
|
|
|
436
539
|
process.exit(1);
|
|
437
540
|
}
|
|
438
541
|
});
|
|
542
|
+
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) => {
|
|
543
|
+
try {
|
|
544
|
+
const templatesPath = await resolveTemplatesPath();
|
|
545
|
+
const isMonolith = await detectMonolithMode();
|
|
546
|
+
const description = await loadTextOption({
|
|
547
|
+
value: options.description,
|
|
548
|
+
filePath: options.descriptionFile,
|
|
549
|
+
valueFlag: "--description",
|
|
550
|
+
fileFlag: "--description-file",
|
|
551
|
+
required: true
|
|
552
|
+
});
|
|
553
|
+
const instruction = await loadTextOption({
|
|
554
|
+
value: options.instruction,
|
|
555
|
+
filePath: options.instructionFile,
|
|
556
|
+
valueFlag: "--instruction",
|
|
557
|
+
fileFlag: "--instruction-file"
|
|
558
|
+
});
|
|
559
|
+
const variables = parseJsonOption(options.variables, "--variables");
|
|
560
|
+
if (!Array.isArray(variables)) throw new Error("--variables must be a JSON array");
|
|
561
|
+
const result = await new GenerateFeatureScaffoldTool(templatesPath, isMonolith).execute({
|
|
562
|
+
templateName: options.template,
|
|
563
|
+
featureName,
|
|
564
|
+
description: description ?? "",
|
|
565
|
+
instruction,
|
|
566
|
+
variables,
|
|
567
|
+
includes: options.include ?? [],
|
|
568
|
+
patterns: options.pattern ?? []
|
|
569
|
+
});
|
|
570
|
+
print.info(assertToolSuccess(result));
|
|
571
|
+
} catch (error) {
|
|
572
|
+
print.error("Error generating feature scaffold:", error instanceof Error ? error.message : String(error));
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
439
576
|
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) => {
|
|
440
577
|
try {
|
|
441
578
|
if (!options.project && !options.template) {
|
|
@@ -574,7 +711,7 @@ const hookCommand = new Command("hook").description("Execute scaffold hooks for
|
|
|
574
711
|
const resolvedAdapterConfig = Object.keys(adapterConfig).length > 0 ? adapterConfig : void 0;
|
|
575
712
|
if (!isHookMethod(hookMethod)) process.exit(0);
|
|
576
713
|
if (agent === CLAUDE_CODE) {
|
|
577
|
-
const hookModule = await import("./claudeCode-
|
|
714
|
+
const hookModule = await import("./claudeCode-CyHppxA_.mjs");
|
|
578
715
|
const claudeCallbacks = [];
|
|
579
716
|
if (hookModule.UseScaffoldMethodHook) {
|
|
580
717
|
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
@@ -589,7 +726,7 @@ const hookCommand = new Command("hook").description("Execute scaffold hooks for
|
|
|
589
726
|
if (claudeCallbacks.length === 0) process.exit(0);
|
|
590
727
|
await new ClaudeCodeAdapter().executeMultiple(claudeCallbacks, resolvedAdapterConfig);
|
|
591
728
|
} else if (agent === GEMINI_CLI) {
|
|
592
|
-
const hookModule = await import("./geminiCli-
|
|
729
|
+
const hookModule = await import("./geminiCli-CgajucCd.mjs");
|
|
593
730
|
const geminiCallbacks = [];
|
|
594
731
|
if (hookModule.UseScaffoldMethodHook) {
|
|
595
732
|
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
@@ -598,13 +735,62 @@ const hookCommand = new Command("hook").description("Execute scaffold hooks for
|
|
|
598
735
|
}
|
|
599
736
|
if (geminiCallbacks.length === 0) process.exit(0);
|
|
600
737
|
await new GeminiCliAdapter().executeMultiple(geminiCallbacks, resolvedAdapterConfig);
|
|
601
|
-
} else
|
|
738
|
+
} else if (agent === CODEX) {
|
|
739
|
+
const hookModule = await import("./codex-7ry557jE.mjs");
|
|
740
|
+
const codexCallbacks = [];
|
|
741
|
+
if (hookModule.UseScaffoldMethodHook) {
|
|
742
|
+
const hookInstance = new hookModule.UseScaffoldMethodHook();
|
|
743
|
+
const hookFn = hookInstance[hookMethod];
|
|
744
|
+
if (hookFn) codexCallbacks.push(hookFn.bind(hookInstance));
|
|
745
|
+
}
|
|
746
|
+
if (codexCallbacks.length === 0) process.exit(0);
|
|
747
|
+
await new CodexAdapter().executeMultiple(codexCallbacks, resolvedAdapterConfig);
|
|
748
|
+
} else throw new Error(`Unsupported agent: ${agent}. Supported: ${CLAUDE_CODE}, ${GEMINI_CLI}, ${CODEX}`);
|
|
602
749
|
} catch (error) {
|
|
603
750
|
print.error(`Hook error: ${error instanceof Error ? error.message : String(error)}`);
|
|
604
751
|
process.exit(1);
|
|
605
752
|
}
|
|
606
753
|
});
|
|
607
754
|
//#endregion
|
|
755
|
+
//#region src/commands/template.ts
|
|
756
|
+
const templateCommand = new Command("template").description("Manage scaffold template authoring assets");
|
|
757
|
+
const fileCommand = new Command("file").description("Manage template files");
|
|
758
|
+
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) => {
|
|
759
|
+
try {
|
|
760
|
+
if ([
|
|
761
|
+
options.content !== void 0,
|
|
762
|
+
options.contentFile !== void 0,
|
|
763
|
+
options.sourceFile !== void 0
|
|
764
|
+
].filter(Boolean).length !== 1) throw new Error("Use exactly one of --content, --content-file, or --source-file");
|
|
765
|
+
const templatesPath = await resolveTemplatesPath();
|
|
766
|
+
const isMonolith = await detectMonolithMode();
|
|
767
|
+
const content = await loadTextOption({
|
|
768
|
+
value: options.content,
|
|
769
|
+
filePath: options.contentFile,
|
|
770
|
+
valueFlag: "--content",
|
|
771
|
+
fileFlag: "--content-file"
|
|
772
|
+
});
|
|
773
|
+
const header = await loadTextOption({
|
|
774
|
+
value: options.header,
|
|
775
|
+
filePath: options.headerFile,
|
|
776
|
+
valueFlag: "--header",
|
|
777
|
+
fileFlag: "--header-file"
|
|
778
|
+
});
|
|
779
|
+
const result = await new GenerateBoilerplateFileTool(templatesPath, isMonolith).execute({
|
|
780
|
+
templateName: options.template,
|
|
781
|
+
filePath,
|
|
782
|
+
content,
|
|
783
|
+
sourceFile: options.sourceFile,
|
|
784
|
+
header
|
|
785
|
+
});
|
|
786
|
+
print.info(assertToolSuccess(result));
|
|
787
|
+
} catch (error) {
|
|
788
|
+
print.error("Error creating template file:", error instanceof Error ? error.message : String(error));
|
|
789
|
+
process.exit(1);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
templateCommand.addCommand(fileCommand);
|
|
793
|
+
//#endregion
|
|
608
794
|
//#region src/cli.ts
|
|
609
795
|
/**
|
|
610
796
|
* Main entry point
|
|
@@ -615,6 +801,8 @@ async function main() {
|
|
|
615
801
|
program.addCommand(mcpServeCommand);
|
|
616
802
|
program.addCommand(boilerplateCommand);
|
|
617
803
|
program.addCommand(scaffoldCommand);
|
|
804
|
+
program.addCommand(templateCommand);
|
|
805
|
+
program.addCommand(fileCommand$1);
|
|
618
806
|
program.addCommand(hookCommand);
|
|
619
807
|
await program.parseAsync(process.argv);
|
|
620
808
|
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { t as ListScaffoldingMethodsTool } from "./ListScaffoldingMethodsTool-dHaM4QXT.mjs";
|
|
2
|
+
import { a as resolveApplyPatchNewFileTargets, i as resolveApplyPatchEditedPaths, n as getGlobalExcludeGlobs, r as matchesExcludeGlob, t as formatScaffoldMethodsHookMessage } from "./shared-BJjclypj.mjs";
|
|
3
|
+
import { ProjectFinderService, TemplatesManagerService } from "@agiflowai/aicode-utils";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { DECISION_ALLOW, DECISION_DENY, DECISION_SKIP, ExecutionLogService } from "@agiflowai/hooks-adapter";
|
|
6
|
+
//#region src/hooks/codex/useScaffoldMethod.ts
|
|
7
|
+
/**
|
|
8
|
+
* Type guard for ScaffoldMethodsResponse
|
|
9
|
+
*/
|
|
10
|
+
function isScaffoldMethodsResponse(value) {
|
|
11
|
+
if (typeof value !== "object" || value === null) return false;
|
|
12
|
+
if ("methods" in value && !Array.isArray(value.methods)) return false;
|
|
13
|
+
if ("excludeGlobs" in value && !Array.isArray(value.excludeGlobs)) return false;
|
|
14
|
+
if ("nextCursor" in value && typeof value.nextCursor !== "string") return false;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* UseScaffoldMethod Hook class for Codex CLI
|
|
19
|
+
*
|
|
20
|
+
* Provides lifecycle hooks for tool execution:
|
|
21
|
+
* - preToolUse: Shows available scaffolding methods before an apply_patch creates new files
|
|
22
|
+
* - postToolUse: Tracks scaffold completion progress after file edits
|
|
23
|
+
*/
|
|
24
|
+
var UseScaffoldMethodHook = class {
|
|
25
|
+
/**
|
|
26
|
+
* PreToolUse hook for Codex CLI
|
|
27
|
+
* Proactively shows available scaffolding methods and guides the agent to use them
|
|
28
|
+
* when an apply_patch is about to create a new file.
|
|
29
|
+
*
|
|
30
|
+
* @param context - Codex CLI hook input
|
|
31
|
+
* @returns Hook response with scaffolding methods guidance
|
|
32
|
+
*/
|
|
33
|
+
async preToolUse(context) {
|
|
34
|
+
try {
|
|
35
|
+
if (!("tool_name" in context)) return {
|
|
36
|
+
decision: DECISION_SKIP,
|
|
37
|
+
message: "Not a tool use event"
|
|
38
|
+
};
|
|
39
|
+
const command = typeof context.tool_input?.command === "string" ? context.tool_input.command : void 0;
|
|
40
|
+
const newFileTargets = resolveApplyPatchNewFileTargets(context.cwd, context.tool_name, command);
|
|
41
|
+
if (newFileTargets.length === 0) return {
|
|
42
|
+
decision: DECISION_SKIP,
|
|
43
|
+
message: "Not a new-file apply_patch operation"
|
|
44
|
+
};
|
|
45
|
+
const globalExcludeGlobs = await getGlobalExcludeGlobs(context.cwd);
|
|
46
|
+
const target = newFileTargets.find((p) => !matchesExcludeGlob(p, globalExcludeGlobs));
|
|
47
|
+
if (!target) return {
|
|
48
|
+
decision: DECISION_ALLOW,
|
|
49
|
+
message: "New file(s) match configured excludeGlobs - writing directly."
|
|
50
|
+
};
|
|
51
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
52
|
+
if (await executionLog.hasExecuted({
|
|
53
|
+
filePath: target,
|
|
54
|
+
decision: DECISION_DENY
|
|
55
|
+
})) return {
|
|
56
|
+
decision: DECISION_SKIP,
|
|
57
|
+
message: "Scaffolding methods already provided for this file"
|
|
58
|
+
};
|
|
59
|
+
const templatesPath = await TemplatesManagerService.findTemplatesPath();
|
|
60
|
+
if (!templatesPath) return {
|
|
61
|
+
decision: DECISION_SKIP,
|
|
62
|
+
message: "Templates folder not found - skipping scaffold method check"
|
|
63
|
+
};
|
|
64
|
+
const tool = new ListScaffoldingMethodsTool(templatesPath, false);
|
|
65
|
+
const projectPath = (await new ProjectFinderService(await TemplatesManagerService.getWorkspaceRoot(context.cwd)).findProjectForFile(target))?.root || context.cwd;
|
|
66
|
+
const result = await tool.execute({ projectPath });
|
|
67
|
+
const firstContent = result.content[0];
|
|
68
|
+
if (firstContent?.type !== "text") return {
|
|
69
|
+
decision: DECISION_SKIP,
|
|
70
|
+
message: "⚠️ Unexpected response type from scaffolding methods tool"
|
|
71
|
+
};
|
|
72
|
+
if (result.isError) return {
|
|
73
|
+
decision: DECISION_SKIP,
|
|
74
|
+
message: `⚠️ Could not load scaffolding methods: ${firstContent.text}`
|
|
75
|
+
};
|
|
76
|
+
const resultText = firstContent.text;
|
|
77
|
+
if (typeof resultText !== "string") return {
|
|
78
|
+
decision: DECISION_SKIP,
|
|
79
|
+
message: "⚠️ Invalid response format from scaffolding methods tool"
|
|
80
|
+
};
|
|
81
|
+
const parsed = JSON.parse(resultText);
|
|
82
|
+
if (!isScaffoldMethodsResponse(parsed)) return {
|
|
83
|
+
decision: DECISION_SKIP,
|
|
84
|
+
message: "⚠️ Unexpected response shape from scaffolding methods tool"
|
|
85
|
+
};
|
|
86
|
+
const data = parsed;
|
|
87
|
+
if (matchesExcludeGlob(target, data.excludeGlobs)) return {
|
|
88
|
+
decision: DECISION_ALLOW,
|
|
89
|
+
message: "File matches template exclude globs - writing directly."
|
|
90
|
+
};
|
|
91
|
+
if (!data.methods || data.methods.length === 0) {
|
|
92
|
+
await executionLog.logExecution({
|
|
93
|
+
filePath: target,
|
|
94
|
+
operation: "list-scaffold-methods",
|
|
95
|
+
decision: DECISION_ALLOW
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
decision: DECISION_ALLOW,
|
|
99
|
+
message: "No scaffolding methods are available for this project template. You should write new files directly."
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const message = formatScaffoldMethodsHookMessage(data.methods);
|
|
103
|
+
await executionLog.logExecution({
|
|
104
|
+
filePath: target,
|
|
105
|
+
operation: "list-scaffold-methods",
|
|
106
|
+
decision: DECISION_DENY
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
decision: DECISION_DENY,
|
|
110
|
+
message
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
decision: DECISION_SKIP,
|
|
115
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* PostToolUse hook for Codex CLI
|
|
121
|
+
* Tracks file edits after scaffold generation and reminds the agent to complete
|
|
122
|
+
* the implementation of all scaffold-generated files.
|
|
123
|
+
*
|
|
124
|
+
* @param context - Codex CLI hook input
|
|
125
|
+
* @returns Hook response with scaffold completion tracking
|
|
126
|
+
*/
|
|
127
|
+
async postToolUse(context) {
|
|
128
|
+
try {
|
|
129
|
+
if (!("tool_name" in context)) return {
|
|
130
|
+
decision: DECISION_SKIP,
|
|
131
|
+
message: "Not a tool use event"
|
|
132
|
+
};
|
|
133
|
+
const executionLog = new ExecutionLogService(context.session_id);
|
|
134
|
+
if (context.tool_name === "use-scaffold-method" || context.tool_input?.toolName === "use-scaffold-method") return {
|
|
135
|
+
decision: DECISION_ALLOW,
|
|
136
|
+
message: "Scaffold execution logged for progress tracking"
|
|
137
|
+
};
|
|
138
|
+
const command = typeof context.tool_input?.command === "string" ? context.tool_input.command : void 0;
|
|
139
|
+
const editedPaths = resolveApplyPatchEditedPaths(context.tool_name, command);
|
|
140
|
+
if (editedPaths.length === 0) return {
|
|
141
|
+
decision: DECISION_SKIP,
|
|
142
|
+
message: "Not a file edit/write operation"
|
|
143
|
+
};
|
|
144
|
+
const lastScaffoldExecution = await getLastScaffoldExecution(executionLog);
|
|
145
|
+
if (!lastScaffoldExecution) return {
|
|
146
|
+
decision: DECISION_SKIP,
|
|
147
|
+
message: "No scaffold execution found"
|
|
148
|
+
};
|
|
149
|
+
const { scaffoldId, generatedFiles, featureName } = lastScaffoldExecution;
|
|
150
|
+
const fulfilledKey = `scaffold-fulfilled-${scaffoldId}`;
|
|
151
|
+
if (await executionLog.hasExecuted({
|
|
152
|
+
filePath: fulfilledKey,
|
|
153
|
+
decision: DECISION_ALLOW
|
|
154
|
+
})) return {
|
|
155
|
+
decision: DECISION_SKIP,
|
|
156
|
+
message: "Scaffold already fulfilled"
|
|
157
|
+
};
|
|
158
|
+
let touchedScaffoldFile = false;
|
|
159
|
+
for (const editedPath of editedPaths) {
|
|
160
|
+
const absoluteEditedPath = path.isAbsolute(editedPath) ? editedPath : path.join(context.cwd, editedPath);
|
|
161
|
+
const generatedMatch = generatedFiles.find((f) => f === editedPath || f === absoluteEditedPath);
|
|
162
|
+
if (!generatedMatch) continue;
|
|
163
|
+
touchedScaffoldFile = true;
|
|
164
|
+
const editKey = `scaffold-edit-${scaffoldId}-${generatedMatch}`;
|
|
165
|
+
if (!await executionLog.hasExecuted({
|
|
166
|
+
filePath: editKey,
|
|
167
|
+
decision: DECISION_ALLOW
|
|
168
|
+
})) await executionLog.logExecution({
|
|
169
|
+
filePath: editKey,
|
|
170
|
+
operation: "scaffold-file-edit",
|
|
171
|
+
decision: DECISION_ALLOW
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const editedFiles = await getEditedScaffoldFiles(executionLog, scaffoldId);
|
|
175
|
+
const totalFiles = generatedFiles.length;
|
|
176
|
+
const remainingFiles = generatedFiles.filter((f) => !editedFiles.includes(f));
|
|
177
|
+
if (remainingFiles.length === 0) {
|
|
178
|
+
await executionLog.logExecution({
|
|
179
|
+
filePath: fulfilledKey,
|
|
180
|
+
operation: "scaffold-fulfilled",
|
|
181
|
+
decision: DECISION_ALLOW
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
decision: DECISION_ALLOW,
|
|
185
|
+
message: `✅ All scaffold files${featureName ? ` for "${featureName}"` : ""} have been implemented! (${totalFiles}/${totalFiles} files completed)`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (touchedScaffoldFile) {
|
|
189
|
+
const remainingFilesList = remainingFiles.map((f) => ` - ${f}`).join("\n");
|
|
190
|
+
return {
|
|
191
|
+
decision: DECISION_ALLOW,
|
|
192
|
+
message: `
|
|
193
|
+
⚠️ **Scaffold Implementation Progress${featureName ? ` for "${featureName}"` : ""}: ${editedFiles.length}/${totalFiles} files completed**
|
|
194
|
+
|
|
195
|
+
**Remaining files to implement:**
|
|
196
|
+
${remainingFilesList}
|
|
197
|
+
|
|
198
|
+
Don't forget to complete the implementation for all scaffolded files!
|
|
199
|
+
`.trim()
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
decision: DECISION_SKIP,
|
|
204
|
+
message: "Edited file not part of last scaffold execution"
|
|
205
|
+
};
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
decision: DECISION_SKIP,
|
|
209
|
+
message: `⚠️ Hook error: ${error instanceof Error ? error.message : String(error)}`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Helper function to get the last scaffold execution for a session.
|
|
216
|
+
* Returns null if no scaffold execution found or on error.
|
|
217
|
+
*/
|
|
218
|
+
async function getLastScaffoldExecution(executionLog) {
|
|
219
|
+
try {
|
|
220
|
+
const entries = await executionLog.loadLog();
|
|
221
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
222
|
+
const entry = entries[i];
|
|
223
|
+
if (entry.operation === "scaffold" && entry.scaffoldId && entry.generatedFiles && entry.generatedFiles.length > 0) return {
|
|
224
|
+
scaffoldId: entry.scaffoldId,
|
|
225
|
+
generatedFiles: entry.generatedFiles,
|
|
226
|
+
featureName: entry.featureName
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error("Error getting last scaffold execution:", error);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Helper function to get the list of edited scaffold files.
|
|
237
|
+
* Returns an empty array if no files found or on error.
|
|
238
|
+
*/
|
|
239
|
+
async function getEditedScaffoldFiles(executionLog, scaffoldId) {
|
|
240
|
+
try {
|
|
241
|
+
const entries = await executionLog.loadLog();
|
|
242
|
+
const editedFiles = [];
|
|
243
|
+
for (const entry of entries) if (entry.operation === "scaffold-file-edit" && entry.filePath.startsWith(`scaffold-edit-${scaffoldId}-`)) {
|
|
244
|
+
const filePath = entry.filePath.replace(`scaffold-edit-${scaffoldId}-`, "");
|
|
245
|
+
editedFiles.push(filePath);
|
|
246
|
+
}
|
|
247
|
+
return editedFiles;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`Error getting edited scaffold files for ${scaffoldId}:`, error);
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { UseScaffoldMethodHook };
|