@specverse/engines 4.1.5 → 4.1.7
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/dist/libs/instance-factories/applications/templates/generic/backend-env-generator.js +22 -0
- package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +66 -0
- package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +54 -0
- package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +290 -0
- package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +530 -0
- package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +437 -0
- package/dist/libs/instance-factories/applications/templates/react/api-types-generator.js +146 -0
- package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +73 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +18 -0
- package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +99 -0
- package/dist/libs/instance-factories/applications/templates/react/gitignore-generator.js +35 -0
- package/dist/libs/instance-factories/applications/templates/react/index-css-generator.js +9 -0
- package/dist/libs/instance-factories/applications/templates/react/index-html-generator.js +23 -0
- package/dist/libs/instance-factories/applications/templates/react/main-tsx-generator.js +29 -0
- package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +49 -0
- package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +156 -0
- package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +935 -0
- package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +101 -0
- package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +50 -0
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +646 -0
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +65 -0
- package/dist/libs/instance-factories/applications/templates/react/tsconfig-generator.js +28 -0
- package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +132 -0
- package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +355 -0
- package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +91 -0
- package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +79 -0
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +42 -0
- package/dist/libs/instance-factories/cli/templates/commander/cli-bin-wrapper-generator.js +11 -0
- package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +111 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +928 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +83 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/publisher-generator.js +91 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.js +86 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/meta-routes-generator.js +93 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +280 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +125 -0
- package/dist/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.js +25 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +371 -0
- package/dist/libs/instance-factories/orms/templates/prisma/services-generator.js +266 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/env-example-generator.js +51 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/env-generator.js +61 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/gitignore-generator.js +59 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +126 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/readme-generator.js +159 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +56 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-react-generator.js +37 -0
- package/dist/libs/instance-factories/sdks/templates/python/sdk-generator.js +29 -0
- package/dist/libs/instance-factories/sdks/templates/typescript/sdk-generator.js +28 -0
- package/dist/libs/instance-factories/services/templates/memory/generate-interpreter.js +14 -0
- package/dist/libs/instance-factories/services/templates/memory/step-conventions-memory.js +415 -0
- package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +177 -0
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +413 -0
- package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +243 -0
- package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +264 -0
- package/dist/libs/instance-factories/services/templates/shared-patterns.js +24 -0
- package/dist/libs/instance-factories/shared/path-resolver.js +59 -0
- package/dist/libs/instance-factories/storage/templates/mongodb/config-generator.js +13 -0
- package/dist/libs/instance-factories/storage/templates/mongodb/docker-generator.js +16 -0
- package/dist/libs/instance-factories/storage/templates/postgresql/config-generator.js +45 -0
- package/dist/libs/instance-factories/storage/templates/postgresql/docker-generator.js +46 -0
- package/dist/libs/instance-factories/storage/templates/redis/config-generator.js +14 -0
- package/dist/libs/instance-factories/storage/templates/redis/docker-generator.js +16 -0
- package/dist/libs/instance-factories/test-generation.js +145 -0
- package/dist/libs/instance-factories/testing/templates/vitest/tests-generator.js +30 -0
- package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +149 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +232 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +49 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +18 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +97 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +64 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +182 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +1210 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +172 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +240 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +147 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +281 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +409 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +414 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +467 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +135 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
- package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +965 -0
- package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +238 -0
- package/dist/libs/instance-factories/validation/templates/zod/validation-generator.js +25 -0
- package/dist/libs/instance-factories/views/index.js +48 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +742 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +824 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +719 -0
- package/dist/libs/instance-factories/views/templates/react/app-generator.js +45 -0
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +779 -0
- package/dist/libs/instance-factories/views/templates/react/forms-generator.js +285 -0
- package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +46 -0
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +111 -0
- package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +9 -0
- package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +23 -0
- package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +21 -0
- package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +299 -0
- package/dist/libs/instance-factories/views/templates/react/router-generator.js +136 -0
- package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +107 -0
- package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +179 -0
- package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +7 -0
- package/dist/libs/instance-factories/views/templates/react/types-generator.js +56 -0
- package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +27 -0
- package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +29 -0
- package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +261 -0
- package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +34 -0
- package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +800 -0
- package/dist/libs/instance-factories/views/templates/shared/base-generator.js +305 -0
- package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +517 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +445 -0
- package/dist/libs/instance-factories/views/templates/shared/index.js +80 -0
- package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +210 -0
- package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +492 -0
- package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +321 -0
- package/dist/realize/index.js +36 -12
- package/dist/realize/index.js.map +1 -1
- package/package.json +3 -2
package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js
ADDED
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
async function loadSpecVerseAPI() {
|
|
2
|
+
const { createRequire } = await import("module");
|
|
3
|
+
const { fileURLToPath } = await import("url");
|
|
4
|
+
const { join: join2, dirname } = await import("path");
|
|
5
|
+
const require2 = createRequire(import.meta.url);
|
|
6
|
+
const publishedPackage = "@specverse/engine-entities";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const isInDist = __dirname.includes("/dist/");
|
|
10
|
+
const levelsUp = isInDist ? 5 : 4;
|
|
11
|
+
const pathParts = Array(levelsUp).fill("..").concat(["dist", "index.js"]);
|
|
12
|
+
const localPath = join2(__dirname, ...pathParts);
|
|
13
|
+
try {
|
|
14
|
+
return await import(localPath);
|
|
15
|
+
} catch (localError) {
|
|
16
|
+
try {
|
|
17
|
+
require2.resolve(publishedPackage);
|
|
18
|
+
return await import(publishedPackage);
|
|
19
|
+
} catch (publishedError) {
|
|
20
|
+
throw new Error(`Failed to load SpecVerse API: local (${localError.message}) and published (${publishedError.message})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let specverseAPI = null;
|
|
25
|
+
import { writeFileSync, unlinkSync, mkdtempSync } from "fs";
|
|
26
|
+
import { join } from "path";
|
|
27
|
+
import { tmpdir } from "os";
|
|
28
|
+
class CLIProxyService {
|
|
29
|
+
constructor(workingDirectory) {
|
|
30
|
+
this.workingDirectory = workingDirectory;
|
|
31
|
+
}
|
|
32
|
+
capabilities = null;
|
|
33
|
+
cliPath = null;
|
|
34
|
+
lastDiscovery = 0;
|
|
35
|
+
DISCOVERY_CACHE_TTL = 5 * 60 * 1e3;
|
|
36
|
+
// 5 minutes
|
|
37
|
+
initialized = false;
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the API - called once before first use
|
|
40
|
+
*/
|
|
41
|
+
async init() {
|
|
42
|
+
if (this.initialized) return;
|
|
43
|
+
specverseAPI = await loadSpecVerseAPI();
|
|
44
|
+
this.cliPath = specverseAPI.getCliPath(this.workingDirectory);
|
|
45
|
+
if (!this.cliPath && process.env.MCP_DEBUG) {
|
|
46
|
+
console.error("\u26A0\uFE0F SpecVerse CLI not found. CLI proxy functionality will be limited.");
|
|
47
|
+
}
|
|
48
|
+
this.initialized = true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Discover CLI capabilities (cached for performance)
|
|
52
|
+
*/
|
|
53
|
+
async discoverCapabilities() {
|
|
54
|
+
await this.init();
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
if (this.capabilities && now - this.lastDiscovery < this.DISCOVERY_CACHE_TTL) {
|
|
57
|
+
return this.capabilities;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
this.capabilities = specverseAPI.getAllCliCapabilities(this.cliPath || void 0);
|
|
61
|
+
this.lastDiscovery = now;
|
|
62
|
+
if (process.env.MCP_DEBUG) {
|
|
63
|
+
console.error(`\u{1F50D} Discovered ${this.capabilities.coreCommands.length} core commands, ${this.capabilities.groupedCommands.length} grouped commands, ${this.capabilities.aiCommands.length} AI commands`);
|
|
64
|
+
}
|
|
65
|
+
return this.capabilities;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (process.env.MCP_DEBUG) {
|
|
68
|
+
console.error("\u274C Failed to discover CLI capabilities:", error);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
coreCommands: [],
|
|
72
|
+
groupedCommands: [],
|
|
73
|
+
aiCommands: []
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Generate MCP tool definitions from CLI capabilities
|
|
79
|
+
*/
|
|
80
|
+
async generateMCPTools() {
|
|
81
|
+
const capabilities = await this.discoverCapabilities();
|
|
82
|
+
const tools = [];
|
|
83
|
+
tools.push({
|
|
84
|
+
name: "specverse_mcp_debug",
|
|
85
|
+
description: "Show MCP server diagnostic information including SpecVerse installation details, path resolution, and environment analysis",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
verbose: {
|
|
90
|
+
type: "boolean",
|
|
91
|
+
description: "Show detailed path resolution information"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
required: []
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
for (const cmd of capabilities.coreCommands) {
|
|
98
|
+
tools.push(this.createMCPToolFromCommand(cmd));
|
|
99
|
+
}
|
|
100
|
+
for (const cmd of capabilities.groupedCommands) {
|
|
101
|
+
tools.push(this.createMCPToolFromGroupedCommand(cmd));
|
|
102
|
+
}
|
|
103
|
+
for (const cmd of capabilities.aiCommands) {
|
|
104
|
+
tools.push(...this.createAIMCPTools());
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
tools.push(...this.createContentBasedTools());
|
|
108
|
+
return tools;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Execute a CLI command via the proxy
|
|
112
|
+
*/
|
|
113
|
+
async executeCommand(toolName, args) {
|
|
114
|
+
try {
|
|
115
|
+
await this.init();
|
|
116
|
+
if (toolName === "specverse_mcp_debug") {
|
|
117
|
+
return await this.executeMCPDebugTool(args.verbose || false);
|
|
118
|
+
}
|
|
119
|
+
if (!this.cliPath) {
|
|
120
|
+
return {
|
|
121
|
+
content: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: "SpecVerse CLI not found. Please ensure SpecVerse is installed and accessible."
|
|
124
|
+
}],
|
|
125
|
+
isError: true
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (toolName.endsWith("_content")) {
|
|
129
|
+
return await this.handleContentBasedCommand(toolName, args);
|
|
130
|
+
}
|
|
131
|
+
const baseCommand = this.mapToolNameToCommand(toolName);
|
|
132
|
+
let command = baseCommand;
|
|
133
|
+
let commandArgs = { ...args };
|
|
134
|
+
if (baseCommand.startsWith("ai ") && args.operation) {
|
|
135
|
+
command = `${baseCommand} ${args.operation}`;
|
|
136
|
+
const { operation, ...remainingArgs } = args;
|
|
137
|
+
commandArgs = remainingArgs;
|
|
138
|
+
} else if (baseCommand.startsWith("lib ")) {
|
|
139
|
+
if (baseCommand === "lib search" && args.query) {
|
|
140
|
+
command = `${baseCommand} "${args.query}"`;
|
|
141
|
+
const { query, ...remainingArgs } = args;
|
|
142
|
+
commandArgs = remainingArgs;
|
|
143
|
+
} else if (baseCommand === "lib info" && args.name) {
|
|
144
|
+
command = `${baseCommand} "${args.name}"`;
|
|
145
|
+
const { name, ...remainingArgs } = args;
|
|
146
|
+
commandArgs = remainingArgs;
|
|
147
|
+
}
|
|
148
|
+
} else if (baseCommand === "init") {
|
|
149
|
+
console.error(`MCP Debug: Received args.name: "${args.name}"`);
|
|
150
|
+
console.error(`MCP Debug: Full args object:`, JSON.stringify(args, null, 2));
|
|
151
|
+
if (args.name) {
|
|
152
|
+
const fullPath = args.name;
|
|
153
|
+
const projectName = args.name.includes("/") ? args.name.split("/").pop() : args.name;
|
|
154
|
+
command = `${baseCommand} "${projectName}"`;
|
|
155
|
+
const { name, ...remainingArgs } = args;
|
|
156
|
+
commandArgs = { ...remainingArgs, fullPath };
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const result2 = await this.executeInitCommand(command, commandArgs);
|
|
160
|
+
return {
|
|
161
|
+
content: [{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: JSON.stringify(result2, null, 2)
|
|
164
|
+
}]
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
168
|
+
return {
|
|
169
|
+
content: [{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: JSON.stringify({
|
|
172
|
+
status: "error",
|
|
173
|
+
message: "Init command failed",
|
|
174
|
+
details: errorMessage,
|
|
175
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
176
|
+
}, null, 2)
|
|
177
|
+
}],
|
|
178
|
+
isError: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
} else if (args.file) {
|
|
182
|
+
command = `${baseCommand} "${args.file}"`;
|
|
183
|
+
const { file, ...remainingArgs } = args;
|
|
184
|
+
commandArgs = remainingArgs;
|
|
185
|
+
}
|
|
186
|
+
const result = await specverseAPI.executeCliCommand(command, commandArgs, {
|
|
187
|
+
cliPath: this.cliPath,
|
|
188
|
+
timeout: 6e4
|
|
189
|
+
// 1 minute timeout
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
content: [{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: JSON.stringify(result, null, 2)
|
|
195
|
+
}]
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
199
|
+
try {
|
|
200
|
+
const parsedError = JSON.parse(errorMessage);
|
|
201
|
+
return {
|
|
202
|
+
content: [{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: JSON.stringify(parsedError, null, 2)
|
|
205
|
+
}],
|
|
206
|
+
isError: true
|
|
207
|
+
};
|
|
208
|
+
} catch {
|
|
209
|
+
return {
|
|
210
|
+
content: [{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: JSON.stringify({
|
|
213
|
+
status: "error",
|
|
214
|
+
message: "CLI command execution failed",
|
|
215
|
+
details: errorMessage,
|
|
216
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
217
|
+
}, null, 2)
|
|
218
|
+
}],
|
|
219
|
+
isError: true
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Create MCP tool definition from core CLI command
|
|
226
|
+
*/
|
|
227
|
+
createMCPToolFromCommand(cmd) {
|
|
228
|
+
const properties = {};
|
|
229
|
+
const required = [];
|
|
230
|
+
if (cmd.args) {
|
|
231
|
+
if (cmd.args.includes("<file>")) {
|
|
232
|
+
properties.file = {
|
|
233
|
+
type: "string",
|
|
234
|
+
description: "SpecVerse specification file path"
|
|
235
|
+
};
|
|
236
|
+
required.push("file");
|
|
237
|
+
}
|
|
238
|
+
if (cmd.args.includes("[name]")) {
|
|
239
|
+
properties.name = {
|
|
240
|
+
type: "string",
|
|
241
|
+
description: "Project name (optional)"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (cmd.command === "init") {
|
|
246
|
+
properties.name = {
|
|
247
|
+
type: "string",
|
|
248
|
+
description: "Project name"
|
|
249
|
+
};
|
|
250
|
+
properties.template = {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: 'Template to use (default: "default")'
|
|
253
|
+
};
|
|
254
|
+
properties.zip = {
|
|
255
|
+
type: "boolean",
|
|
256
|
+
description: "Return project as ZIP file"
|
|
257
|
+
};
|
|
258
|
+
properties.json = {
|
|
259
|
+
type: "boolean",
|
|
260
|
+
description: "Return project as JSON file structure"
|
|
261
|
+
};
|
|
262
|
+
} else if (cmd.command === "validate" || cmd.command === "infer") {
|
|
263
|
+
properties.verbose = {
|
|
264
|
+
type: "boolean",
|
|
265
|
+
description: "Show detailed output"
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (this.isFileGeneratingCommand(cmd.command)) {
|
|
269
|
+
properties.zip = {
|
|
270
|
+
type: "boolean",
|
|
271
|
+
description: "Return files as ZIP package instead of creating locally (useful for remote servers or consistent delivery)"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
name: `specverse_${cmd.command}`,
|
|
276
|
+
description: cmd.description,
|
|
277
|
+
inputSchema: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties,
|
|
280
|
+
required
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Check if a command generates files that could benefit from ZIP packaging
|
|
286
|
+
*/
|
|
287
|
+
isFileGeneratingCommand(command) {
|
|
288
|
+
return ["init"].includes(command);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Check if a grouped command generates files that could benefit from ZIP packaging
|
|
292
|
+
*/
|
|
293
|
+
isFileGeneratingGroupedCommand(cmd) {
|
|
294
|
+
if (cmd.group === "gen") {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Create MCP tool definition from grouped command
|
|
301
|
+
*/
|
|
302
|
+
createMCPToolFromGroupedCommand(cmd) {
|
|
303
|
+
const properties = {};
|
|
304
|
+
const required = [];
|
|
305
|
+
if (cmd.args?.includes("<file>")) {
|
|
306
|
+
properties.file = {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "SpecVerse specification file path"
|
|
309
|
+
};
|
|
310
|
+
required.push("file");
|
|
311
|
+
}
|
|
312
|
+
if (cmd.group === "gen") {
|
|
313
|
+
properties.output = {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Output file or directory path"
|
|
316
|
+
};
|
|
317
|
+
} else if (cmd.group === "dev" && cmd.subcommand === "watch") {
|
|
318
|
+
properties.directory = {
|
|
319
|
+
type: "string",
|
|
320
|
+
description: "Directory to watch for changes"
|
|
321
|
+
};
|
|
322
|
+
} else if (cmd.group === "lib") {
|
|
323
|
+
if (cmd.subcommand === "search") {
|
|
324
|
+
if (cmd.args?.includes("[query]")) {
|
|
325
|
+
properties.query = {
|
|
326
|
+
type: "string",
|
|
327
|
+
description: "Search term (name, description, tags)"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
properties.type = {
|
|
331
|
+
type: "string",
|
|
332
|
+
enum: ["component", "deployment", "manifest"],
|
|
333
|
+
description: "Filter by type"
|
|
334
|
+
};
|
|
335
|
+
properties.category = {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Filter by category"
|
|
338
|
+
};
|
|
339
|
+
properties.tags = {
|
|
340
|
+
type: "string",
|
|
341
|
+
description: "Filter by tags (comma-separated: auth,jwt,session)"
|
|
342
|
+
};
|
|
343
|
+
properties.limit = {
|
|
344
|
+
type: "number",
|
|
345
|
+
description: "Results limit (default: 20)"
|
|
346
|
+
};
|
|
347
|
+
properties.format = {
|
|
348
|
+
type: "string",
|
|
349
|
+
enum: ["table", "json"],
|
|
350
|
+
description: "Output format (default: table)"
|
|
351
|
+
};
|
|
352
|
+
} else if (cmd.subcommand === "info") {
|
|
353
|
+
if (cmd.args?.includes("<name>")) {
|
|
354
|
+
properties.name = {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Library name"
|
|
357
|
+
};
|
|
358
|
+
required.push("name");
|
|
359
|
+
}
|
|
360
|
+
properties.version = {
|
|
361
|
+
type: "string",
|
|
362
|
+
description: "Specific version (default: latest)"
|
|
363
|
+
};
|
|
364
|
+
properties.format = {
|
|
365
|
+
type: "string",
|
|
366
|
+
enum: ["table", "json"],
|
|
367
|
+
description: "Output format (default: table)"
|
|
368
|
+
};
|
|
369
|
+
properties.content = {
|
|
370
|
+
type: "boolean",
|
|
371
|
+
description: "Show library content"
|
|
372
|
+
};
|
|
373
|
+
} else if (cmd.subcommand === "list") {
|
|
374
|
+
properties.cached = {
|
|
375
|
+
type: "boolean",
|
|
376
|
+
description: "Show locally cached libraries"
|
|
377
|
+
};
|
|
378
|
+
properties.used = {
|
|
379
|
+
type: "boolean",
|
|
380
|
+
description: "Show libraries used in current project"
|
|
381
|
+
};
|
|
382
|
+
properties.format = {
|
|
383
|
+
type: "string",
|
|
384
|
+
enum: ["table", "json"],
|
|
385
|
+
description: "Output format (default: table)"
|
|
386
|
+
};
|
|
387
|
+
} else if (cmd.subcommand === "tags") {
|
|
388
|
+
properties.format = {
|
|
389
|
+
type: "string",
|
|
390
|
+
enum: ["table", "json"],
|
|
391
|
+
description: "Output format (default: table)"
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (this.isFileGeneratingGroupedCommand(cmd)) {
|
|
396
|
+
properties.zip = {
|
|
397
|
+
type: "boolean",
|
|
398
|
+
description: "Return files as ZIP package instead of creating locally (useful for remote servers or consistent delivery)"
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
name: `specverse_${cmd.command.replace(/\s+/g, "_")}`,
|
|
403
|
+
description: cmd.description,
|
|
404
|
+
inputSchema: {
|
|
405
|
+
type: "object",
|
|
406
|
+
properties,
|
|
407
|
+
required
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Create MCP tools for AI commands
|
|
413
|
+
*/
|
|
414
|
+
createAIMCPTools() {
|
|
415
|
+
return [
|
|
416
|
+
{
|
|
417
|
+
name: "specverse_ai_template",
|
|
418
|
+
description: "Get AI prompt templates for SpecVerse operations",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
type: "object",
|
|
421
|
+
properties: {
|
|
422
|
+
operation: {
|
|
423
|
+
type: "string",
|
|
424
|
+
enum: ["analyse", "create", "materialise", "realize"],
|
|
425
|
+
description: "AI operation type"
|
|
426
|
+
},
|
|
427
|
+
pver: {
|
|
428
|
+
type: "string",
|
|
429
|
+
description: "Prompt version (v1|v2|v3|v4|v5|v6|v7) (default: v1)"
|
|
430
|
+
},
|
|
431
|
+
output: {
|
|
432
|
+
type: "string",
|
|
433
|
+
description: "Output file path"
|
|
434
|
+
},
|
|
435
|
+
copy: {
|
|
436
|
+
type: "boolean",
|
|
437
|
+
description: "Copy result to clipboard"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
required: ["operation"]
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "specverse_ai_fill",
|
|
445
|
+
description: "Fill AI prompt templates with requirements",
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: "object",
|
|
448
|
+
properties: {
|
|
449
|
+
operation: {
|
|
450
|
+
type: "string",
|
|
451
|
+
enum: ["analyse", "create", "materialise", "realize"],
|
|
452
|
+
description: "AI operation type"
|
|
453
|
+
},
|
|
454
|
+
requirements: {
|
|
455
|
+
type: "string",
|
|
456
|
+
description: "Project requirements"
|
|
457
|
+
},
|
|
458
|
+
scale: {
|
|
459
|
+
type: "string",
|
|
460
|
+
enum: ["personal", "business", "enterprise"],
|
|
461
|
+
description: "Project scale (default: business)"
|
|
462
|
+
},
|
|
463
|
+
framework: {
|
|
464
|
+
type: "string",
|
|
465
|
+
description: "Framework preference"
|
|
466
|
+
},
|
|
467
|
+
domain: {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "Project domain"
|
|
470
|
+
},
|
|
471
|
+
compliance: {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "Compliance requirements (comma-separated)"
|
|
474
|
+
},
|
|
475
|
+
tech: {
|
|
476
|
+
type: "string",
|
|
477
|
+
description: "Technology preferences (comma-separated)"
|
|
478
|
+
},
|
|
479
|
+
pver: {
|
|
480
|
+
type: "string",
|
|
481
|
+
description: "Prompt version (v1|v2|v3|v4|v5|v6|v7) (default: v1)"
|
|
482
|
+
},
|
|
483
|
+
output: {
|
|
484
|
+
type: "string",
|
|
485
|
+
description: "Output file path"
|
|
486
|
+
},
|
|
487
|
+
copy: {
|
|
488
|
+
type: "boolean",
|
|
489
|
+
description: "Copy result to clipboard"
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
required: ["operation", "requirements"]
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "specverse_ai_suggest",
|
|
497
|
+
description: "Get AI library suggestions for project requirements",
|
|
498
|
+
inputSchema: {
|
|
499
|
+
type: "object",
|
|
500
|
+
properties: {
|
|
501
|
+
requirements: {
|
|
502
|
+
type: "string",
|
|
503
|
+
description: "Project requirements"
|
|
504
|
+
},
|
|
505
|
+
domain: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "Project domain"
|
|
508
|
+
},
|
|
509
|
+
scale: {
|
|
510
|
+
type: "string",
|
|
511
|
+
enum: ["personal", "business", "enterprise"],
|
|
512
|
+
default: "business",
|
|
513
|
+
description: "Project scale"
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
required: ["requirements"]
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: "specverse_ai_enhance",
|
|
521
|
+
description: "Get enhanced AI prompts with library context (BEST)",
|
|
522
|
+
inputSchema: {
|
|
523
|
+
type: "object",
|
|
524
|
+
properties: {
|
|
525
|
+
operation: {
|
|
526
|
+
type: "string",
|
|
527
|
+
enum: ["analyse", "create", "materialise", "realize"],
|
|
528
|
+
description: "AI operation type"
|
|
529
|
+
},
|
|
530
|
+
requirements: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description: "Project requirements"
|
|
533
|
+
},
|
|
534
|
+
scale: {
|
|
535
|
+
type: "string",
|
|
536
|
+
enum: ["personal", "business", "enterprise"],
|
|
537
|
+
description: "Project scale (default: business)"
|
|
538
|
+
},
|
|
539
|
+
framework: {
|
|
540
|
+
type: "string",
|
|
541
|
+
description: "Framework preference"
|
|
542
|
+
},
|
|
543
|
+
domain: {
|
|
544
|
+
type: "string",
|
|
545
|
+
description: "Project domain"
|
|
546
|
+
},
|
|
547
|
+
compliance: {
|
|
548
|
+
type: "string",
|
|
549
|
+
description: "Compliance requirements (comma-separated)"
|
|
550
|
+
},
|
|
551
|
+
tech: {
|
|
552
|
+
type: "string",
|
|
553
|
+
description: "Technology preferences (comma-separated)"
|
|
554
|
+
},
|
|
555
|
+
pver: {
|
|
556
|
+
type: "string",
|
|
557
|
+
description: "Prompt version (v1|v2|v3|v4|v5|v6|v7) (default: v1)"
|
|
558
|
+
},
|
|
559
|
+
output: {
|
|
560
|
+
type: "string",
|
|
561
|
+
description: "Output file path"
|
|
562
|
+
},
|
|
563
|
+
copy: {
|
|
564
|
+
type: "boolean",
|
|
565
|
+
description: "Copy result to clipboard"
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
required: ["operation", "requirements"]
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
];
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Create content-based tools for direct LLM interaction
|
|
575
|
+
*/
|
|
576
|
+
createContentBasedTools() {
|
|
577
|
+
return [
|
|
578
|
+
{
|
|
579
|
+
name: "specverse_validate_content",
|
|
580
|
+
description: "Validate SpecVerse specification content directly (no file required)",
|
|
581
|
+
inputSchema: {
|
|
582
|
+
type: "object",
|
|
583
|
+
properties: {
|
|
584
|
+
content: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "SpecVerse specification content (.specly format)"
|
|
587
|
+
},
|
|
588
|
+
filename: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "Optional filename for error reporting (default: temp.specly)"
|
|
591
|
+
},
|
|
592
|
+
verbose: {
|
|
593
|
+
type: "boolean",
|
|
594
|
+
description: "Show detailed validation results"
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
required: ["content"]
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: "specverse_infer_content",
|
|
602
|
+
description: "Generate complete specification from content using AI inference",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
content: {
|
|
607
|
+
type: "string",
|
|
608
|
+
description: "Minimal SpecVerse specification content (.specly format)"
|
|
609
|
+
},
|
|
610
|
+
filename: {
|
|
611
|
+
type: "string",
|
|
612
|
+
description: "Optional filename (default: temp.specly)"
|
|
613
|
+
},
|
|
614
|
+
controllers: {
|
|
615
|
+
type: "boolean",
|
|
616
|
+
description: "Generate controllers (default: true)"
|
|
617
|
+
},
|
|
618
|
+
services: {
|
|
619
|
+
type: "boolean",
|
|
620
|
+
description: "Generate services (default: true)"
|
|
621
|
+
},
|
|
622
|
+
events: {
|
|
623
|
+
type: "boolean",
|
|
624
|
+
description: "Generate events (default: true)"
|
|
625
|
+
},
|
|
626
|
+
views: {
|
|
627
|
+
type: "boolean",
|
|
628
|
+
description: "Generate views (default: true)"
|
|
629
|
+
},
|
|
630
|
+
deployment: {
|
|
631
|
+
type: "boolean",
|
|
632
|
+
description: "Generate deployment specification (default: false)"
|
|
633
|
+
},
|
|
634
|
+
environment: {
|
|
635
|
+
type: "string",
|
|
636
|
+
enum: ["development", "staging", "production"],
|
|
637
|
+
description: "Target environment for deployment"
|
|
638
|
+
},
|
|
639
|
+
verbose: {
|
|
640
|
+
type: "boolean",
|
|
641
|
+
description: "Show detailed inference process"
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
required: ["content"]
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: "specverse_gen_yaml_content",
|
|
649
|
+
description: "Generate YAML from SpecVerse specification content",
|
|
650
|
+
inputSchema: {
|
|
651
|
+
type: "object",
|
|
652
|
+
properties: {
|
|
653
|
+
content: {
|
|
654
|
+
type: "string",
|
|
655
|
+
description: "SpecVerse specification content (.specly format)"
|
|
656
|
+
},
|
|
657
|
+
filename: {
|
|
658
|
+
type: "string",
|
|
659
|
+
description: "Optional filename (default: temp.specly)"
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
required: ["content"]
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
];
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Handle content-based commands by creating temporary files
|
|
669
|
+
*/
|
|
670
|
+
async handleContentBasedCommand(toolName, args) {
|
|
671
|
+
const { content, filename = "temp.specly", ...otherArgs } = args;
|
|
672
|
+
if (!content) {
|
|
673
|
+
return {
|
|
674
|
+
content: [{
|
|
675
|
+
type: "text",
|
|
676
|
+
text: JSON.stringify({
|
|
677
|
+
status: "error",
|
|
678
|
+
message: "Content is required for content-based commands",
|
|
679
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
680
|
+
}, null, 2)
|
|
681
|
+
}],
|
|
682
|
+
isError: true
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
let tempDir;
|
|
686
|
+
let tempFile;
|
|
687
|
+
try {
|
|
688
|
+
tempDir = mkdtempSync(join(tmpdir(), "specverse-mcp-"));
|
|
689
|
+
tempFile = join(tempDir, filename);
|
|
690
|
+
writeFileSync(tempFile, content, "utf8");
|
|
691
|
+
const cliCommand = this.mapContentToolToCommand(toolName);
|
|
692
|
+
const result = await specverseAPI.executeCliCommand(`${cliCommand} "${tempFile}"`, otherArgs, {
|
|
693
|
+
cliPath: this.cliPath,
|
|
694
|
+
timeout: 6e4
|
|
695
|
+
});
|
|
696
|
+
return {
|
|
697
|
+
content: [{
|
|
698
|
+
type: "text",
|
|
699
|
+
text: JSON.stringify(result, null, 2)
|
|
700
|
+
}]
|
|
701
|
+
};
|
|
702
|
+
} catch (error) {
|
|
703
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
704
|
+
try {
|
|
705
|
+
const parsedError = JSON.parse(errorMessage);
|
|
706
|
+
return {
|
|
707
|
+
content: [{
|
|
708
|
+
type: "text",
|
|
709
|
+
text: JSON.stringify(parsedError, null, 2)
|
|
710
|
+
}],
|
|
711
|
+
isError: true
|
|
712
|
+
};
|
|
713
|
+
} catch {
|
|
714
|
+
return {
|
|
715
|
+
content: [{
|
|
716
|
+
type: "text",
|
|
717
|
+
text: JSON.stringify({
|
|
718
|
+
status: "error",
|
|
719
|
+
message: "Content-based command execution failed",
|
|
720
|
+
details: errorMessage,
|
|
721
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
722
|
+
}, null, 2)
|
|
723
|
+
}],
|
|
724
|
+
isError: true
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
} finally {
|
|
728
|
+
try {
|
|
729
|
+
if (tempFile) unlinkSync(tempFile);
|
|
730
|
+
} catch {
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Map content-based tool names to CLI commands
|
|
736
|
+
*/
|
|
737
|
+
mapContentToolToCommand(toolName) {
|
|
738
|
+
const mapping = {
|
|
739
|
+
"specverse_validate_content": "validate",
|
|
740
|
+
"specverse_infer_content": "infer",
|
|
741
|
+
"specverse_gen_yaml_content": "gen yaml"
|
|
742
|
+
};
|
|
743
|
+
return mapping[toolName] || toolName.replace(/^specverse_/, "").replace(/_content$/, "").replace(/_/g, " ");
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Check if we're running in a remote environment where local file creation won't work
|
|
747
|
+
*/
|
|
748
|
+
async isRemoteEnvironment() {
|
|
749
|
+
if (process.env.MCP_FORCE_LOCAL === "true") {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
return (
|
|
753
|
+
// Vercel/Netlify/similar serverless
|
|
754
|
+
process.env.VERCEL === "1" || process.env.NETLIFY === "true" || process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0 || // Web deployment mode indicators
|
|
755
|
+
process.env.NODE_ENV === "production" && (process.env.PORT !== void 0 || process.env.HOST !== void 0) || // Environment explicitly marked as remote
|
|
756
|
+
process.env.MCP_REMOTE === "true" || // Detect if we can't write to current directory (more reliable than just checking root)
|
|
757
|
+
await this.isDirectoryReadOnly()
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Check if we should use remote environment detection specifically for root directory
|
|
762
|
+
* Only triggers if running from root AND no full paths are provided
|
|
763
|
+
*/
|
|
764
|
+
isProblematicRootEnvironment(args) {
|
|
765
|
+
if (process.cwd() !== "/") {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
const hasFullPaths = args.name && args.name.startsWith("/") || // /full/path/project
|
|
769
|
+
args.file && args.file.startsWith("/") || // /full/path/file.specly
|
|
770
|
+
args.output && args.output.startsWith("/") || // /full/path/output
|
|
771
|
+
args.directory && args.directory.startsWith("/");
|
|
772
|
+
if (hasFullPaths) {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
const hasRelativeName = args.name && !args.name.startsWith("/");
|
|
776
|
+
return hasRelativeName;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Check if current directory is read-only
|
|
780
|
+
*/
|
|
781
|
+
async isDirectoryReadOnly() {
|
|
782
|
+
try {
|
|
783
|
+
const { writeFileSync: writeFileSync2, unlinkSync: unlinkSync2 } = await import("fs");
|
|
784
|
+
const testFile = ".mcp-write-test-" + Date.now();
|
|
785
|
+
writeFileSync2(testFile, "test");
|
|
786
|
+
unlinkSync2(testFile);
|
|
787
|
+
return false;
|
|
788
|
+
} catch {
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Read project files into structured format for direct delivery
|
|
794
|
+
*/
|
|
795
|
+
async readProjectFilesStructured(projectPath) {
|
|
796
|
+
const { readdir, readFile, stat } = await import("fs/promises");
|
|
797
|
+
const { join: join2, relative, extname } = await import("path");
|
|
798
|
+
const files = [];
|
|
799
|
+
let totalSize = 0;
|
|
800
|
+
const directories = [];
|
|
801
|
+
const readDirectory = async (dirPath) => {
|
|
802
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
803
|
+
for (const entry of entries) {
|
|
804
|
+
const fullPath = join2(dirPath, entry.name);
|
|
805
|
+
const relativePath = relative(projectPath, fullPath);
|
|
806
|
+
if (entry.isDirectory()) {
|
|
807
|
+
directories.push(relativePath + "/");
|
|
808
|
+
await readDirectory(fullPath);
|
|
809
|
+
} else if (entry.isFile()) {
|
|
810
|
+
try {
|
|
811
|
+
const content = await readFile(fullPath, "utf8");
|
|
812
|
+
const stats = await stat(fullPath);
|
|
813
|
+
const extension = extname(entry.name).toLowerCase();
|
|
814
|
+
let fileType = "text";
|
|
815
|
+
if (extension === ".js" || extension === ".ts") fileType = "javascript";
|
|
816
|
+
else if (extension === ".py") fileType = "python";
|
|
817
|
+
else if (extension === ".md") fileType = "markdown";
|
|
818
|
+
else if (extension === ".json") fileType = "json";
|
|
819
|
+
else if (extension === ".yaml" || extension === ".yml") fileType = "yaml";
|
|
820
|
+
else if (extension === ".specly") fileType = "specverse";
|
|
821
|
+
else if (extension === ".sh") fileType = "shell";
|
|
822
|
+
else if (extension === ".html") fileType = "html";
|
|
823
|
+
else if (extension === ".css") fileType = "css";
|
|
824
|
+
files.push({
|
|
825
|
+
path: relativePath,
|
|
826
|
+
content,
|
|
827
|
+
type: fileType,
|
|
828
|
+
size: stats.size,
|
|
829
|
+
encoding: "utf-8"
|
|
830
|
+
});
|
|
831
|
+
totalSize += stats.size;
|
|
832
|
+
} catch (error) {
|
|
833
|
+
console.error(`Failed to read file ${fullPath}:`, error);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
await readDirectory(projectPath);
|
|
839
|
+
return {
|
|
840
|
+
files,
|
|
841
|
+
summary: {
|
|
842
|
+
total_files: files.length,
|
|
843
|
+
total_size: totalSize < 1024 ? `${totalSize}B` : totalSize < 1024 * 1024 ? `${(totalSize / 1024).toFixed(1)}KB` : `${(totalSize / 1024 / 1024).toFixed(2)}MB`,
|
|
844
|
+
directories: [...new Set(directories)].sort(),
|
|
845
|
+
file_types: [...new Set(files.map((f) => f.type))].sort()
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Package project files into ZIP for remote delivery (DEPRECATED - keeping for fallback)
|
|
851
|
+
*/
|
|
852
|
+
async packageProjectFiles(projectPath) {
|
|
853
|
+
const { execSync } = await import("child_process");
|
|
854
|
+
const { readFile, unlink } = await import("fs/promises");
|
|
855
|
+
const { basename, dirname, join: join2 } = await import("path");
|
|
856
|
+
const { tmpdir: tmpdir2 } = await import("os");
|
|
857
|
+
const projectName = basename(projectPath);
|
|
858
|
+
const parentDir = dirname(projectPath);
|
|
859
|
+
const tempZipPath = join2(tmpdir2(), `${projectName}-${Date.now()}.zip`);
|
|
860
|
+
console.error(`MCP Packaging Debug: Project path: ${projectPath}`);
|
|
861
|
+
console.error(`MCP Packaging Debug: Project name: ${projectName}`);
|
|
862
|
+
console.error(`MCP Packaging Debug: Parent dir: ${parentDir}`);
|
|
863
|
+
console.error(`MCP Packaging Debug: Temp ZIP path: ${tempZipPath}`);
|
|
864
|
+
try {
|
|
865
|
+
const zipCommand = `cd "${parentDir}" && zip -r "${tempZipPath}" "${projectName}"`;
|
|
866
|
+
console.error(`MCP Packaging Debug: Running command: ${zipCommand}`);
|
|
867
|
+
execSync(zipCommand, {
|
|
868
|
+
encoding: "utf8",
|
|
869
|
+
stdio: "pipe"
|
|
870
|
+
});
|
|
871
|
+
console.error(`MCP Packaging Debug: ZIP file created successfully`);
|
|
872
|
+
const zipBuffer = await readFile(tempZipPath);
|
|
873
|
+
console.error(`MCP Packaging Debug: ZIP buffer size: ${zipBuffer.length} bytes`);
|
|
874
|
+
console.error(`MCP Packaging Debug: ZIP buffer size: ${(zipBuffer.length / 1024 / 1024).toFixed(2)} MB`);
|
|
875
|
+
const zipData = zipBuffer.toString("base64");
|
|
876
|
+
console.error(`MCP Packaging Debug: Base64 string length: ${zipData.length} chars`);
|
|
877
|
+
await unlink(tempZipPath);
|
|
878
|
+
console.error(`MCP Packaging Debug: Temp file cleaned up`);
|
|
879
|
+
return {
|
|
880
|
+
zipData,
|
|
881
|
+
fileName: `${projectName}.zip`,
|
|
882
|
+
size: zipBuffer.length
|
|
883
|
+
};
|
|
884
|
+
} catch (error) {
|
|
885
|
+
console.error(`MCP Packaging Debug: ZIP command failed, trying fallback:`, error);
|
|
886
|
+
return await this.createZipFallback(projectPath);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Fallback ZIP creation using pure Node.js
|
|
891
|
+
*/
|
|
892
|
+
async createZipFallback(projectPath) {
|
|
893
|
+
const { execSync } = await import("child_process");
|
|
894
|
+
const { readFile, unlink } = await import("fs/promises");
|
|
895
|
+
const { basename, dirname, join: join2 } = await import("path");
|
|
896
|
+
const { tmpdir: tmpdir2 } = await import("os");
|
|
897
|
+
const projectName = basename(projectPath);
|
|
898
|
+
const parentDir = dirname(projectPath);
|
|
899
|
+
const tempTarPath = join2(tmpdir2(), `${projectName}-${Date.now()}.tar.gz`);
|
|
900
|
+
try {
|
|
901
|
+
execSync(`cd "${parentDir}" && tar -czf "${tempTarPath}" "${projectName}"`, {
|
|
902
|
+
encoding: "utf8",
|
|
903
|
+
stdio: "pipe"
|
|
904
|
+
});
|
|
905
|
+
const tarBuffer = await readFile(tempTarPath);
|
|
906
|
+
const tarData = tarBuffer.toString("base64");
|
|
907
|
+
await unlink(tempTarPath);
|
|
908
|
+
return {
|
|
909
|
+
zipData: tarData,
|
|
910
|
+
fileName: `${projectName}.tar.gz`,
|
|
911
|
+
size: tarBuffer.length
|
|
912
|
+
};
|
|
913
|
+
} catch (error) {
|
|
914
|
+
throw new Error(`Failed to create project archive: ${error}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Execute init command without --json flag (not supported)
|
|
919
|
+
* For remote deployments, returns project files as JSON structure
|
|
920
|
+
*/
|
|
921
|
+
async executeInitCommand(command, args) {
|
|
922
|
+
const { execSync } = await import("child_process");
|
|
923
|
+
const argString = Object.entries(args).filter(
|
|
924
|
+
([key, value]) => value !== void 0 && value !== null && key !== "zip" && // ZIP is handled by MCP service, not CLI
|
|
925
|
+
key !== "local" && // Local is MCP server flag, not CLI flag
|
|
926
|
+
key !== "fullPath" && // fullPath is used internally for directory handling
|
|
927
|
+
key !== "name"
|
|
928
|
+
// name is passed as positional argument, not flag
|
|
929
|
+
).map(([key, value]) => {
|
|
930
|
+
if (typeof value === "boolean") {
|
|
931
|
+
return value ? `--${key}` : "";
|
|
932
|
+
} else {
|
|
933
|
+
return `--${key} "${value}"`;
|
|
934
|
+
}
|
|
935
|
+
}).filter(Boolean).join(" ");
|
|
936
|
+
const baseCommand = this.cliPath === "specverse" ? "specverse" : `node ${this.cliPath}`;
|
|
937
|
+
const fullCommand = `${baseCommand} ${command} ${argString}`.trim();
|
|
938
|
+
const rawName = args.fullPath || command.match(/"([^"]+)"/)?.[1] || args.name || "project";
|
|
939
|
+
let projectName;
|
|
940
|
+
let targetWorkingDir;
|
|
941
|
+
if (rawName.startsWith("/")) {
|
|
942
|
+
const { dirname, basename } = await import("path");
|
|
943
|
+
projectName = basename(rawName);
|
|
944
|
+
targetWorkingDir = dirname(rawName);
|
|
945
|
+
} else {
|
|
946
|
+
projectName = rawName;
|
|
947
|
+
targetWorkingDir = process.cwd();
|
|
948
|
+
}
|
|
949
|
+
const forceZip = args.zip === true;
|
|
950
|
+
const forceJson = args.json === true;
|
|
951
|
+
console.error(`MCP Init Debug: Working directory: ${process.cwd()}`);
|
|
952
|
+
console.error(`MCP Init Debug: Target working directory: ${targetWorkingDir}`);
|
|
953
|
+
console.error(`MCP Init Debug: Raw name: ${rawName}`);
|
|
954
|
+
console.error(`MCP Init Debug: Project name: ${projectName}`);
|
|
955
|
+
console.error(`MCP Init Debug: Force ZIP: ${forceZip}`);
|
|
956
|
+
console.error(`MCP Init Debug: Force JSON: ${forceJson}`);
|
|
957
|
+
console.error(`MCP Init Debug: Executing command: ${fullCommand}`);
|
|
958
|
+
if (forceZip) {
|
|
959
|
+
console.error(`MCP Init Debug: ZIP requested - will return ZIP file`);
|
|
960
|
+
return await this.executeInitWithZip(fullCommand, projectName, args);
|
|
961
|
+
} else if (forceJson) {
|
|
962
|
+
console.error(`MCP Init Debug: JSON requested - will return JSON structure`);
|
|
963
|
+
return await this.executeInitWithJson(fullCommand, projectName, args);
|
|
964
|
+
} else if (await this.canCreateLocalFiles(projectName, targetWorkingDir)) {
|
|
965
|
+
console.error(`MCP Init Debug: Local filesystem - will create files locally`);
|
|
966
|
+
return await this.executeInitLocal(fullCommand, projectName, targetWorkingDir);
|
|
967
|
+
} else {
|
|
968
|
+
console.error(`MCP Init Debug: Cannot create local files - will return JSON structure`);
|
|
969
|
+
return await this.executeInitWithJson(fullCommand, projectName, args);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
async canCreateLocalFiles(projectName, targetWorkingDir) {
|
|
973
|
+
try {
|
|
974
|
+
const targetDir = targetWorkingDir || await (async () => {
|
|
975
|
+
const cwd = process.cwd();
|
|
976
|
+
if (projectName.startsWith("/")) {
|
|
977
|
+
const { dirname } = await import("path");
|
|
978
|
+
return dirname(projectName);
|
|
979
|
+
} else {
|
|
980
|
+
if (cwd === "/") {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
return cwd;
|
|
984
|
+
}
|
|
985
|
+
})();
|
|
986
|
+
if (!targetDir) {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
const { writeFileSync: writeFileSync2, unlinkSync: unlinkSync2 } = await import("fs");
|
|
990
|
+
const { join: join2 } = await import("path");
|
|
991
|
+
const testFile = join2(targetDir, ".mcp-write-test-" + Date.now());
|
|
992
|
+
writeFileSync2(testFile, "test");
|
|
993
|
+
unlinkSync2(testFile);
|
|
994
|
+
return true;
|
|
995
|
+
} catch {
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
async executeInitLocal(fullCommand, projectName, targetWorkingDir) {
|
|
1000
|
+
const { execSync } = await import("child_process");
|
|
1001
|
+
const { join: join2 } = await import("path");
|
|
1002
|
+
const workingDir = targetWorkingDir || process.cwd();
|
|
1003
|
+
try {
|
|
1004
|
+
const result = execSync(fullCommand, {
|
|
1005
|
+
encoding: "utf8",
|
|
1006
|
+
timeout: 3e4,
|
|
1007
|
+
stdio: "pipe",
|
|
1008
|
+
cwd: workingDir
|
|
1009
|
+
});
|
|
1010
|
+
return {
|
|
1011
|
+
status: "success",
|
|
1012
|
+
message: `Project '${projectName}' created successfully`,
|
|
1013
|
+
project_path: join2(workingDir, projectName),
|
|
1014
|
+
delivery_method: "local_filesystem",
|
|
1015
|
+
created: true
|
|
1016
|
+
};
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
const errorOutput = error.stdout || error.stderr || error.message;
|
|
1019
|
+
throw new Error(`Failed to create project '${projectName}': ${errorOutput}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async executeInitWithZip(fullCommand, projectName, args) {
|
|
1023
|
+
const { execSync } = await import("child_process");
|
|
1024
|
+
const { mkdtemp, rm } = await import("fs/promises");
|
|
1025
|
+
const { tmpdir: tmpdir2 } = await import("os");
|
|
1026
|
+
const { join: join2 } = await import("path");
|
|
1027
|
+
const tempDir = await mkdtemp(join2(tmpdir2(), "specverse-zip-"));
|
|
1028
|
+
try {
|
|
1029
|
+
const result = execSync(fullCommand, {
|
|
1030
|
+
encoding: "utf8",
|
|
1031
|
+
timeout: 3e4,
|
|
1032
|
+
stdio: "pipe",
|
|
1033
|
+
cwd: tempDir
|
|
1034
|
+
});
|
|
1035
|
+
const projectPath = join2(tempDir, projectName);
|
|
1036
|
+
const zipData = await this.packageProjectFiles(projectPath);
|
|
1037
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
1038
|
+
return {
|
|
1039
|
+
status: "success",
|
|
1040
|
+
message: `Project '${projectName}' packaged as ZIP`,
|
|
1041
|
+
delivery_method: "zip_file",
|
|
1042
|
+
zip_data: zipData.zipData,
|
|
1043
|
+
file_name: zipData.fileName,
|
|
1044
|
+
size: zipData.size
|
|
1045
|
+
};
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
1048
|
+
});
|
|
1049
|
+
const errorOutput = error.stdout || error.stderr || error.message;
|
|
1050
|
+
throw new Error(`Failed to create project '${projectName}': ${errorOutput}`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
async executeInitWithJson(fullCommand, projectName, args) {
|
|
1054
|
+
const { execSync } = await import("child_process");
|
|
1055
|
+
const { mkdtemp, rm } = await import("fs/promises");
|
|
1056
|
+
const { tmpdir: tmpdir2 } = await import("os");
|
|
1057
|
+
const { join: join2 } = await import("path");
|
|
1058
|
+
const tempDir = await mkdtemp(join2(tmpdir2(), "specverse-json-"));
|
|
1059
|
+
try {
|
|
1060
|
+
const result = execSync(fullCommand, {
|
|
1061
|
+
encoding: "utf8",
|
|
1062
|
+
timeout: 3e4,
|
|
1063
|
+
stdio: "pipe",
|
|
1064
|
+
cwd: tempDir
|
|
1065
|
+
});
|
|
1066
|
+
const projectPath = join2(tempDir, projectName);
|
|
1067
|
+
const structuredFiles = await this.readProjectFilesStructured(projectPath);
|
|
1068
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
1069
|
+
return {
|
|
1070
|
+
status: "success",
|
|
1071
|
+
instruction: "create_file_attachments_for_each_file_described_in_files",
|
|
1072
|
+
message: `Project '${projectName}' created successfully`,
|
|
1073
|
+
project: projectName,
|
|
1074
|
+
files: structuredFiles.files,
|
|
1075
|
+
summary: structuredFiles.summary,
|
|
1076
|
+
delivery_method: "json_structure"
|
|
1077
|
+
};
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
1080
|
+
});
|
|
1081
|
+
const errorOutput = error.stdout || error.stderr || error.message;
|
|
1082
|
+
throw new Error(`Failed to create project '${projectName}': ${errorOutput}`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Map MCP tool name back to CLI command
|
|
1087
|
+
*/
|
|
1088
|
+
mapToolNameToCommand(toolName) {
|
|
1089
|
+
const command = toolName.replace(/^specverse_/, "").replace(/_/g, " ");
|
|
1090
|
+
if (command.startsWith("ai ")) {
|
|
1091
|
+
return command;
|
|
1092
|
+
}
|
|
1093
|
+
return command;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Get MCP package version using Node.js built-in package resolution
|
|
1097
|
+
*/
|
|
1098
|
+
async getMCPVersion() {
|
|
1099
|
+
try {
|
|
1100
|
+
const { readFile } = await import("fs/promises");
|
|
1101
|
+
const { join: join2, dirname } = await import("path");
|
|
1102
|
+
const { fileURLToPath } = await import("url");
|
|
1103
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1104
|
+
const __dirname = dirname(__filename);
|
|
1105
|
+
const packagePaths = [
|
|
1106
|
+
join2(__dirname, "..", "..", "package.json"),
|
|
1107
|
+
// dist/services -> root
|
|
1108
|
+
join2(__dirname, "..", "..", "..", "package.json")
|
|
1109
|
+
// dist/target/services -> root
|
|
1110
|
+
];
|
|
1111
|
+
for (const packagePath of packagePaths) {
|
|
1112
|
+
try {
|
|
1113
|
+
const content = await readFile(packagePath, "utf8");
|
|
1114
|
+
const packageInfo = JSON.parse(content);
|
|
1115
|
+
if (packageInfo.name === "@specverse/mcp") {
|
|
1116
|
+
return packageInfo.version;
|
|
1117
|
+
}
|
|
1118
|
+
} catch {
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
try {
|
|
1123
|
+
const { createRequire } = await import("module");
|
|
1124
|
+
const require2 = createRequire(import.meta.url);
|
|
1125
|
+
const packageInfo = require2("@specverse/mcp/package.json");
|
|
1126
|
+
return packageInfo.version;
|
|
1127
|
+
} catch {
|
|
1128
|
+
try {
|
|
1129
|
+
const packageJsonUrl = import.meta.resolve("@specverse/mcp/package.json");
|
|
1130
|
+
const content = await readFile(new URL(packageJsonUrl), "utf8");
|
|
1131
|
+
const packageInfo = JSON.parse(content);
|
|
1132
|
+
return packageInfo.version;
|
|
1133
|
+
} catch {
|
|
1134
|
+
return process.env.MCP_VERSION || "unknown";
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
} catch {
|
|
1138
|
+
return process.env.MCP_VERSION || "unknown";
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Execute MCP-specific debug tool
|
|
1143
|
+
*/
|
|
1144
|
+
async executeMCPDebugTool(verbose) {
|
|
1145
|
+
try {
|
|
1146
|
+
const mcpVersion = await this.getMCPVersion();
|
|
1147
|
+
const diagnostics = {
|
|
1148
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1149
|
+
mcp_server: {
|
|
1150
|
+
version: mcpVersion,
|
|
1151
|
+
mode: "diagnostic"
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
try {
|
|
1155
|
+
const { resolvePackageRoot } = specverseAPI;
|
|
1156
|
+
const packageRoot = resolvePackageRoot();
|
|
1157
|
+
const { readFile } = await import("fs/promises");
|
|
1158
|
+
const { join: join2 } = await import("path");
|
|
1159
|
+
const packageJsonPath = join2(packageRoot, "package.json");
|
|
1160
|
+
const packageContent = await readFile(packageJsonPath, "utf8");
|
|
1161
|
+
const packageData = JSON.parse(packageContent);
|
|
1162
|
+
diagnostics.specverse_installation = {
|
|
1163
|
+
type: "npm_package",
|
|
1164
|
+
version: packageData.version,
|
|
1165
|
+
location: packageRoot,
|
|
1166
|
+
package_name: packageData.name,
|
|
1167
|
+
status: "found"
|
|
1168
|
+
};
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
diagnostics.specverse_installation = {
|
|
1171
|
+
status: "error",
|
|
1172
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
diagnostics.cli_detection = {
|
|
1176
|
+
cli_path: this.cliPath,
|
|
1177
|
+
working_directory: this.workingDirectory || process.cwd(),
|
|
1178
|
+
status: this.cliPath ? "found" : "not_found"
|
|
1179
|
+
};
|
|
1180
|
+
diagnostics.health_check = {
|
|
1181
|
+
can_import_specverse: !!diagnostics.specverse_installation.version,
|
|
1182
|
+
cli_accessible: !!this.cliPath,
|
|
1183
|
+
capabilities_loaded: !!this.capabilities,
|
|
1184
|
+
overall_status: !!diagnostics.specverse_installation.version && !!this.cliPath ? "healthy" : "degraded"
|
|
1185
|
+
};
|
|
1186
|
+
return {
|
|
1187
|
+
content: [{
|
|
1188
|
+
type: "text",
|
|
1189
|
+
text: JSON.stringify(diagnostics, null, 2)
|
|
1190
|
+
}]
|
|
1191
|
+
};
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
return {
|
|
1194
|
+
content: [{
|
|
1195
|
+
type: "text",
|
|
1196
|
+
text: JSON.stringify({
|
|
1197
|
+
status: "error",
|
|
1198
|
+
message: "MCP debug tool execution failed",
|
|
1199
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1200
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1201
|
+
}, null, 2)
|
|
1202
|
+
}],
|
|
1203
|
+
isError: true
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
export {
|
|
1209
|
+
CLIProxyService
|
|
1210
|
+
};
|