@apexmcp/cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +302 -0
- package/dist/chunk-BWPU5TUK.js +29 -0
- package/dist/chunk-BWPU5TUK.js.map +1 -0
- package/dist/cli.js +2600 -0
- package/dist/cli.js.map +1 -0
- package/dist/load-env-file-EUKRAC2D.js +8 -0
- package/dist/load-env-file-EUKRAC2D.js.map +1 -0
- package/package.json +97 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2600 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadEnvFile
|
|
4
|
+
} from "./chunk-BWPU5TUK.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/commands/deploy.ts
|
|
10
|
+
import { existsSync as existsSync3 } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
|
|
13
|
+
// src/utils/logger.ts
|
|
14
|
+
import { Logger } from "@apexmcp/logger";
|
|
15
|
+
var logger = new Logger();
|
|
16
|
+
|
|
17
|
+
// src/utils/cli-io.ts
|
|
18
|
+
function isJsonMode(options) {
|
|
19
|
+
return !!options?.json || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
20
|
+
}
|
|
21
|
+
function createLogger(options) {
|
|
22
|
+
if (isJsonMode(options)) {
|
|
23
|
+
return {
|
|
24
|
+
debug: () => {
|
|
25
|
+
},
|
|
26
|
+
info: () => {
|
|
27
|
+
},
|
|
28
|
+
warn: (message, ...args) => {
|
|
29
|
+
console.error(message, ...args);
|
|
30
|
+
},
|
|
31
|
+
error: (message, ...args) => {
|
|
32
|
+
console.error(message, ...args);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return logger;
|
|
37
|
+
}
|
|
38
|
+
function withStdoutSuppressed(enabled, fn) {
|
|
39
|
+
if (!enabled) return fn();
|
|
40
|
+
const originalLog = console.log;
|
|
41
|
+
const originalInfo = console.info;
|
|
42
|
+
const originalDebug = console.debug;
|
|
43
|
+
try {
|
|
44
|
+
console.log = () => {
|
|
45
|
+
};
|
|
46
|
+
console.info = () => {
|
|
47
|
+
};
|
|
48
|
+
console.debug = () => {
|
|
49
|
+
};
|
|
50
|
+
return fn();
|
|
51
|
+
} finally {
|
|
52
|
+
console.log = originalLog;
|
|
53
|
+
console.info = originalInfo;
|
|
54
|
+
console.debug = originalDebug;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/utils/validate-mcp-project.ts
|
|
59
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
60
|
+
import { join } from "path";
|
|
61
|
+
import { execSync } from "child_process";
|
|
62
|
+
async function createDeploymentBundleSmart(projectPath, entryPoint) {
|
|
63
|
+
console.log("\u{1F680} Starting smart deployment bundling...");
|
|
64
|
+
const buildResult = await tryBuildProject(projectPath);
|
|
65
|
+
if (buildResult.success && buildResult.outputPath) {
|
|
66
|
+
console.log("\u{1F4E6} Using built artifact for bundling");
|
|
67
|
+
const bundleResult = await bundleBuiltArtifactSmart(buildResult.outputPath, projectPath);
|
|
68
|
+
if (bundleResult.success) {
|
|
69
|
+
return bundleResult;
|
|
70
|
+
}
|
|
71
|
+
console.log("\u26A0\uFE0F Built artifact bundling failed, trying source bundling");
|
|
72
|
+
}
|
|
73
|
+
console.log("\u{1F4E6} Attempting source bundling with Deno configuration");
|
|
74
|
+
const sourceBundleResult = await bundleSourceWithDenoConfig(projectPath, entryPoint);
|
|
75
|
+
if (sourceBundleResult.success) {
|
|
76
|
+
return sourceBundleResult;
|
|
77
|
+
}
|
|
78
|
+
console.log("\u26A0\uFE0F Source bundling failed, falling back to direct source copy");
|
|
79
|
+
return await copySourceDirectlySmart(projectPath, entryPoint);
|
|
80
|
+
}
|
|
81
|
+
async function tryBuildProject(projectPath) {
|
|
82
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
83
|
+
if (existsSync(packageJsonPath)) {
|
|
84
|
+
try {
|
|
85
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
86
|
+
if (packageJson.scripts?.build) {
|
|
87
|
+
console.log(`\u{1F528} Running npm build script: ${packageJson.scripts.build}`);
|
|
88
|
+
execSync("npm run build", {
|
|
89
|
+
cwd: projectPath,
|
|
90
|
+
stdio: "inherit",
|
|
91
|
+
env: { ...process.env, NODE_ENV: "production" }
|
|
92
|
+
});
|
|
93
|
+
const possibleOutputs = ["dist/main.ts", "dist/index.ts", "build/main.ts", "build/index.ts"];
|
|
94
|
+
for (const output of possibleOutputs) {
|
|
95
|
+
const outputPath = join(projectPath, output);
|
|
96
|
+
if (existsSync(outputPath)) {
|
|
97
|
+
return { success: true, outputPath, errors: [] };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(`\u26A0\uFE0F Build script failed: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const denoJsonPath = join(projectPath, "deno.json");
|
|
106
|
+
const denoJsoncPath = join(projectPath, "deno.jsonc");
|
|
107
|
+
const denoConfigPath = existsSync(denoJsonPath) ? denoJsonPath : existsSync(denoJsoncPath) ? denoJsoncPath : null;
|
|
108
|
+
if (denoConfigPath) {
|
|
109
|
+
try {
|
|
110
|
+
const denoConfig = JSON.parse(readFileSync(denoConfigPath, "utf-8"));
|
|
111
|
+
if (denoConfig.tasks?.build) {
|
|
112
|
+
console.log(`\u{1F528} Running deno task build: ${denoConfig.tasks.build}`);
|
|
113
|
+
execSync("deno task build", {
|
|
114
|
+
cwd: projectPath,
|
|
115
|
+
stdio: "inherit",
|
|
116
|
+
env: { ...process.env, NODE_ENV: "production" }
|
|
117
|
+
});
|
|
118
|
+
const possibleOutputs = ["dist/main.ts", "dist/index.ts"];
|
|
119
|
+
for (const output of possibleOutputs) {
|
|
120
|
+
const outputPath = join(projectPath, output);
|
|
121
|
+
if (existsSync(outputPath)) {
|
|
122
|
+
return { success: true, outputPath, errors: [] };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.log(`\u26A0\uFE0F Deno build task failed: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { success: false, errors: ["No build script found or build failed"] };
|
|
131
|
+
}
|
|
132
|
+
async function bundleBuiltArtifactSmart(artifactPath, projectPath) {
|
|
133
|
+
try {
|
|
134
|
+
const denoConfig = loadDenoConfigForBundling(projectPath);
|
|
135
|
+
const permissions = inferDenoPermissions(projectPath);
|
|
136
|
+
const bundleArgs = [
|
|
137
|
+
"deno bundle",
|
|
138
|
+
"--quiet",
|
|
139
|
+
"--allow-import",
|
|
140
|
+
...denoConfig.configArgs,
|
|
141
|
+
artifactPath
|
|
142
|
+
];
|
|
143
|
+
console.log(`\u{1F4E6} Bundling built artifact: ${artifactPath}`);
|
|
144
|
+
const bundleCmd = bundleArgs.join(" ");
|
|
145
|
+
const bundledCode = execSync(bundleCmd, {
|
|
146
|
+
cwd: projectPath,
|
|
147
|
+
encoding: "utf-8",
|
|
148
|
+
stdio: "pipe"
|
|
149
|
+
});
|
|
150
|
+
console.log(`\u2705 Successfully bundled ${bundledCode.length} characters from built artifact`);
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
code: bundledCode,
|
|
154
|
+
files: {
|
|
155
|
+
"main.ts": {
|
|
156
|
+
content: bundledCode,
|
|
157
|
+
encoding: "utf-8"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
errors: [],
|
|
161
|
+
method: "built-artifact"
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.log(`\u26A0\uFE0F Built artifact bundling failed: ${error.message}`);
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
files: {},
|
|
168
|
+
errors: [error.message],
|
|
169
|
+
method: "built-artifact"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function bundleSourceWithDenoConfig(projectPath, entryPoint) {
|
|
174
|
+
try {
|
|
175
|
+
const denoConfig = loadDenoConfigForBundling(projectPath);
|
|
176
|
+
const permissions = inferDenoPermissions(projectPath);
|
|
177
|
+
const bundleArgs = [
|
|
178
|
+
"deno bundle",
|
|
179
|
+
"--quiet",
|
|
180
|
+
"--allow-import",
|
|
181
|
+
...denoConfig.configArgs,
|
|
182
|
+
entryPoint
|
|
183
|
+
];
|
|
184
|
+
console.log(`\u{1F4E6} Bundling source with Deno config: ${entryPoint}`);
|
|
185
|
+
const bundleCmd = bundleArgs.join(" ");
|
|
186
|
+
const bundledCode = execSync(bundleCmd, {
|
|
187
|
+
cwd: projectPath,
|
|
188
|
+
encoding: "utf-8",
|
|
189
|
+
stdio: "pipe"
|
|
190
|
+
});
|
|
191
|
+
console.log(`\u2705 Successfully bundled ${bundledCode.length} characters from source`);
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
code: bundledCode,
|
|
195
|
+
files: {
|
|
196
|
+
"main.ts": {
|
|
197
|
+
content: bundledCode,
|
|
198
|
+
encoding: "utf-8"
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
errors: [],
|
|
202
|
+
method: "source-bundle"
|
|
203
|
+
};
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.log(`\u26A0\uFE0F Source bundling failed: ${error.message}`);
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
files: {},
|
|
209
|
+
errors: [error.message],
|
|
210
|
+
method: "source-bundle"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function copySourceDirectlySmart(projectPath, entryPoint) {
|
|
215
|
+
try {
|
|
216
|
+
console.log(`\u{1F4C4} Using source file directly: ${entryPoint}`);
|
|
217
|
+
const sourcePath = join(projectPath, entryPoint);
|
|
218
|
+
const sourceContent = readFileSync(sourcePath, "utf-8");
|
|
219
|
+
const fileName = entryPoint.split("/").pop() || "main.ts";
|
|
220
|
+
const files = {
|
|
221
|
+
[fileName]: {
|
|
222
|
+
content: sourceContent,
|
|
223
|
+
encoding: "utf-8"
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
if (entryPoint.startsWith("dist/") && sourceContent.includes("../src/")) {
|
|
227
|
+
console.log("\u{1F4E6} Including src/ directory files for deployment");
|
|
228
|
+
await includeSourceDirectorySmart(projectPath, files);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
success: true,
|
|
232
|
+
code: sourceContent,
|
|
233
|
+
files,
|
|
234
|
+
errors: [],
|
|
235
|
+
method: "direct-copy"
|
|
236
|
+
};
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.log(`\u274C Failed to copy source file: ${error.message}`);
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
files: {},
|
|
242
|
+
errors: [error.message],
|
|
243
|
+
method: "direct-copy"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function inferDenoPermissions(projectPath) {
|
|
248
|
+
const permissions = ["read"];
|
|
249
|
+
try {
|
|
250
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
251
|
+
if (existsSync(packageJsonPath)) {
|
|
252
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
253
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
254
|
+
if (deps.dotenv || deps["dotenv-cli"] || packageJson.scripts?.build?.includes("NODE_ENV")) {
|
|
255
|
+
permissions.push("env");
|
|
256
|
+
}
|
|
257
|
+
if (deps["fs-extra"] || deps.mkdirp) {
|
|
258
|
+
permissions.push("write");
|
|
259
|
+
}
|
|
260
|
+
if (deps.axios || deps.node || deps["node-fetch"] || deps.hono) {
|
|
261
|
+
permissions.push("net");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const denoJsonPath = join(projectPath, "deno.json");
|
|
265
|
+
const denoJsoncPath = join(projectPath, "deno.jsonc");
|
|
266
|
+
const denoConfigPath = existsSync(denoJsonPath) ? denoJsonPath : existsSync(denoJsoncPath) ? denoJsoncPath : null;
|
|
267
|
+
if (denoConfigPath) {
|
|
268
|
+
try {
|
|
269
|
+
const config = JSON.parse(readFileSync(denoConfigPath, "utf-8"));
|
|
270
|
+
if (config.deploy) {
|
|
271
|
+
permissions.push("net");
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.log(`\u26A0\uFE0F Error inferring permissions: ${error.message}`);
|
|
278
|
+
}
|
|
279
|
+
return [...new Set(permissions)];
|
|
280
|
+
}
|
|
281
|
+
async function includeSourceDirectorySmart(projectPath, files) {
|
|
282
|
+
const srcPath = join(projectPath, "src");
|
|
283
|
+
function addDirectory(dirPath, relativePath = "") {
|
|
284
|
+
const items = readdirSync(dirPath);
|
|
285
|
+
for (const item of items) {
|
|
286
|
+
const itemPath = join(dirPath, item);
|
|
287
|
+
const itemRelativePath = relativePath ? `${relativePath}/${item}` : item;
|
|
288
|
+
const stat = statSync(itemPath);
|
|
289
|
+
if (stat.isDirectory()) {
|
|
290
|
+
if (item !== "dev") {
|
|
291
|
+
addDirectory(itemPath, itemRelativePath);
|
|
292
|
+
}
|
|
293
|
+
} else if (stat.isFile() && (item.endsWith(".ts") || item.endsWith(".js"))) {
|
|
294
|
+
const content = readFileSync(itemPath, "utf-8");
|
|
295
|
+
files[`src/${itemRelativePath}`] = {
|
|
296
|
+
content,
|
|
297
|
+
encoding: "utf-8"
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (existsSync(srcPath)) {
|
|
303
|
+
addDirectory(srcPath);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function deployDenoSourceFiles(projectPath, entryPoint) {
|
|
307
|
+
const files = {};
|
|
308
|
+
const denoJsonPath = join(projectPath, "deno.json");
|
|
309
|
+
if (existsSync(denoJsonPath)) {
|
|
310
|
+
files["deno.json"] = {
|
|
311
|
+
content: readFileSync(denoJsonPath, "utf-8"),
|
|
312
|
+
encoding: "utf-8"
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function addDirectory(dirPath, relativePath = "") {
|
|
316
|
+
const items = readdirSync(dirPath);
|
|
317
|
+
for (const item of items) {
|
|
318
|
+
const itemPath = join(dirPath, item);
|
|
319
|
+
const itemRelativePath = relativePath ? `${relativePath}/${item}` : item;
|
|
320
|
+
const stat = statSync(itemPath);
|
|
321
|
+
if (stat.isDirectory()) {
|
|
322
|
+
if (!["node_modules", ".git", "dist", "coverage", ".swc", "dev"].includes(item)) {
|
|
323
|
+
addDirectory(itemPath, itemRelativePath);
|
|
324
|
+
}
|
|
325
|
+
} else if (stat.isFile()) {
|
|
326
|
+
const ext = item.split(".").pop()?.toLowerCase();
|
|
327
|
+
if (["ts", "js", "json", "txt", "md"].includes(ext || "")) {
|
|
328
|
+
files[itemRelativePath] = {
|
|
329
|
+
content: readFileSync(itemPath, "utf-8"),
|
|
330
|
+
encoding: "utf-8"
|
|
331
|
+
};
|
|
332
|
+
} else {
|
|
333
|
+
const buffer = readFileSync(itemPath);
|
|
334
|
+
files[itemRelativePath] = {
|
|
335
|
+
content: buffer.toString("base64"),
|
|
336
|
+
encoding: "base64"
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (existsSync(join(projectPath, "src"))) {
|
|
343
|
+
addDirectory(join(projectPath, "src"), "src");
|
|
344
|
+
}
|
|
345
|
+
const rootFiles = ["package.json", "import_map.json", "README.md"];
|
|
346
|
+
for (const file of rootFiles) {
|
|
347
|
+
const filePath = join(projectPath, file);
|
|
348
|
+
if (existsSync(filePath)) {
|
|
349
|
+
files[file] = {
|
|
350
|
+
content: readFileSync(filePath, "utf-8"),
|
|
351
|
+
encoding: "utf-8"
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
console.log(`\u{1F50D} Deno Deploy Source: entrypoint="${entryPoint}", files=${Object.keys(files).length}`);
|
|
356
|
+
console.log(`\u{1F4C1} Included files: ${Object.keys(files).sort().join(", ")}`);
|
|
357
|
+
return { entryPoint, files };
|
|
358
|
+
}
|
|
359
|
+
function loadDenoConfigForBundling(projectPath) {
|
|
360
|
+
const denoJsonPath = join(projectPath, "deno.json");
|
|
361
|
+
const denoJsoncPath = join(projectPath, "deno.jsonc");
|
|
362
|
+
const configPath = existsSync(denoJsonPath) ? denoJsonPath : existsSync(denoJsoncPath) ? denoJsoncPath : null;
|
|
363
|
+
if (!configPath) {
|
|
364
|
+
return { configArgs: [] };
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
368
|
+
const configArgs = [`--config`, configPath];
|
|
369
|
+
if (config.importMap) {
|
|
370
|
+
const importMapPath = join(projectPath, config.importMap);
|
|
371
|
+
if (existsSync(importMapPath)) {
|
|
372
|
+
configArgs.push("--import-map", importMapPath);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return { configArgs, importMap: config.importMap };
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.log(`\u26A0\uFE0F Failed to load Deno config: ${error.message}`);
|
|
378
|
+
return { configArgs: [] };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async function validateMCPProject(projectPath) {
|
|
382
|
+
const denoJsonPath = join(projectPath, "deno.json");
|
|
383
|
+
const denoJsoncPath = join(projectPath, "deno.jsonc");
|
|
384
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
385
|
+
let configPath = null;
|
|
386
|
+
let configType = null;
|
|
387
|
+
let config = null;
|
|
388
|
+
if (existsSync(denoJsonPath)) {
|
|
389
|
+
configPath = denoJsonPath;
|
|
390
|
+
configType = "deno";
|
|
391
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
392
|
+
} else if (existsSync(denoJsoncPath)) {
|
|
393
|
+
configPath = denoJsoncPath;
|
|
394
|
+
configType = "deno";
|
|
395
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
396
|
+
} else if (existsSync(packageJsonPath)) {
|
|
397
|
+
configPath = packageJsonPath;
|
|
398
|
+
configType = "package";
|
|
399
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
400
|
+
}
|
|
401
|
+
let entryPoint = null;
|
|
402
|
+
if (config) {
|
|
403
|
+
if (configType === "deno") {
|
|
404
|
+
entryPoint = config.main || config.entryPoint;
|
|
405
|
+
if (!entryPoint && config.deploy?.entrypoint) {
|
|
406
|
+
entryPoint = config.deploy.entrypoint;
|
|
407
|
+
}
|
|
408
|
+
} else if (configType === "package") {
|
|
409
|
+
entryPoint = config.main || config.module;
|
|
410
|
+
if (!entryPoint && config.exports) {
|
|
411
|
+
if (typeof config.exports === "string") {
|
|
412
|
+
entryPoint = config.exports;
|
|
413
|
+
} else if (config.exports["."]) {
|
|
414
|
+
const dotExport = config.exports["."];
|
|
415
|
+
if (typeof dotExport === "string") {
|
|
416
|
+
entryPoint = dotExport;
|
|
417
|
+
} else if (dotExport.import) {
|
|
418
|
+
entryPoint = dotExport.import;
|
|
419
|
+
} else if (dotExport.default) {
|
|
420
|
+
entryPoint = dotExport.default;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!entryPoint) {
|
|
427
|
+
const commonEntryPoints = [
|
|
428
|
+
"main.ts",
|
|
429
|
+
"main.js",
|
|
430
|
+
"index.ts",
|
|
431
|
+
"index.js",
|
|
432
|
+
"server.ts",
|
|
433
|
+
"server.js",
|
|
434
|
+
"app.ts",
|
|
435
|
+
"app.js"
|
|
436
|
+
];
|
|
437
|
+
for (const candidate of commonEntryPoints) {
|
|
438
|
+
const candidatePath = join(projectPath, candidate);
|
|
439
|
+
if (existsSync(candidatePath)) {
|
|
440
|
+
entryPoint = candidate;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (!entryPoint) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
"No entry point found. Please specify one in deno.json/package.json or use a common filename like main.ts, index.ts, server.ts, or app.ts"
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
const hasDenoDeployConfig = configType === "deno" && config?.deploy?.entrypoint;
|
|
451
|
+
const resolvedEntryPointPath = join(projectPath, entryPoint);
|
|
452
|
+
if (hasDenoDeployConfig && existsSync(resolvedEntryPointPath)) {
|
|
453
|
+
console.log(`\u{1F680} Deno Deploy project detected, deploying source files with entrypoint: ${entryPoint}`);
|
|
454
|
+
return await deployDenoSourceFiles(projectPath, entryPoint);
|
|
455
|
+
}
|
|
456
|
+
console.log(`\u{1F4E6} No Deno Deploy configuration found, using legacy bundling approach`);
|
|
457
|
+
const distDir = join(projectPath, "dist");
|
|
458
|
+
const entryPointFilename = entryPoint.split("/").pop() || "main.ts";
|
|
459
|
+
const distEntryPointPath = join(distDir, entryPointFilename);
|
|
460
|
+
let finalEntryPoint = entryPoint;
|
|
461
|
+
let entryPointPath = join(projectPath, entryPoint);
|
|
462
|
+
if (existsSync(distDir) && existsSync(distEntryPointPath)) {
|
|
463
|
+
finalEntryPoint = `dist/${entryPointFilename}`;
|
|
464
|
+
entryPointPath = distEntryPointPath;
|
|
465
|
+
console.log(`\u{1F4E6} Using built dist entrypoint: ${finalEntryPoint}`);
|
|
466
|
+
} else {
|
|
467
|
+
if (!existsSync(entryPointPath)) {
|
|
468
|
+
throw new Error(`Entry point file not found: ${entryPoint}`);
|
|
469
|
+
}
|
|
470
|
+
console.log(`\u{1F4E6} Using source entrypoint: ${entryPoint}`);
|
|
471
|
+
}
|
|
472
|
+
const bundleResult = await createDeploymentBundleSmart(projectPath, finalEntryPoint);
|
|
473
|
+
if (!bundleResult.success) {
|
|
474
|
+
throw new Error(`Failed to create deployment bundle: ${bundleResult.errors.join(", ")}`);
|
|
475
|
+
}
|
|
476
|
+
const files = {};
|
|
477
|
+
Object.assign(files, bundleResult.files);
|
|
478
|
+
finalEntryPoint = "main.ts";
|
|
479
|
+
const denoJsonFilePath = join(projectPath, "deno.json");
|
|
480
|
+
if (existsSync(denoJsonFilePath)) {
|
|
481
|
+
const denoJsonContent = readFileSync(denoJsonFilePath, "utf-8");
|
|
482
|
+
const denoJson = JSON.parse(denoJsonContent);
|
|
483
|
+
if (denoJson.deploy?.entrypoint) {
|
|
484
|
+
denoJson.deploy.entrypoint = finalEntryPoint;
|
|
485
|
+
}
|
|
486
|
+
if (denoJson.main) {
|
|
487
|
+
denoJson.main = finalEntryPoint;
|
|
488
|
+
}
|
|
489
|
+
files["deno.json"] = {
|
|
490
|
+
content: JSON.stringify(denoJson, null, 2),
|
|
491
|
+
encoding: "utf-8"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
console.log(`\u{1F50D} CLI Result: entrypoint="${finalEntryPoint}", files=${Object.keys(files).length}`);
|
|
495
|
+
console.log(`\u{1F4C1} Included files: ${Object.keys(files).sort().join(", ")}`);
|
|
496
|
+
return { entryPoint: finalEntryPoint, files };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/utils/config.ts
|
|
500
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
501
|
+
import { join as join2 } from "path";
|
|
502
|
+
import { homedir } from "os";
|
|
503
|
+
var CONFIG_DIR = join2(homedir(), ".apexmcp");
|
|
504
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
505
|
+
var DEFAULT_CONFIG = {
|
|
506
|
+
version: "0.1.0",
|
|
507
|
+
endpoints: {
|
|
508
|
+
core: "https://core.apexmcp.dev",
|
|
509
|
+
deploy: "https://deploy.apexmcp.dev"
|
|
510
|
+
},
|
|
511
|
+
auth: {},
|
|
512
|
+
preferences: {
|
|
513
|
+
json: false,
|
|
514
|
+
verbose: false
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
var ConfigManager = class {
|
|
518
|
+
config;
|
|
519
|
+
constructor() {
|
|
520
|
+
this.config = this.loadConfig();
|
|
521
|
+
}
|
|
522
|
+
loadConfig() {
|
|
523
|
+
try {
|
|
524
|
+
if (existsSync2(CONFIG_FILE)) {
|
|
525
|
+
const data = readFileSync2(CONFIG_FILE, "utf-8");
|
|
526
|
+
const parsed = JSON.parse(data);
|
|
527
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
528
|
+
}
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.warn("Failed to load config file, using defaults:", error);
|
|
531
|
+
}
|
|
532
|
+
return { ...DEFAULT_CONFIG };
|
|
533
|
+
}
|
|
534
|
+
saveConfig() {
|
|
535
|
+
try {
|
|
536
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
537
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(this.config, null, 2));
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error("Failed to save config:", error);
|
|
540
|
+
throw new Error("Could not save configuration");
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
getConfig() {
|
|
544
|
+
return { ...this.config };
|
|
545
|
+
}
|
|
546
|
+
updateConfig(updates) {
|
|
547
|
+
this.config = { ...this.config, ...updates };
|
|
548
|
+
this.saveConfig();
|
|
549
|
+
}
|
|
550
|
+
setEndpoint(service, url) {
|
|
551
|
+
this.config.endpoints[service] = url;
|
|
552
|
+
this.saveConfig();
|
|
553
|
+
}
|
|
554
|
+
setAuth(token, refreshToken, expiresAt) {
|
|
555
|
+
this.config.auth = { token, refreshToken, expiresAt };
|
|
556
|
+
this.saveConfig();
|
|
557
|
+
}
|
|
558
|
+
getToken() {
|
|
559
|
+
return this.config.auth.token;
|
|
560
|
+
}
|
|
561
|
+
clearAuth() {
|
|
562
|
+
this.config.auth = {};
|
|
563
|
+
this.saveConfig();
|
|
564
|
+
}
|
|
565
|
+
setPreference(key, value) {
|
|
566
|
+
this.config.preferences[key] = value;
|
|
567
|
+
this.saveConfig();
|
|
568
|
+
}
|
|
569
|
+
getPreference(key) {
|
|
570
|
+
return this.config.preferences[key];
|
|
571
|
+
}
|
|
572
|
+
isAuthenticated() {
|
|
573
|
+
const token = this.getToken();
|
|
574
|
+
if (!token) return false;
|
|
575
|
+
if (this.config.auth.expiresAt) {
|
|
576
|
+
const expiresAt = new Date(this.config.auth.expiresAt);
|
|
577
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
578
|
+
this.clearAuth();
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
var configManager = new ConfigManager();
|
|
586
|
+
|
|
587
|
+
// src/commands/deploy.ts
|
|
588
|
+
var DeployService = class {
|
|
589
|
+
constructor(fs = { existsSync: existsSync3 }, path = { resolve }, envLoader = { loadEnvFile }, validator = { validateMCPProject }, fetchClient = { fetch: global.fetch }, log = logger) {
|
|
590
|
+
this.fs = fs;
|
|
591
|
+
this.path = path;
|
|
592
|
+
this.envLoader = envLoader;
|
|
593
|
+
this.validator = validator;
|
|
594
|
+
this.fetchClient = fetchClient;
|
|
595
|
+
this.log = log;
|
|
596
|
+
}
|
|
597
|
+
validateProjectPath(projectPath) {
|
|
598
|
+
const resolvedPath = this.path.resolve(projectPath);
|
|
599
|
+
if (!this.fs.existsSync(resolvedPath)) {
|
|
600
|
+
throw new Error(`Project path does not exist: ${resolvedPath}`);
|
|
601
|
+
}
|
|
602
|
+
return resolvedPath;
|
|
603
|
+
}
|
|
604
|
+
async validateProjectStructure(projectPath) {
|
|
605
|
+
return await this.validator.validateMCPProject(projectPath);
|
|
606
|
+
}
|
|
607
|
+
async loadEnvironmentVariables(envFile) {
|
|
608
|
+
if (!envFile) {
|
|
609
|
+
return {};
|
|
610
|
+
}
|
|
611
|
+
return await this.envLoader.loadEnvFile(envFile);
|
|
612
|
+
}
|
|
613
|
+
prepareDeploymentData(resolvedPath, projectData, envVars, options) {
|
|
614
|
+
const formData = new FormData();
|
|
615
|
+
const projectIdentifier = options.projectPath || resolvedPath.split("/").pop() || "unnamed-project";
|
|
616
|
+
formData.append("projectPath", projectIdentifier);
|
|
617
|
+
const effectiveEntryPoint = options.entryPoint || projectData.entryPoint;
|
|
618
|
+
formData.append("entryPoint", effectiveEntryPoint);
|
|
619
|
+
if (options.domain) {
|
|
620
|
+
formData.append("domain", options.domain);
|
|
621
|
+
}
|
|
622
|
+
if (options.urlStrategy) {
|
|
623
|
+
formData.append("urlStrategy", options.urlStrategy);
|
|
624
|
+
}
|
|
625
|
+
if (options.projectName) {
|
|
626
|
+
formData.append("projectName", options.projectName);
|
|
627
|
+
}
|
|
628
|
+
if (Object.keys(envVars).length > 0) {
|
|
629
|
+
const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
630
|
+
formData.append(
|
|
631
|
+
"envFile",
|
|
632
|
+
new Blob([envContent], { type: "text/plain" }),
|
|
633
|
+
"env"
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
for (const [filename, fileData] of Object.entries(projectData.files)) {
|
|
637
|
+
const mimeType = fileData.encoding === "base64" ? "application/octet-stream" : "text/plain";
|
|
638
|
+
formData.append(
|
|
639
|
+
"files",
|
|
640
|
+
new Blob([fileData.content], { type: mimeType }),
|
|
641
|
+
filename
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
projectIdentifier,
|
|
646
|
+
entryPoint: effectiveEntryPoint,
|
|
647
|
+
envVars,
|
|
648
|
+
formData
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
async executeDeployment(endpoint, formData, token) {
|
|
652
|
+
const headers = {};
|
|
653
|
+
if (token) {
|
|
654
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
655
|
+
}
|
|
656
|
+
this.log.info("\u{1F4E6} Initiating deployment...");
|
|
657
|
+
const response = await this.fetchClient.fetch(`${endpoint}/api/deploy`, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
body: formData,
|
|
660
|
+
headers
|
|
661
|
+
});
|
|
662
|
+
if (!response.ok) {
|
|
663
|
+
const errorText = await response.text();
|
|
664
|
+
throw new Error(
|
|
665
|
+
`Deployment failed: ${response.status} ${response.statusText}
|
|
666
|
+
${errorText}`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
const result = await response.json();
|
|
670
|
+
if (result.denoDeploymentId && result.deployment?.status !== "success") {
|
|
671
|
+
this.log.info("\u{1F50D} Monitoring deployment progress...");
|
|
672
|
+
const monitorResult = await this.monitorDeployment(endpoint, result.denoDeploymentId, token);
|
|
673
|
+
if (monitorResult.status === "failed") {
|
|
674
|
+
this.log.error("\u274C Build failed - check the logs above for error details");
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return result;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Monitor deployment progress with real-time logs and status updates
|
|
681
|
+
*/
|
|
682
|
+
async monitorDeployment(endpoint, deploymentId, token) {
|
|
683
|
+
const headers = {};
|
|
684
|
+
if (token) {
|
|
685
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
686
|
+
}
|
|
687
|
+
let lastStatus = "pending";
|
|
688
|
+
const collectedLogs = [];
|
|
689
|
+
let buildLogsReceived = 0;
|
|
690
|
+
const maxWaitTime = 5 * 60 * 1e3;
|
|
691
|
+
const pollInterval = 2e3;
|
|
692
|
+
const startTime = Date.now();
|
|
693
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
694
|
+
try {
|
|
695
|
+
const statusResponse = await this.fetchClient.fetch(
|
|
696
|
+
`${endpoint}/api/deployments/${deploymentId}/status`,
|
|
697
|
+
{ headers }
|
|
698
|
+
);
|
|
699
|
+
if (statusResponse.ok) {
|
|
700
|
+
const statusData = await statusResponse.json();
|
|
701
|
+
const currentStatus = statusData?.status?.status;
|
|
702
|
+
if (currentStatus !== lastStatus) {
|
|
703
|
+
this.log.info(`\u{1F4CA} Deployment status: ${currentStatus}`);
|
|
704
|
+
lastStatus = currentStatus;
|
|
705
|
+
}
|
|
706
|
+
if (currentStatus === "success") {
|
|
707
|
+
this.log.info("\u2705 Deployment completed successfully!");
|
|
708
|
+
return { status: currentStatus, logs: collectedLogs };
|
|
709
|
+
} else if (currentStatus === "failed") {
|
|
710
|
+
this.log.error("\u274C Deployment failed!");
|
|
711
|
+
if (collectedLogs.length === 0) {
|
|
712
|
+
try {
|
|
713
|
+
const logsResponse2 = await this.fetchClient.fetch(
|
|
714
|
+
`${endpoint}/api/deployments/${deploymentId}/logs/build`,
|
|
715
|
+
{ headers }
|
|
716
|
+
);
|
|
717
|
+
if (logsResponse2.ok) {
|
|
718
|
+
const logsData = await logsResponse2.json();
|
|
719
|
+
if (logsData?.logs && Array.isArray(logsData.logs)) {
|
|
720
|
+
logsData.logs.forEach((log) => {
|
|
721
|
+
const logMessage = `${log.level.toUpperCase()}: ${log.message}`;
|
|
722
|
+
collectedLogs.push(logMessage);
|
|
723
|
+
this.log.error(`\u{1F534} ${log.message}`);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} catch (error) {
|
|
728
|
+
this.log.debug("Could not fetch build logs for failed deployment", error);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (collectedLogs.length > 0) {
|
|
732
|
+
this.log.info("\u{1F4CB} Build logs:");
|
|
733
|
+
collectedLogs.forEach((log) => this.log.info(` ${log}`));
|
|
734
|
+
} else {
|
|
735
|
+
this.log.info("\u{1F4CB} No build logs available");
|
|
736
|
+
}
|
|
737
|
+
return { status: currentStatus, logs: collectedLogs };
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const logsResponse = await this.fetchClient.fetch(
|
|
741
|
+
`${endpoint}/api/deployments/${deploymentId}/logs/build?since=${new Date(Date.now() - 3e4).toISOString()}`,
|
|
742
|
+
{ headers }
|
|
743
|
+
);
|
|
744
|
+
if (logsResponse.ok) {
|
|
745
|
+
const logsData = await logsResponse.json();
|
|
746
|
+
if (logsData?.logs && Array.isArray(logsData.logs)) {
|
|
747
|
+
for (const log of logsData.logs) {
|
|
748
|
+
const logMessage = `${log.level.toUpperCase()}: ${log.message}`;
|
|
749
|
+
collectedLogs.push(logMessage);
|
|
750
|
+
if (log.level === "error") {
|
|
751
|
+
this.log.error(`\u{1F534} ${log.message}`);
|
|
752
|
+
} else if (log.level === "warn") {
|
|
753
|
+
this.log.warn(`\u{1F7E1} ${log.message}`);
|
|
754
|
+
} else {
|
|
755
|
+
this.log.info(`\u2139\uFE0F ${log.message}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
} catch (error) {
|
|
761
|
+
this.log.debug("Monitoring error (continuing):", error);
|
|
762
|
+
}
|
|
763
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
764
|
+
}
|
|
765
|
+
if (Date.now() - startTime >= maxWaitTime) {
|
|
766
|
+
this.log.warn("\u23F0 Deployment monitoring timed out, but deployment may still be in progress");
|
|
767
|
+
return { status: "timeout", logs: collectedLogs };
|
|
768
|
+
}
|
|
769
|
+
return { status: lastStatus, logs: collectedLogs };
|
|
770
|
+
}
|
|
771
|
+
async handleResponse(result, options) {
|
|
772
|
+
const outputJson = isJsonMode(options);
|
|
773
|
+
if (outputJson) {
|
|
774
|
+
console.log(JSON.stringify(result));
|
|
775
|
+
} else {
|
|
776
|
+
if (result.success && result.urls && result.deployment) {
|
|
777
|
+
this.log.info("\u{1F389} Deployment successful!");
|
|
778
|
+
this.log.info(`\u{1F310} URL: ${result.urls.primary}`);
|
|
779
|
+
this.log.info(`\u{1F4CB} Deployment ID: ${result.deployment.id}`);
|
|
780
|
+
this.log.info(`\u{1F4CA} Status: ${result.deployment.status}`);
|
|
781
|
+
if (result.project) {
|
|
782
|
+
this.log.info(`\u{1F3D7}\uFE0F Project: ${result.project.name}`);
|
|
783
|
+
}
|
|
784
|
+
if (result.domainAttachment) {
|
|
785
|
+
this.log.info(`\u{1F310} Domain attachment: ${result.domainAttachment.note}`);
|
|
786
|
+
}
|
|
787
|
+
this.log.info("\n\u{1F4DA} Next steps:");
|
|
788
|
+
this.log.info("1. Test your MCP server at the URLs above");
|
|
789
|
+
this.log.info("2. Configure your MCP client to connect to this endpoint");
|
|
790
|
+
this.log.info("3. Monitor deployment status if needed");
|
|
791
|
+
} else if (result.error) {
|
|
792
|
+
this.log.error("\u274C Deployment failed:", result.error);
|
|
793
|
+
} else {
|
|
794
|
+
this.log.error("\u274C Deployment failed: Unknown error");
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
async deploy(projectPath, options) {
|
|
799
|
+
try {
|
|
800
|
+
this.log.info("\u{1F680} Starting MCP server deployment...");
|
|
801
|
+
this.log.info(`\u{1F4C1} Project directory: ${projectPath}`);
|
|
802
|
+
const config = configManager.getConfig();
|
|
803
|
+
const effectiveOptions = {
|
|
804
|
+
...options,
|
|
805
|
+
endpoint: options.endpoint || config.endpoints.deploy,
|
|
806
|
+
token: options.token || config.auth.token
|
|
807
|
+
};
|
|
808
|
+
if (!effectiveOptions.token) {
|
|
809
|
+
throw new Error(
|
|
810
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
const resolvedPath = this.validateProjectPath(projectPath);
|
|
814
|
+
const projectData = await withStdoutSuppressed(
|
|
815
|
+
isJsonMode(options),
|
|
816
|
+
() => this.validateProjectStructure(resolvedPath)
|
|
817
|
+
);
|
|
818
|
+
if (options.entryPoint) {
|
|
819
|
+
const override = options.entryPoint.trim();
|
|
820
|
+
if (!override) {
|
|
821
|
+
throw new Error("--entry-point cannot be empty");
|
|
822
|
+
}
|
|
823
|
+
if (!projectData.files[override]) {
|
|
824
|
+
const overridePath = `${resolvedPath}/${override}`;
|
|
825
|
+
if (!this.fs.existsSync(overridePath)) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
`Entry point override not found in packaged files and does not exist on disk: ${override}`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
throw new Error(
|
|
831
|
+
`Entry point override exists on disk but was not included in packaged files: ${override}. Ensure it is reachable by your project config (deno.json/package.json) or use the auto-detected entry point.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
projectData.entryPoint = override;
|
|
835
|
+
}
|
|
836
|
+
this.log.info(`\u2705 Entry point: ${projectData.entryPoint}`);
|
|
837
|
+
const envVars = await this.loadEnvironmentVariables(options.envFile);
|
|
838
|
+
if (options.envFile) {
|
|
839
|
+
this.log.info(
|
|
840
|
+
`\u{1F4C4} Loading environment variables from: ${options.envFile}`
|
|
841
|
+
);
|
|
842
|
+
this.log.info(
|
|
843
|
+
`\u2705 Loaded ${Object.keys(envVars).length} environment variables`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
const deploymentData = this.prepareDeploymentData(
|
|
847
|
+
resolvedPath,
|
|
848
|
+
projectData,
|
|
849
|
+
envVars,
|
|
850
|
+
effectiveOptions
|
|
851
|
+
);
|
|
852
|
+
this.log.info("\u{1F680} Deploying to ApexMCP...");
|
|
853
|
+
const result = await this.executeDeployment(
|
|
854
|
+
effectiveOptions.endpoint,
|
|
855
|
+
deploymentData.formData,
|
|
856
|
+
effectiveOptions.token
|
|
857
|
+
);
|
|
858
|
+
await this.handleResponse(result, effectiveOptions);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
this.log.error(
|
|
861
|
+
"\u274C Deployment failed:",
|
|
862
|
+
error instanceof Error ? error.message : String(error)
|
|
863
|
+
);
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
async function deployCommand(projectPath, options) {
|
|
869
|
+
const service = new DeployService(
|
|
870
|
+
void 0,
|
|
871
|
+
void 0,
|
|
872
|
+
void 0,
|
|
873
|
+
void 0,
|
|
874
|
+
void 0,
|
|
875
|
+
createLogger(options)
|
|
876
|
+
);
|
|
877
|
+
try {
|
|
878
|
+
await service.deploy(projectPath, options);
|
|
879
|
+
} catch (error) {
|
|
880
|
+
const log = createLogger(options);
|
|
881
|
+
log.error(
|
|
882
|
+
"Deployment failed:",
|
|
883
|
+
error instanceof Error ? error.message : String(error)
|
|
884
|
+
);
|
|
885
|
+
process.exit(1);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/commands/deployments.ts
|
|
890
|
+
import { createInterface } from "readline";
|
|
891
|
+
async function getUserConfirmation(message) {
|
|
892
|
+
const rl = createInterface({
|
|
893
|
+
input: process.stdin,
|
|
894
|
+
output: process.stdout
|
|
895
|
+
});
|
|
896
|
+
return new Promise((resolve3) => {
|
|
897
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
898
|
+
rl.close();
|
|
899
|
+
const normalizedAnswer = answer.toLowerCase().trim();
|
|
900
|
+
resolve3(normalizedAnswer === "y" || normalizedAnswer === "yes");
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
async function listDeploymentsCommand(options) {
|
|
905
|
+
const outputJson = isJsonMode(options);
|
|
906
|
+
const log = createLogger(options);
|
|
907
|
+
try {
|
|
908
|
+
log.info("\u{1F4CB} Fetching deployments...");
|
|
909
|
+
const config = configManager.getConfig();
|
|
910
|
+
const effectiveOptions = {
|
|
911
|
+
...options,
|
|
912
|
+
endpoint: options.endpoint || config.endpoints.deploy,
|
|
913
|
+
token: options.token || config.auth.token
|
|
914
|
+
};
|
|
915
|
+
if (!effectiveOptions.token) {
|
|
916
|
+
throw new Error(
|
|
917
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
const headers = {};
|
|
921
|
+
if (effectiveOptions.token) {
|
|
922
|
+
headers["Authorization"] = `Bearer ${effectiveOptions.token}`;
|
|
923
|
+
}
|
|
924
|
+
const response = await fetch(`${effectiveOptions.endpoint}/api/deployments`, {
|
|
925
|
+
method: "GET",
|
|
926
|
+
headers
|
|
927
|
+
});
|
|
928
|
+
if (!response.ok) {
|
|
929
|
+
const errorText = await response.text();
|
|
930
|
+
throw new Error(
|
|
931
|
+
`Failed to list deployments: ${response.status} ${response.statusText}
|
|
932
|
+
${errorText}`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
const result = await response.json();
|
|
936
|
+
if (outputJson) {
|
|
937
|
+
console.log(JSON.stringify(result, null, 2));
|
|
938
|
+
} else {
|
|
939
|
+
if (result.deployments.length === 0) {
|
|
940
|
+
log.info("\u{1F4ED} No deployments found.");
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
log.info(`\u{1F4CB} Found ${result.deployments.length} deployment(s):
|
|
944
|
+
`);
|
|
945
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
946
|
+
log.info("\u2502 Deployment Name \u2502 Domain \u2502 Status \u2502 Tier \u2502");
|
|
947
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
948
|
+
for (const deployment of result.deployments) {
|
|
949
|
+
if (!deployment.name || !deployment.status || !deployment.tier) {
|
|
950
|
+
throw new Error(`Invalid deployment data, ${JSON.stringify(deployment)}`);
|
|
951
|
+
}
|
|
952
|
+
const name = String(deployment.name).padEnd(36);
|
|
953
|
+
const domain = String(deployment.domain || "N/A").substring(0, 18).padEnd(18);
|
|
954
|
+
const status = String(deployment.status).substring(0, 7).padEnd(7);
|
|
955
|
+
const tier = String(deployment.tier).substring(0, 7).padEnd(7);
|
|
956
|
+
log.info(`\u2502 ${name} \u2502 ${domain} \u2502 ${status} \u2502 ${tier} \u2502`);
|
|
957
|
+
}
|
|
958
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
959
|
+
for (const deployment of result.deployments) {
|
|
960
|
+
log.info(`\u{1F4CB} ${deployment.name}:`);
|
|
961
|
+
log.info(` \u{1F310} URL: ${deployment.url}`);
|
|
962
|
+
log.info(` \u{1F3F7}\uFE0F Domain: ${deployment.domain}`);
|
|
963
|
+
log.info(` \u{1F4C5} Created: ${new Date(deployment.createdAt).toLocaleString()}`);
|
|
964
|
+
if (deployment.status !== "active") {
|
|
965
|
+
log.info(` \u26A0\uFE0F Status: ${deployment.status}`);
|
|
966
|
+
}
|
|
967
|
+
log.info("");
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
log.error(
|
|
972
|
+
"\u274C Failed to list deployments:",
|
|
973
|
+
error instanceof Error ? error.message : String(error)
|
|
974
|
+
);
|
|
975
|
+
process.exit(1);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async function getDeploymentCommand(deploymentId, options) {
|
|
979
|
+
const outputJson = isJsonMode(options);
|
|
980
|
+
const log = createLogger(options);
|
|
981
|
+
try {
|
|
982
|
+
log.info(`\u{1F4CB} Fetching deployment details for: ${deploymentId}`);
|
|
983
|
+
const config = configManager.getConfig();
|
|
984
|
+
const effectiveOptions = {
|
|
985
|
+
...options,
|
|
986
|
+
endpoint: options.endpoint || config.endpoints.deploy,
|
|
987
|
+
token: options.token || config.auth.token
|
|
988
|
+
};
|
|
989
|
+
if (!effectiveOptions.token) {
|
|
990
|
+
throw new Error(
|
|
991
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
const headers = {};
|
|
995
|
+
if (effectiveOptions.token) {
|
|
996
|
+
headers["Authorization"] = `Bearer ${effectiveOptions.token}`;
|
|
997
|
+
}
|
|
998
|
+
const response = await fetch(`${effectiveOptions.endpoint}/api/deployments/${deploymentId}`, {
|
|
999
|
+
method: "GET",
|
|
1000
|
+
headers
|
|
1001
|
+
});
|
|
1002
|
+
if (!response.ok) {
|
|
1003
|
+
const errorText = await response.text();
|
|
1004
|
+
throw new Error(
|
|
1005
|
+
`Failed to get deployment: ${response.status} ${response.statusText}
|
|
1006
|
+
${errorText}`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
const result = await response.json();
|
|
1010
|
+
if (outputJson) {
|
|
1011
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1012
|
+
} else {
|
|
1013
|
+
const deployment = result.deployment;
|
|
1014
|
+
log.info("\u{1F4CB} Deployment Details:");
|
|
1015
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1016
|
+
log.info("\u2502 Property \u2502 Value \u2502");
|
|
1017
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
1018
|
+
const formatRow = (label, value) => {
|
|
1019
|
+
const displayValue = value || "N/A";
|
|
1020
|
+
const truncatedValue = displayValue.length > 36 ? displayValue.substring(0, 33) + "..." : displayValue;
|
|
1021
|
+
log.info(`\u2502 ${label.padEnd(36)} \u2502 ${truncatedValue.padEnd(36)} \u2502`);
|
|
1022
|
+
};
|
|
1023
|
+
formatRow("ID", deployment.id ? String(deployment.id) : void 0);
|
|
1024
|
+
formatRow("Name", deployment.name ? String(deployment.name) : void 0);
|
|
1025
|
+
formatRow("Domain", deployment.domain ? String(deployment.domain) : void 0);
|
|
1026
|
+
formatRow("Status", deployment.status ? String(deployment.status) : void 0);
|
|
1027
|
+
formatRow("Tier", deployment.tier ? String(deployment.tier) : void 0);
|
|
1028
|
+
formatRow("URL", deployment.url ? String(deployment.url) : void 0);
|
|
1029
|
+
formatRow("Subdomain", deployment.subdomain ? String(deployment.subdomain) : void 0);
|
|
1030
|
+
formatRow("Deployment URL", deployment.deploymentUrl ? String(deployment.deploymentUrl) : void 0);
|
|
1031
|
+
formatRow("Entry Point", deployment.entryPoint ? String(deployment.entryPoint) : void 0);
|
|
1032
|
+
formatRow("Project Path", deployment.projectPath ? String(deployment.projectPath) : void 0);
|
|
1033
|
+
formatRow("Build Status", deployment.buildStatus ? String(deployment.buildStatus) : void 0);
|
|
1034
|
+
formatRow("Runtime Status", deployment.runtimeStatus ? String(deployment.runtimeStatus) : void 0);
|
|
1035
|
+
formatRow("Created", deployment.createdAt ? new Date(deployment.createdAt).toLocaleString() : void 0);
|
|
1036
|
+
formatRow("Updated", deployment.updatedAt ? new Date(deployment.updatedAt).toLocaleString() : void 0);
|
|
1037
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1038
|
+
if (deployment.envVars && Object.keys(deployment.envVars).length > 0) {
|
|
1039
|
+
log.info("\n\u{1F527} Environment Variables:");
|
|
1040
|
+
for (const [key, value] of Object.entries(deployment.envVars)) {
|
|
1041
|
+
const stringValue = String(value);
|
|
1042
|
+
const maskedValue = stringValue.length > 20 ? `${stringValue.substring(0, 10)}...` : stringValue;
|
|
1043
|
+
log.info(` ${key}=${maskedValue}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (deployment.errorMessage) {
|
|
1047
|
+
log.info("\n\u274C Error:");
|
|
1048
|
+
log.info(` ${deployment.errorMessage}`);
|
|
1049
|
+
}
|
|
1050
|
+
if (deployment.logs && deployment.logs.length > 0) {
|
|
1051
|
+
log.info("\n\u{1F4DD} Recent Logs:");
|
|
1052
|
+
const recentLogs = deployment.logs.slice(-5);
|
|
1053
|
+
for (const logEntry of recentLogs) {
|
|
1054
|
+
log.info(` ${logEntry}`);
|
|
1055
|
+
}
|
|
1056
|
+
if (deployment.logs.length > 5) {
|
|
1057
|
+
log.info(` ... and ${deployment.logs.length - 5} more entries`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
log.error(
|
|
1063
|
+
"\u274C Failed to get deployment details:",
|
|
1064
|
+
error instanceof Error ? error.message : String(error)
|
|
1065
|
+
);
|
|
1066
|
+
process.exit(1);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
async function deleteDeploymentCommand(deploymentId, options) {
|
|
1070
|
+
const outputJson = isJsonMode(options);
|
|
1071
|
+
const log = createLogger(options);
|
|
1072
|
+
try {
|
|
1073
|
+
log.info(`\u{1F5D1}\uFE0F Deleting deployment: ${deploymentId}`);
|
|
1074
|
+
const config = configManager.getConfig();
|
|
1075
|
+
const effectiveOptions = {
|
|
1076
|
+
...options,
|
|
1077
|
+
endpoint: options.endpoint || config.endpoints.deploy,
|
|
1078
|
+
token: options.token || config.auth.token
|
|
1079
|
+
};
|
|
1080
|
+
if (!effectiveOptions.token) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
if (!options.force && !outputJson) {
|
|
1086
|
+
log.info("\u26A0\uFE0F This action cannot be undone. All deployment data will be permanently deleted.");
|
|
1087
|
+
const confirmed = await getUserConfirmation(`Are you sure you want to delete deployment "${deploymentId}"?`);
|
|
1088
|
+
if (!confirmed) {
|
|
1089
|
+
log.info("\u274C Deletion cancelled.");
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const headers = {};
|
|
1094
|
+
if (effectiveOptions.token) {
|
|
1095
|
+
headers["Authorization"] = `Bearer ${effectiveOptions.token}`;
|
|
1096
|
+
}
|
|
1097
|
+
const response = await fetch(`${effectiveOptions.endpoint}/api/deployments/${deploymentId}`, {
|
|
1098
|
+
method: "DELETE",
|
|
1099
|
+
headers
|
|
1100
|
+
});
|
|
1101
|
+
if (!response.ok) {
|
|
1102
|
+
const errorText = await response.text();
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`Failed to delete deployment: ${response.status} ${response.statusText}
|
|
1105
|
+
${errorText}`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
const result = await response.json();
|
|
1109
|
+
if (outputJson) {
|
|
1110
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1111
|
+
} else {
|
|
1112
|
+
if (result.success) {
|
|
1113
|
+
log.info("\u2705 Deployment deleted successfully!");
|
|
1114
|
+
} else {
|
|
1115
|
+
log.error("\u274C Failed to delete deployment");
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
log.error(
|
|
1120
|
+
"\u274C Failed to delete deployment:",
|
|
1121
|
+
error instanceof Error ? error.message : String(error)
|
|
1122
|
+
);
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async function updateDeploymentCommand(deploymentId, options) {
|
|
1127
|
+
const outputJson = isJsonMode(options);
|
|
1128
|
+
const log = createLogger(options);
|
|
1129
|
+
try {
|
|
1130
|
+
log.info(`\u{1F4DD} Updating deployment: ${deploymentId}`);
|
|
1131
|
+
const config = configManager.getConfig();
|
|
1132
|
+
const effectiveOptions = {
|
|
1133
|
+
...options,
|
|
1134
|
+
endpoint: options.endpoint || config.endpoints.deploy,
|
|
1135
|
+
token: options.token || config.auth.token
|
|
1136
|
+
};
|
|
1137
|
+
if (!effectiveOptions.token) {
|
|
1138
|
+
throw new Error(
|
|
1139
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
if (!options.name && !options.domain && !options.envFile) {
|
|
1143
|
+
throw new Error(
|
|
1144
|
+
"At least one update option must be provided (--name, --domain, or --env-file)"
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
const headers = {
|
|
1148
|
+
"Content-Type": "application/json"
|
|
1149
|
+
};
|
|
1150
|
+
if (effectiveOptions.token) {
|
|
1151
|
+
headers["Authorization"] = `Bearer ${effectiveOptions.token}`;
|
|
1152
|
+
}
|
|
1153
|
+
const updatePayload = {};
|
|
1154
|
+
if (options.name) {
|
|
1155
|
+
updatePayload.name = options.name;
|
|
1156
|
+
}
|
|
1157
|
+
if (options.domain) {
|
|
1158
|
+
updatePayload.domain = options.domain;
|
|
1159
|
+
}
|
|
1160
|
+
if (options.envFile) {
|
|
1161
|
+
const { loadEnvFile: loadEnvFile2 } = await import("./load-env-file-EUKRAC2D.js");
|
|
1162
|
+
try {
|
|
1163
|
+
const envVars = await loadEnvFile2(options.envFile);
|
|
1164
|
+
updatePayload.envVars = envVars;
|
|
1165
|
+
log.info(`\u{1F4C4} Loaded ${Object.keys(envVars).length} environment variables from: ${options.envFile}`);
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
throw new Error(`Failed to load environment file: ${error instanceof Error ? error.message : String(error)}`);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const response = await fetch(`${effectiveOptions.endpoint}/api/deployments/${deploymentId}`, {
|
|
1171
|
+
method: "PATCH",
|
|
1172
|
+
headers,
|
|
1173
|
+
body: JSON.stringify(updatePayload)
|
|
1174
|
+
});
|
|
1175
|
+
if (!response.ok) {
|
|
1176
|
+
const errorText = await response.text();
|
|
1177
|
+
throw new Error(
|
|
1178
|
+
`Failed to update deployment: ${response.status} ${response.statusText}
|
|
1179
|
+
${errorText}`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
const result = await response.json();
|
|
1183
|
+
if (outputJson) {
|
|
1184
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1185
|
+
} else {
|
|
1186
|
+
log.info("\u2705 Deployment updated successfully!");
|
|
1187
|
+
if (options.name) {
|
|
1188
|
+
log.info(`\u{1F4DD} Name updated to: ${options.name}`);
|
|
1189
|
+
}
|
|
1190
|
+
if (options.domain) {
|
|
1191
|
+
log.info(`\u{1F310} Domain updated to: ${options.domain}`);
|
|
1192
|
+
}
|
|
1193
|
+
if (options.envFile) {
|
|
1194
|
+
log.info(`\u{1F527} Environment variables updated`);
|
|
1195
|
+
}
|
|
1196
|
+
if (result.deployment) {
|
|
1197
|
+
log.info("\n\u{1F4CB} Updated deployment details:");
|
|
1198
|
+
log.info(` ID: ${result.deployment.id}`);
|
|
1199
|
+
log.info(` Name: ${result.deployment.name || "N/A"}`);
|
|
1200
|
+
log.info(` URL: ${result.deployment.url || "N/A"}`);
|
|
1201
|
+
log.info(` Status: ${result.deployment.status || "unknown"}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
log.error(
|
|
1206
|
+
"\u274C Failed to update deployment:",
|
|
1207
|
+
error instanceof Error ? error.message : String(error)
|
|
1208
|
+
);
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/commands/validate.ts
|
|
1214
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1215
|
+
import { resolve as resolve2 } from "path";
|
|
1216
|
+
async function validateCommand(projectPath, options) {
|
|
1217
|
+
const outputJson = isJsonMode(options);
|
|
1218
|
+
const log = createLogger(options);
|
|
1219
|
+
try {
|
|
1220
|
+
log.info("\u{1F50D} Validating MCP project...");
|
|
1221
|
+
log.info(`\u{1F4C1} Project path: ${projectPath}`);
|
|
1222
|
+
const resolvedPath = resolve2(projectPath);
|
|
1223
|
+
if (!existsSync4(resolvedPath)) {
|
|
1224
|
+
throw new Error(`Project path does not exist: ${resolvedPath}`);
|
|
1225
|
+
}
|
|
1226
|
+
log.info("\u{1F50D} Performing local validation...");
|
|
1227
|
+
const projectData = await withStdoutSuppressed(
|
|
1228
|
+
outputJson,
|
|
1229
|
+
() => validateMCPProject(resolvedPath)
|
|
1230
|
+
);
|
|
1231
|
+
log.info(`\u2705 Entry point: ${projectData.entryPoint}`);
|
|
1232
|
+
log.info(`\u{1F4C4} Found ${Object.keys(projectData.files).length} file(s)`);
|
|
1233
|
+
const localValidation = {
|
|
1234
|
+
isValid: true,
|
|
1235
|
+
errors: [],
|
|
1236
|
+
warnings: []
|
|
1237
|
+
};
|
|
1238
|
+
const denoJsonContent = projectData.files["deno.json"]?.content || projectData.files["deno.jsonc"]?.content;
|
|
1239
|
+
if (!denoJsonContent) {
|
|
1240
|
+
localValidation.errors.push("deno.json or deno.jsonc file not found");
|
|
1241
|
+
localValidation.isValid = false;
|
|
1242
|
+
} else {
|
|
1243
|
+
try {
|
|
1244
|
+
const denoJson = JSON.parse(denoJsonContent);
|
|
1245
|
+
const hasEntryPoint = denoJson.main || denoJson.entryPoint || denoJson.deploy && denoJson.deploy.entrypoint;
|
|
1246
|
+
if (!hasEntryPoint) {
|
|
1247
|
+
localValidation.errors.push('deno.json must have a "main", "entryPoint", or "deploy.entrypoint" field');
|
|
1248
|
+
localValidation.isValid = false;
|
|
1249
|
+
}
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
localValidation.errors.push("deno.json is not valid JSON");
|
|
1252
|
+
localValidation.isValid = false;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
let apiValidation = null;
|
|
1256
|
+
const config = configManager.getConfig();
|
|
1257
|
+
const effectiveEndpoint = options.endpoint || config.endpoints.deploy;
|
|
1258
|
+
if (effectiveEndpoint) {
|
|
1259
|
+
log.info("\u{1F50D} Performing API validation...");
|
|
1260
|
+
const effectiveToken = options.token || config.auth.token;
|
|
1261
|
+
const formData = new FormData();
|
|
1262
|
+
for (const [filename, fileData] of Object.entries(projectData.files)) {
|
|
1263
|
+
const mimeType = fileData.encoding === "base64" ? "application/octet-stream" : "text/plain";
|
|
1264
|
+
formData.append(
|
|
1265
|
+
"files",
|
|
1266
|
+
new Blob([fileData.content], { type: mimeType }),
|
|
1267
|
+
filename
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
const headers = {};
|
|
1271
|
+
if (effectiveToken) {
|
|
1272
|
+
headers["Authorization"] = `Bearer ${effectiveToken}`;
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
const response = await fetch(`${effectiveEndpoint}/api/validate`, {
|
|
1276
|
+
method: "POST",
|
|
1277
|
+
body: formData,
|
|
1278
|
+
headers
|
|
1279
|
+
});
|
|
1280
|
+
if (response.ok) {
|
|
1281
|
+
const result = await response.json();
|
|
1282
|
+
apiValidation = result.validation;
|
|
1283
|
+
log.info("\u2705 API validation completed");
|
|
1284
|
+
} else {
|
|
1285
|
+
log.warn("\u26A0\uFE0F API validation failed, but local validation succeeded");
|
|
1286
|
+
}
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
log.warn("\u26A0\uFE0F API validation unavailable, but local validation succeeded");
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const combinedValidation = {
|
|
1292
|
+
isValid: localValidation.isValid && (apiValidation?.isValid ?? true),
|
|
1293
|
+
errors: [...localValidation.errors, ...apiValidation?.errors || []],
|
|
1294
|
+
warnings: [...localValidation.warnings, ...apiValidation?.warnings || []]
|
|
1295
|
+
};
|
|
1296
|
+
if (outputJson) {
|
|
1297
|
+
console.log(JSON.stringify({
|
|
1298
|
+
local: localValidation,
|
|
1299
|
+
api: apiValidation,
|
|
1300
|
+
combined: combinedValidation
|
|
1301
|
+
}, null, 2));
|
|
1302
|
+
} else {
|
|
1303
|
+
if (combinedValidation.isValid) {
|
|
1304
|
+
log.info("\u2705 Project validation successful!");
|
|
1305
|
+
if (combinedValidation.warnings.length > 0) {
|
|
1306
|
+
log.warn("\u26A0\uFE0F Warnings:");
|
|
1307
|
+
combinedValidation.warnings.forEach((warning) => {
|
|
1308
|
+
log.warn(` - ${warning}`);
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
} else {
|
|
1312
|
+
log.error("\u274C Project validation failed:");
|
|
1313
|
+
combinedValidation.errors.forEach((error) => {
|
|
1314
|
+
log.error(` - ${error}`);
|
|
1315
|
+
});
|
|
1316
|
+
if (combinedValidation.warnings.length > 0) {
|
|
1317
|
+
log.warn("\u26A0\uFE0F Warnings:");
|
|
1318
|
+
combinedValidation.warnings.forEach((warning) => {
|
|
1319
|
+
log.warn(` - ${warning}`);
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
log.error(
|
|
1327
|
+
"\u274C Validation failed:",
|
|
1328
|
+
error instanceof Error ? error.message : String(error)
|
|
1329
|
+
);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// src/commands/auth.ts
|
|
1335
|
+
async function loginCommand(options) {
|
|
1336
|
+
const outputJson = isJsonMode(options);
|
|
1337
|
+
const log = createLogger(options);
|
|
1338
|
+
try {
|
|
1339
|
+
if (options.token) {
|
|
1340
|
+
configManager.setAuth(options.token);
|
|
1341
|
+
if (outputJson) {
|
|
1342
|
+
console.log(JSON.stringify({ success: true, authenticated: true }));
|
|
1343
|
+
} else {
|
|
1344
|
+
log.info("\u{1F510} Authenticating with provided token...");
|
|
1345
|
+
log.info("\u2705 Authentication successful!");
|
|
1346
|
+
}
|
|
1347
|
+
if (!outputJson) {
|
|
1348
|
+
await testAuth(options.endpoint || configManager.getConfig().endpoints.core);
|
|
1349
|
+
}
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (options.email && options.password) {
|
|
1353
|
+
log.info("\u{1F510} Authenticating with email and password...");
|
|
1354
|
+
const endpoint = options.endpoint || configManager.getConfig().endpoints.core;
|
|
1355
|
+
const response = await fetch(`${endpoint}/auth/login`, {
|
|
1356
|
+
method: "POST",
|
|
1357
|
+
headers: {
|
|
1358
|
+
"Content-Type": "application/json"
|
|
1359
|
+
},
|
|
1360
|
+
body: JSON.stringify({
|
|
1361
|
+
email: options.email,
|
|
1362
|
+
password: options.password
|
|
1363
|
+
})
|
|
1364
|
+
});
|
|
1365
|
+
if (!response.ok) {
|
|
1366
|
+
const errorData = await response.json();
|
|
1367
|
+
throw new Error(errorData.error || "Authentication failed");
|
|
1368
|
+
}
|
|
1369
|
+
const result = await response.json();
|
|
1370
|
+
if (result.access_token) {
|
|
1371
|
+
configManager.setAuth(
|
|
1372
|
+
result.access_token,
|
|
1373
|
+
result.refresh_token,
|
|
1374
|
+
result.expires_at
|
|
1375
|
+
);
|
|
1376
|
+
if (outputJson) {
|
|
1377
|
+
console.log(JSON.stringify({ success: true, authenticated: true }));
|
|
1378
|
+
} else {
|
|
1379
|
+
log.info("\u2705 Authentication successful!");
|
|
1380
|
+
}
|
|
1381
|
+
} else {
|
|
1382
|
+
throw new Error("Invalid authentication response");
|
|
1383
|
+
}
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
log.info("\u{1F510} Please provide authentication credentials:");
|
|
1387
|
+
log.info(" Use --token for direct token authentication");
|
|
1388
|
+
log.info(" Use --email and --password for email/password authentication");
|
|
1389
|
+
log.info("");
|
|
1390
|
+
log.info("Example:");
|
|
1391
|
+
log.info(" apexmcp login --token your-jwt-token");
|
|
1392
|
+
log.info(" apexmcp login --email user@example.com --password yourpassword");
|
|
1393
|
+
process.exit(1);
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
log.error(
|
|
1396
|
+
"\u274C Authentication failed:",
|
|
1397
|
+
error instanceof Error ? error.message : String(error)
|
|
1398
|
+
);
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function logoutCommand(options) {
|
|
1403
|
+
const outputJson = isJsonMode(options);
|
|
1404
|
+
const log = createLogger(options);
|
|
1405
|
+
try {
|
|
1406
|
+
configManager.clearAuth();
|
|
1407
|
+
if (outputJson) {
|
|
1408
|
+
console.log(JSON.stringify({ success: true, message: "Logged out successfully" }));
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
log.info("\u{1F6AA} Logging out...");
|
|
1412
|
+
log.info("\u2705 Successfully logged out!");
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
log.error(
|
|
1415
|
+
"\u274C Logout failed:",
|
|
1416
|
+
error instanceof Error ? error.message : String(error)
|
|
1417
|
+
);
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
async function whoamiCommand(options) {
|
|
1422
|
+
const outputJson = isJsonMode(options);
|
|
1423
|
+
const log = createLogger(options);
|
|
1424
|
+
try {
|
|
1425
|
+
const config = configManager.getConfig();
|
|
1426
|
+
const isAuthenticated = configManager.isAuthenticated();
|
|
1427
|
+
const result = {
|
|
1428
|
+
authenticated: isAuthenticated,
|
|
1429
|
+
endpoints: config.endpoints,
|
|
1430
|
+
preferences: config.preferences
|
|
1431
|
+
};
|
|
1432
|
+
if (isAuthenticated) {
|
|
1433
|
+
try {
|
|
1434
|
+
const endpoint = options.endpoint || config.endpoints.core;
|
|
1435
|
+
const token = config.auth.token;
|
|
1436
|
+
const response = await fetch(`${endpoint}/auth/org-status`, {
|
|
1437
|
+
headers: {
|
|
1438
|
+
Authorization: `Bearer ${token}`
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
if (response.ok) {
|
|
1442
|
+
const orgStatus = await response.json();
|
|
1443
|
+
result.orgStatus = orgStatus;
|
|
1444
|
+
}
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
if (outputJson) {
|
|
1449
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1450
|
+
} else {
|
|
1451
|
+
log.info("\u{1F464} Authentication Status:");
|
|
1452
|
+
log.info(` Authenticated: ${isAuthenticated ? "\u2705 Yes" : "\u274C No"}`);
|
|
1453
|
+
if (isAuthenticated) {
|
|
1454
|
+
log.info(" Token: [REDACTED]");
|
|
1455
|
+
if (config.auth.expiresAt) {
|
|
1456
|
+
const expiresAt = new Date(config.auth.expiresAt);
|
|
1457
|
+
log.info(` Expires: ${expiresAt.toLocaleString()}`);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
log.info("");
|
|
1461
|
+
log.info("\u{1F517} Endpoints:");
|
|
1462
|
+
log.info(` Core API: ${config.endpoints.core}`);
|
|
1463
|
+
log.info(` Deploy API: ${config.endpoints.deploy}`);
|
|
1464
|
+
log.info("");
|
|
1465
|
+
log.info("\u2699\uFE0F Preferences:");
|
|
1466
|
+
log.info(` JSON Output: ${config.preferences.json}`);
|
|
1467
|
+
log.info(` Verbose: ${config.preferences.verbose}`);
|
|
1468
|
+
}
|
|
1469
|
+
} catch (error) {
|
|
1470
|
+
log.error(
|
|
1471
|
+
"\u274C Failed to get authentication status:",
|
|
1472
|
+
error instanceof Error ? error.message : String(error)
|
|
1473
|
+
);
|
|
1474
|
+
process.exit(1);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async function configCommand(action, key, value, options) {
|
|
1478
|
+
const outputJson = isJsonMode(options);
|
|
1479
|
+
const log = createLogger(options);
|
|
1480
|
+
try {
|
|
1481
|
+
const config = configManager.getConfig();
|
|
1482
|
+
if (action === "get") {
|
|
1483
|
+
if (outputJson) {
|
|
1484
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1485
|
+
} else {
|
|
1486
|
+
log.info("\u{1F527} Current Configuration:");
|
|
1487
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1488
|
+
}
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (action === "set") {
|
|
1492
|
+
if (!key || value === void 0) {
|
|
1493
|
+
throw new Error("Key and value are required for set action");
|
|
1494
|
+
}
|
|
1495
|
+
const keys = key.split(".");
|
|
1496
|
+
let current = config;
|
|
1497
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1498
|
+
const currentKey = keys[i];
|
|
1499
|
+
if (!currentKey) {
|
|
1500
|
+
throw new Error("Invalid key format");
|
|
1501
|
+
}
|
|
1502
|
+
if (!current[currentKey]) {
|
|
1503
|
+
current[currentKey] = {};
|
|
1504
|
+
}
|
|
1505
|
+
current = current[currentKey];
|
|
1506
|
+
}
|
|
1507
|
+
const finalKey = keys[keys.length - 1];
|
|
1508
|
+
if (!finalKey) {
|
|
1509
|
+
throw new Error("Invalid key format");
|
|
1510
|
+
}
|
|
1511
|
+
let parsedValue = value;
|
|
1512
|
+
if (value === "true") parsedValue = true;
|
|
1513
|
+
else if (value === "false") parsedValue = false;
|
|
1514
|
+
else if (!isNaN(Number(value))) parsedValue = Number(value);
|
|
1515
|
+
current[finalKey] = parsedValue;
|
|
1516
|
+
configManager.updateConfig(config);
|
|
1517
|
+
if (outputJson) {
|
|
1518
|
+
console.log(JSON.stringify({ success: true, key, value: parsedValue }, null, 2));
|
|
1519
|
+
} else {
|
|
1520
|
+
log.info(`\u2705 Set ${key} = ${parsedValue}`);
|
|
1521
|
+
}
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
if (action === "reset") {
|
|
1525
|
+
configManager.updateConfig({
|
|
1526
|
+
version: "0.1.0",
|
|
1527
|
+
endpoints: {
|
|
1528
|
+
core: "https://core.apexmcp.dev",
|
|
1529
|
+
deploy: "https://deploy.apexmcp.dev"
|
|
1530
|
+
},
|
|
1531
|
+
auth: {},
|
|
1532
|
+
preferences: {
|
|
1533
|
+
json: false,
|
|
1534
|
+
verbose: false
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
if (outputJson) {
|
|
1538
|
+
console.log(JSON.stringify({ success: true, message: "Configuration reset to defaults" }, null, 2));
|
|
1539
|
+
} else {
|
|
1540
|
+
log.info("\u2705 Configuration reset to defaults");
|
|
1541
|
+
}
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
throw new Error("Invalid action. Use: get, set <key> <value>, or reset");
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
log.error(
|
|
1547
|
+
"\u274C Configuration command failed:",
|
|
1548
|
+
error instanceof Error ? error.message : String(error)
|
|
1549
|
+
);
|
|
1550
|
+
process.exit(1);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
async function testAuth(endpoint) {
|
|
1554
|
+
const log = createLogger({ json: false });
|
|
1555
|
+
try {
|
|
1556
|
+
const token = configManager.getToken();
|
|
1557
|
+
if (!token) {
|
|
1558
|
+
throw new Error("No authentication token available");
|
|
1559
|
+
}
|
|
1560
|
+
const response = await fetch(`${endpoint}/health`, {
|
|
1561
|
+
headers: {
|
|
1562
|
+
Authorization: `Bearer ${token}`
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
if (!response.ok) {
|
|
1566
|
+
throw new Error(`Authentication test failed: ${response.status}`);
|
|
1567
|
+
}
|
|
1568
|
+
log.info("\u2705 Token validation successful");
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
log.warn("\u26A0\uFE0F Token validation failed, but token was saved");
|
|
1571
|
+
log.warn(" You may need to re-authenticate");
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/commands/mcp-servers.ts
|
|
1576
|
+
async function listAvailableMcpServersCommand(options) {
|
|
1577
|
+
const outputJson = isJsonMode(options);
|
|
1578
|
+
const log = createLogger(options);
|
|
1579
|
+
try {
|
|
1580
|
+
log.info("\u{1F4CB} Fetching available MCP servers...");
|
|
1581
|
+
const config = configManager.getConfig();
|
|
1582
|
+
const effectiveOptions = {
|
|
1583
|
+
...options,
|
|
1584
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1585
|
+
token: options.token || config.auth.token
|
|
1586
|
+
};
|
|
1587
|
+
const headers = {};
|
|
1588
|
+
if (effectiveOptions.token) {
|
|
1589
|
+
headers["Authorization"] = `Bearer ${effectiveOptions.token}`;
|
|
1590
|
+
}
|
|
1591
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/available`, {
|
|
1592
|
+
method: "GET",
|
|
1593
|
+
headers
|
|
1594
|
+
});
|
|
1595
|
+
if (!response.ok) {
|
|
1596
|
+
const errorText = await response.text();
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
`Failed to list MCP servers: ${response.status} ${response.statusText}
|
|
1599
|
+
${errorText}`
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
const result = await response.json();
|
|
1603
|
+
if (outputJson) {
|
|
1604
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1605
|
+
} else {
|
|
1606
|
+
if (result.plugins.length === 0) {
|
|
1607
|
+
log.info("\u{1F4ED} No MCP servers available.");
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
log.info(`\u{1F4CB} Found ${result.plugins.length} MCP server(s) in catalog:
|
|
1611
|
+
`);
|
|
1612
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1613
|
+
log.info("\u2502 MCP Server ID \u2502 Name \u2502 Status \u2502 Enabled \u2502");
|
|
1614
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
1615
|
+
for (const server of result.plugins) {
|
|
1616
|
+
const id = server.catalog_id.substring(0, 36).padEnd(36);
|
|
1617
|
+
const name = (server.name || "N/A").substring(0, 18).padEnd(18);
|
|
1618
|
+
const status = server.status.substring(0, 7).padEnd(7);
|
|
1619
|
+
const enabled = server.enabled ? "\u2705 Yes" : "\u274C No";
|
|
1620
|
+
const enabledPadded = enabled.substring(0, 7).padEnd(7);
|
|
1621
|
+
log.info(`\u2502 ${id} \u2502 ${name} \u2502 ${status} \u2502 ${enabledPadded} \u2502`);
|
|
1622
|
+
}
|
|
1623
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
1624
|
+
for (const server of result.plugins) {
|
|
1625
|
+
log.info(`\u{1F527} ${server.name} (${server.catalog_id}):`);
|
|
1626
|
+
log.info(` \u{1F4DD} Description: ${server.description || "No description"}`);
|
|
1627
|
+
log.info(` \u{1F310} Endpoint: ${server.http_endpoint}`);
|
|
1628
|
+
log.info(` \u{1F4CA} Status: ${server.status}`);
|
|
1629
|
+
log.info(` \u2705 Enabled: ${server.enabled ? "Yes" : "No"}`);
|
|
1630
|
+
if (server.secrets_requirements && server.secrets_requirements.length > 0) {
|
|
1631
|
+
log.info(` \u{1F510} Requires ${server.secrets_requirements.length} secret(s)`);
|
|
1632
|
+
}
|
|
1633
|
+
log.info("");
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
log.error(
|
|
1638
|
+
"\u274C Failed to list MCP servers:",
|
|
1639
|
+
error instanceof Error ? error.message : String(error)
|
|
1640
|
+
);
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
async function listEnabledMcpServersCommand(options) {
|
|
1645
|
+
const outputJson = isJsonMode(options);
|
|
1646
|
+
const log = createLogger(options);
|
|
1647
|
+
try {
|
|
1648
|
+
log.info("\u{1F4CB} Fetching enabled MCP servers...");
|
|
1649
|
+
const config = configManager.getConfig();
|
|
1650
|
+
const effectiveOptions = {
|
|
1651
|
+
...options,
|
|
1652
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1653
|
+
token: options.token || config.auth.token
|
|
1654
|
+
};
|
|
1655
|
+
if (!effectiveOptions.token) {
|
|
1656
|
+
throw new Error(
|
|
1657
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
const headers = {
|
|
1661
|
+
"Authorization": `Bearer ${effectiveOptions.token}`
|
|
1662
|
+
};
|
|
1663
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/enabled`, {
|
|
1664
|
+
method: "GET",
|
|
1665
|
+
headers
|
|
1666
|
+
});
|
|
1667
|
+
if (!response.ok) {
|
|
1668
|
+
const errorText = await response.text();
|
|
1669
|
+
throw new Error(
|
|
1670
|
+
`Failed to list enabled MCP servers: ${response.status} ${response.statusText}
|
|
1671
|
+
${errorText}`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
const result = await response.json();
|
|
1675
|
+
if (outputJson) {
|
|
1676
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1677
|
+
} else {
|
|
1678
|
+
if (result.plugins.length === 0) {
|
|
1679
|
+
log.info("\u{1F4ED} No MCP servers enabled for your organization.");
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
log.info(`\u{1F4CB} Found ${result.plugins.length} enabled MCP server(s):
|
|
1683
|
+
`);
|
|
1684
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1685
|
+
log.info("\u2502 MCP Server ID \u2502 Name \u2502 Status \u2502 Config \u2502");
|
|
1686
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
1687
|
+
for (const server of result.plugins) {
|
|
1688
|
+
const id = server.catalog_id.substring(0, 36).padEnd(36);
|
|
1689
|
+
const name = (server.name || "N/A").substring(0, 18).padEnd(18);
|
|
1690
|
+
const status = "Enabled";
|
|
1691
|
+
const configStr = server.config.rate_limit ? `${server.config.rate_limit}/min` : "Default";
|
|
1692
|
+
const configPadded = configStr.substring(0, 7).padEnd(7);
|
|
1693
|
+
log.info(`\u2502 ${id} \u2502 ${name} \u2502 ${status} \u2502 ${configPadded} \u2502`);
|
|
1694
|
+
}
|
|
1695
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
1696
|
+
for (const server of result.plugins) {
|
|
1697
|
+
log.info(`\u{1F527} ${server.name} (${server.catalog_id}):`);
|
|
1698
|
+
log.info(` \u{1F4DD} Description: ${server.description || "No description"}`);
|
|
1699
|
+
log.info(` \u{1F310} Endpoint: ${server.http_endpoint || "N/A"}`);
|
|
1700
|
+
log.info(` \u2699\uFE0F Config: Rate limit ${server.config.rate_limit || "unlimited"}/min, Timeout ${server.config.timeout_ms || 3e4}ms`);
|
|
1701
|
+
if (server.secrets_requirements && server.secrets_requirements.length > 0) {
|
|
1702
|
+
log.info(` \u{1F510} Secrets configured: ${server.secrets_requirements.length} required`);
|
|
1703
|
+
}
|
|
1704
|
+
log.info("");
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
log.error(
|
|
1709
|
+
"\u274C Failed to list enabled MCP servers:",
|
|
1710
|
+
error instanceof Error ? error.message : String(error)
|
|
1711
|
+
);
|
|
1712
|
+
process.exit(1);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
async function enableMcpServerCommand(catalogId, options) {
|
|
1716
|
+
const outputJson = isJsonMode(options);
|
|
1717
|
+
const log = createLogger(options);
|
|
1718
|
+
try {
|
|
1719
|
+
log.info(`\u{1F527} Enabling MCP server: ${catalogId}`);
|
|
1720
|
+
const config = configManager.getConfig();
|
|
1721
|
+
const effectiveOptions = {
|
|
1722
|
+
...options,
|
|
1723
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1724
|
+
token: options.token || config.auth.token
|
|
1725
|
+
};
|
|
1726
|
+
if (!effectiveOptions.token) {
|
|
1727
|
+
throw new Error(
|
|
1728
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
const headers = {
|
|
1732
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
1733
|
+
"Content-Type": "application/json"
|
|
1734
|
+
};
|
|
1735
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/toggle`, {
|
|
1736
|
+
method: "POST",
|
|
1737
|
+
headers,
|
|
1738
|
+
body: JSON.stringify({
|
|
1739
|
+
catalog_id: catalogId,
|
|
1740
|
+
enabled: true
|
|
1741
|
+
})
|
|
1742
|
+
});
|
|
1743
|
+
if (!response.ok) {
|
|
1744
|
+
const errorText = await response.text();
|
|
1745
|
+
throw new Error(
|
|
1746
|
+
`Failed to enable MCP server: ${response.status} ${response.statusText}
|
|
1747
|
+
${errorText}`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
const result = await response.json();
|
|
1751
|
+
if (outputJson) {
|
|
1752
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1753
|
+
} else {
|
|
1754
|
+
if (result.success) {
|
|
1755
|
+
log.info("\u2705 MCP server enabled successfully!");
|
|
1756
|
+
log.info("\u2139\uFE0F You may need to configure secrets for this server to work properly.");
|
|
1757
|
+
log.info(` Use: apexmcp plugins secrets set ${catalogId} <secret-key> <secret-value>`);
|
|
1758
|
+
} else {
|
|
1759
|
+
log.error("\u274C Failed to enable MCP server");
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
log.error(
|
|
1764
|
+
"\u274C Failed to enable MCP server:",
|
|
1765
|
+
error instanceof Error ? error.message : String(error)
|
|
1766
|
+
);
|
|
1767
|
+
process.exit(1);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async function disableMcpServerCommand(catalogId, options) {
|
|
1771
|
+
const outputJson = isJsonMode(options);
|
|
1772
|
+
const log = createLogger(options);
|
|
1773
|
+
try {
|
|
1774
|
+
log.info(`\u{1F527} Disabling MCP server: ${catalogId}`);
|
|
1775
|
+
const config = configManager.getConfig();
|
|
1776
|
+
const effectiveOptions = {
|
|
1777
|
+
...options,
|
|
1778
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1779
|
+
token: options.token || config.auth.token
|
|
1780
|
+
};
|
|
1781
|
+
if (!effectiveOptions.token) {
|
|
1782
|
+
throw new Error(
|
|
1783
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
const headers = {
|
|
1787
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
1788
|
+
"Content-Type": "application/json"
|
|
1789
|
+
};
|
|
1790
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/toggle`, {
|
|
1791
|
+
method: "POST",
|
|
1792
|
+
headers,
|
|
1793
|
+
body: JSON.stringify({
|
|
1794
|
+
catalog_id: catalogId,
|
|
1795
|
+
enabled: false
|
|
1796
|
+
})
|
|
1797
|
+
});
|
|
1798
|
+
if (!response.ok) {
|
|
1799
|
+
const errorText = await response.text();
|
|
1800
|
+
throw new Error(
|
|
1801
|
+
`Failed to disable MCP server: ${response.status} ${response.statusText}
|
|
1802
|
+
${errorText}`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
const result = await response.json();
|
|
1806
|
+
if (outputJson) {
|
|
1807
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1808
|
+
} else {
|
|
1809
|
+
if (result.success) {
|
|
1810
|
+
log.info("\u2705 MCP server disabled successfully!");
|
|
1811
|
+
} else {
|
|
1812
|
+
log.error("\u274C Failed to disable MCP server");
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
log.error(
|
|
1817
|
+
"\u274C Failed to disable MCP server:",
|
|
1818
|
+
error instanceof Error ? error.message : String(error)
|
|
1819
|
+
);
|
|
1820
|
+
process.exit(1);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
async function getMcpServerCommand(catalogId, options) {
|
|
1824
|
+
const outputJson = isJsonMode(options);
|
|
1825
|
+
const log = createLogger(options);
|
|
1826
|
+
try {
|
|
1827
|
+
log.info(`\u{1F4CB} Fetching MCP server details: ${catalogId}`);
|
|
1828
|
+
const config = configManager.getConfig();
|
|
1829
|
+
const effectiveOptions = {
|
|
1830
|
+
...options,
|
|
1831
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1832
|
+
token: options.token || config.auth.token
|
|
1833
|
+
};
|
|
1834
|
+
const headers = {};
|
|
1835
|
+
if (effectiveOptions.token) {
|
|
1836
|
+
headers["Authorization"] = `Bearer ${effectiveOptions.token}`;
|
|
1837
|
+
}
|
|
1838
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/available`, {
|
|
1839
|
+
method: "GET",
|
|
1840
|
+
headers
|
|
1841
|
+
});
|
|
1842
|
+
if (!response.ok) {
|
|
1843
|
+
const errorText = await response.text();
|
|
1844
|
+
throw new Error(
|
|
1845
|
+
`Failed to get MCP server: ${response.status} ${response.statusText}
|
|
1846
|
+
${errorText}`
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
const result = await response.json();
|
|
1850
|
+
const server = result.plugins.find((p) => p.catalog_id === catalogId);
|
|
1851
|
+
if (!server) {
|
|
1852
|
+
throw new Error(`MCP server '${catalogId}' not found in catalog`);
|
|
1853
|
+
}
|
|
1854
|
+
if (outputJson) {
|
|
1855
|
+
console.log(JSON.stringify({ server }, null, 2));
|
|
1856
|
+
} else {
|
|
1857
|
+
log.info(`\u{1F527} MCP Server: ${server.name}`);
|
|
1858
|
+
log.info(` \u{1F4DD} Description: ${server.description || "No description"}`);
|
|
1859
|
+
log.info(` \u{1F194} Catalog ID: ${server.catalog_id}`);
|
|
1860
|
+
log.info(` \u{1F310} Endpoint: ${server.http_endpoint}`);
|
|
1861
|
+
log.info(` \u{1F4CA} Status: ${server.status}`);
|
|
1862
|
+
log.info(` \u2705 Enabled: ${server.enabled ? "Yes" : "No"}`);
|
|
1863
|
+
if (server.secrets_requirements && server.secrets_requirements.length > 0) {
|
|
1864
|
+
log.info(` \u{1F510} Secrets Requirements:`);
|
|
1865
|
+
for (const secret of server.secrets_requirements) {
|
|
1866
|
+
const required = secret.required ? "(required)" : "(optional)";
|
|
1867
|
+
log.info(` \u2022 ${secret.name} (${secret.key}) ${required}: ${secret.description}`);
|
|
1868
|
+
if (secret.placeholder) {
|
|
1869
|
+
log.info(` Placeholder: ${secret.placeholder}`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
} else {
|
|
1873
|
+
log.info(` \u{1F510} Secrets Requirements: None`);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
log.error(
|
|
1878
|
+
"\u274C Failed to get MCP server details:",
|
|
1879
|
+
error instanceof Error ? error.message : String(error)
|
|
1880
|
+
);
|
|
1881
|
+
process.exit(1);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// src/commands/plugins.ts
|
|
1886
|
+
async function listPluginSecretsCommand(catalogId, options) {
|
|
1887
|
+
const outputJson = isJsonMode(options);
|
|
1888
|
+
const log = createLogger(options);
|
|
1889
|
+
try {
|
|
1890
|
+
log.info(`\u{1F510} Fetching secrets for MCP server: ${catalogId}`);
|
|
1891
|
+
const config = configManager.getConfig();
|
|
1892
|
+
const effectiveOptions = {
|
|
1893
|
+
...options,
|
|
1894
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1895
|
+
token: options.token || config.auth.token
|
|
1896
|
+
};
|
|
1897
|
+
if (!effectiveOptions.token) {
|
|
1898
|
+
throw new Error(
|
|
1899
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
const headers = {
|
|
1903
|
+
"Authorization": `Bearer ${effectiveOptions.token}`
|
|
1904
|
+
};
|
|
1905
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/${catalogId}/secrets`, {
|
|
1906
|
+
method: "GET",
|
|
1907
|
+
headers
|
|
1908
|
+
});
|
|
1909
|
+
if (!response.ok) {
|
|
1910
|
+
const errorText = await response.text();
|
|
1911
|
+
throw new Error(
|
|
1912
|
+
`Failed to list plugin secrets: ${response.status} ${response.statusText}
|
|
1913
|
+
${errorText}`
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
const result = await response.json();
|
|
1917
|
+
if (outputJson) {
|
|
1918
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1919
|
+
} else {
|
|
1920
|
+
if (result.secrets.length === 0) {
|
|
1921
|
+
log.info("\u{1F510} No secrets configured for this MCP server.");
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
log.info(`\u{1F510} Found ${result.secrets.length} secret(s) configured:
|
|
1925
|
+
`);
|
|
1926
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1927
|
+
log.info("\u2502 Secret Key \u2502");
|
|
1928
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
1929
|
+
for (const secret of result.secrets) {
|
|
1930
|
+
const key = secret.secret_key.substring(0, 18).padEnd(18);
|
|
1931
|
+
log.info(`\u2502 ${key} \u2502`);
|
|
1932
|
+
}
|
|
1933
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
1934
|
+
log.info("\u{1F4A1} Note: Secret values are not displayed for security reasons.");
|
|
1935
|
+
}
|
|
1936
|
+
} catch (error) {
|
|
1937
|
+
log.error(
|
|
1938
|
+
"\u274C Failed to list plugin secrets:",
|
|
1939
|
+
error instanceof Error ? error.message : String(error)
|
|
1940
|
+
);
|
|
1941
|
+
process.exit(1);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
async function setPluginSecretCommand(catalogId, secretKey, secretValue, options) {
|
|
1945
|
+
const outputJson = isJsonMode(options);
|
|
1946
|
+
const log = createLogger(options);
|
|
1947
|
+
try {
|
|
1948
|
+
log.info(`\u{1F510} Setting secret '${secretKey}' for MCP server: ${catalogId}`);
|
|
1949
|
+
const config = configManager.getConfig();
|
|
1950
|
+
const effectiveOptions = {
|
|
1951
|
+
...options,
|
|
1952
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
1953
|
+
token: options.token || config.auth.token
|
|
1954
|
+
};
|
|
1955
|
+
if (!effectiveOptions.token) {
|
|
1956
|
+
throw new Error(
|
|
1957
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
const headers = {
|
|
1961
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
1962
|
+
"Content-Type": "application/json"
|
|
1963
|
+
};
|
|
1964
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/${catalogId}/secrets`, {
|
|
1965
|
+
method: "POST",
|
|
1966
|
+
headers,
|
|
1967
|
+
body: JSON.stringify({
|
|
1968
|
+
secret_key: secretKey,
|
|
1969
|
+
secret_value: secretValue
|
|
1970
|
+
})
|
|
1971
|
+
});
|
|
1972
|
+
if (!response.ok) {
|
|
1973
|
+
const errorText = await response.text();
|
|
1974
|
+
throw new Error(
|
|
1975
|
+
`Failed to set plugin secret: ${response.status} ${response.statusText}
|
|
1976
|
+
${errorText}`
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
const result = await response.json();
|
|
1980
|
+
if (outputJson) {
|
|
1981
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1982
|
+
} else {
|
|
1983
|
+
if (result.success) {
|
|
1984
|
+
log.info("\u2705 Plugin secret set successfully!");
|
|
1985
|
+
} else {
|
|
1986
|
+
log.error("\u274C Failed to set plugin secret");
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
log.error(
|
|
1991
|
+
"\u274C Failed to set plugin secret:",
|
|
1992
|
+
error instanceof Error ? error.message : String(error)
|
|
1993
|
+
);
|
|
1994
|
+
process.exit(1);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
async function deletePluginSecretCommand(catalogId, secretKey, options) {
|
|
1998
|
+
const outputJson = isJsonMode(options);
|
|
1999
|
+
const log = createLogger(options);
|
|
2000
|
+
try {
|
|
2001
|
+
log.info(`\u{1F5D1}\uFE0F Deleting secret '${secretKey}' for MCP server: ${catalogId}`);
|
|
2002
|
+
const config = configManager.getConfig();
|
|
2003
|
+
const effectiveOptions = {
|
|
2004
|
+
...options,
|
|
2005
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2006
|
+
token: options.token || config.auth.token
|
|
2007
|
+
};
|
|
2008
|
+
if (!effectiveOptions.token) {
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
const headers = {
|
|
2014
|
+
"Authorization": `Bearer ${effectiveOptions.token}`
|
|
2015
|
+
};
|
|
2016
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/${catalogId}/secrets/${secretKey}`, {
|
|
2017
|
+
method: "DELETE",
|
|
2018
|
+
headers
|
|
2019
|
+
});
|
|
2020
|
+
if (!response.ok) {
|
|
2021
|
+
const errorText = await response.text();
|
|
2022
|
+
throw new Error(
|
|
2023
|
+
`Failed to delete plugin secret: ${response.status} ${response.statusText}
|
|
2024
|
+
${errorText}`
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
const result = await response.json();
|
|
2028
|
+
if (outputJson) {
|
|
2029
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2030
|
+
} else {
|
|
2031
|
+
if (result.success) {
|
|
2032
|
+
log.info("\u2705 Plugin secret deleted successfully!");
|
|
2033
|
+
} else {
|
|
2034
|
+
log.error("\u274C Failed to delete plugin secret");
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
} catch (error) {
|
|
2038
|
+
log.error(
|
|
2039
|
+
"\u274C Failed to delete plugin secret:",
|
|
2040
|
+
error instanceof Error ? error.message : String(error)
|
|
2041
|
+
);
|
|
2042
|
+
process.exit(1);
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
async function deletePluginCommand(catalogId, options) {
|
|
2046
|
+
const outputJson = isJsonMode(options);
|
|
2047
|
+
const log = createLogger(options);
|
|
2048
|
+
try {
|
|
2049
|
+
log.info(`\u{1F5D1}\uFE0F Deleting plugin configuration: ${catalogId}`);
|
|
2050
|
+
log.info("\u26A0\uFE0F This will disable the plugin and remove all associated secrets.");
|
|
2051
|
+
const config = configManager.getConfig();
|
|
2052
|
+
const effectiveOptions = {
|
|
2053
|
+
...options,
|
|
2054
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2055
|
+
token: options.token || config.auth.token
|
|
2056
|
+
};
|
|
2057
|
+
if (!effectiveOptions.token) {
|
|
2058
|
+
throw new Error(
|
|
2059
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
const headers = {
|
|
2063
|
+
"Authorization": `Bearer ${effectiveOptions.token}`
|
|
2064
|
+
};
|
|
2065
|
+
const response = await fetch(`${effectiveOptions.endpoint}/plugins/${catalogId}`, {
|
|
2066
|
+
method: "DELETE",
|
|
2067
|
+
headers
|
|
2068
|
+
});
|
|
2069
|
+
if (!response.ok) {
|
|
2070
|
+
const errorText = await response.text();
|
|
2071
|
+
throw new Error(
|
|
2072
|
+
`Failed to delete plugin: ${response.status} ${response.statusText}
|
|
2073
|
+
${errorText}`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
const result = await response.json();
|
|
2077
|
+
if (outputJson) {
|
|
2078
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2079
|
+
} else {
|
|
2080
|
+
if (result.success) {
|
|
2081
|
+
log.info("\u2705 Plugin deleted successfully!");
|
|
2082
|
+
} else {
|
|
2083
|
+
log.error("\u274C Failed to delete plugin");
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
} catch (error) {
|
|
2087
|
+
log.error(
|
|
2088
|
+
"\u274C Failed to delete plugin:",
|
|
2089
|
+
error instanceof Error ? error.message : String(error)
|
|
2090
|
+
);
|
|
2091
|
+
process.exit(1);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// src/commands/mcp.ts
|
|
2096
|
+
async function listToolsCommand(options) {
|
|
2097
|
+
const outputJson = isJsonMode(options);
|
|
2098
|
+
const log = createLogger(options);
|
|
2099
|
+
try {
|
|
2100
|
+
log.info("\u{1F527} Fetching available tools...");
|
|
2101
|
+
const config = configManager.getConfig();
|
|
2102
|
+
const effectiveOptions = {
|
|
2103
|
+
...options,
|
|
2104
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2105
|
+
token: options.token || config.auth.token
|
|
2106
|
+
};
|
|
2107
|
+
if (!effectiveOptions.token) {
|
|
2108
|
+
throw new Error(
|
|
2109
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
const headers = {
|
|
2113
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
2114
|
+
"Content-Type": "application/json"
|
|
2115
|
+
};
|
|
2116
|
+
const response = await fetch(`${effectiveOptions.endpoint}/tools/list`, {
|
|
2117
|
+
method: "POST",
|
|
2118
|
+
headers,
|
|
2119
|
+
body: JSON.stringify({
|
|
2120
|
+
jsonrpc: "2.0",
|
|
2121
|
+
id: Date.now(),
|
|
2122
|
+
method: "tools/list",
|
|
2123
|
+
params: {}
|
|
2124
|
+
})
|
|
2125
|
+
});
|
|
2126
|
+
if (!response.ok) {
|
|
2127
|
+
const errorText = await response.text();
|
|
2128
|
+
throw new Error(
|
|
2129
|
+
`Failed to list tools: ${response.status} ${response.statusText}
|
|
2130
|
+
${errorText}`
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
const result = await response.json();
|
|
2134
|
+
if (outputJson) {
|
|
2135
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2136
|
+
} else {
|
|
2137
|
+
if (!result.result || !result.result.tools || result.result.tools.length === 0) {
|
|
2138
|
+
log.info("\u{1F527} No tools available.");
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const tools = result.result.tools;
|
|
2142
|
+
log.info(`\u{1F527} Found ${tools.length} tool(s):
|
|
2143
|
+
`);
|
|
2144
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
2145
|
+
log.info("\u2502 Tool Name \u2502 Description \u2502");
|
|
2146
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
2147
|
+
for (const tool of tools) {
|
|
2148
|
+
const name = tool.name.substring(0, 36).padEnd(36);
|
|
2149
|
+
const description = (tool.description || "No description").substring(0, 36).padEnd(36);
|
|
2150
|
+
log.info(`\u2502 ${name} \u2502 ${description} \u2502`);
|
|
2151
|
+
}
|
|
2152
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
2153
|
+
for (const tool of tools) {
|
|
2154
|
+
log.info(`\u{1F527} ${tool.name}:`);
|
|
2155
|
+
log.info(` \u{1F4DD} Description: ${tool.description || "No description"}`);
|
|
2156
|
+
if (tool.inputSchema && tool.inputSchema.properties) {
|
|
2157
|
+
log.info(` \u{1F4E5} Parameters:`);
|
|
2158
|
+
const properties = tool.inputSchema.properties;
|
|
2159
|
+
for (const [paramName, paramSchema] of Object.entries(properties)) {
|
|
2160
|
+
const param = paramSchema;
|
|
2161
|
+
const required = tool.inputSchema.required?.includes(paramName) ? "(required)" : "(optional)";
|
|
2162
|
+
log.info(` \u2022 ${paramName} ${required}: ${param.description || "No description"}`);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
log.info("");
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
} catch (error) {
|
|
2169
|
+
log.error(
|
|
2170
|
+
"\u274C Failed to list tools:",
|
|
2171
|
+
error instanceof Error ? error.message : String(error)
|
|
2172
|
+
);
|
|
2173
|
+
process.exit(1);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
async function callToolCommand(toolName, paramsJson, options) {
|
|
2177
|
+
const outputJson = isJsonMode(options);
|
|
2178
|
+
const log = createLogger(options);
|
|
2179
|
+
try {
|
|
2180
|
+
log.info(`\u{1F527} Calling tool: ${toolName}`);
|
|
2181
|
+
let params;
|
|
2182
|
+
try {
|
|
2183
|
+
params = JSON.parse(paramsJson);
|
|
2184
|
+
} catch (error) {
|
|
2185
|
+
throw new Error(`Invalid JSON parameters: ${paramsJson}`);
|
|
2186
|
+
}
|
|
2187
|
+
const config = configManager.getConfig();
|
|
2188
|
+
const effectiveOptions = {
|
|
2189
|
+
...options,
|
|
2190
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2191
|
+
token: options.token || config.auth.token
|
|
2192
|
+
};
|
|
2193
|
+
if (!effectiveOptions.token) {
|
|
2194
|
+
throw new Error(
|
|
2195
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
const headers = {
|
|
2199
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
2200
|
+
"Content-Type": "application/json"
|
|
2201
|
+
};
|
|
2202
|
+
const response = await fetch(`${effectiveOptions.endpoint}/tools/call`, {
|
|
2203
|
+
method: "POST",
|
|
2204
|
+
headers,
|
|
2205
|
+
body: JSON.stringify({
|
|
2206
|
+
jsonrpc: "2.0",
|
|
2207
|
+
id: Date.now(),
|
|
2208
|
+
method: "tools/call",
|
|
2209
|
+
params: {
|
|
2210
|
+
name: toolName,
|
|
2211
|
+
arguments: params
|
|
2212
|
+
}
|
|
2213
|
+
})
|
|
2214
|
+
});
|
|
2215
|
+
if (!response.ok) {
|
|
2216
|
+
const errorText = await response.text();
|
|
2217
|
+
throw new Error(
|
|
2218
|
+
`Failed to call tool: ${response.status} ${response.statusText}
|
|
2219
|
+
${errorText}`
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
const result = await response.json();
|
|
2223
|
+
if (outputJson) {
|
|
2224
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2225
|
+
} else {
|
|
2226
|
+
if (result.error) {
|
|
2227
|
+
log.error("\u274C Tool call failed:", result.error.message);
|
|
2228
|
+
if (result.error.data) {
|
|
2229
|
+
log.error(" Details:", JSON.stringify(result.error.data, null, 2));
|
|
2230
|
+
}
|
|
2231
|
+
} else if (result.result) {
|
|
2232
|
+
log.info("\u2705 Tool call successful!");
|
|
2233
|
+
if (result.result.content && Array.isArray(result.result.content)) {
|
|
2234
|
+
for (const content of result.result.content) {
|
|
2235
|
+
if (content.type === "text") {
|
|
2236
|
+
log.info("\u{1F4C4} Result:", content.text);
|
|
2237
|
+
} else {
|
|
2238
|
+
log.info(`\u{1F4C4} Result (${content.type}):`, JSON.stringify(content, null, 2));
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
} else {
|
|
2242
|
+
log.info("\u{1F4C4} Result:", JSON.stringify(result.result, null, 2));
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
log.error(
|
|
2248
|
+
"\u274C Failed to call tool:",
|
|
2249
|
+
error instanceof Error ? error.message : String(error)
|
|
2250
|
+
);
|
|
2251
|
+
process.exit(1);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
async function listResourcesCommand(options) {
|
|
2255
|
+
const outputJson = isJsonMode(options);
|
|
2256
|
+
const log = createLogger(options);
|
|
2257
|
+
try {
|
|
2258
|
+
log.info("\u{1F4C4} Fetching available resources...");
|
|
2259
|
+
const config = configManager.getConfig();
|
|
2260
|
+
const effectiveOptions = {
|
|
2261
|
+
...options,
|
|
2262
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2263
|
+
token: options.token || config.auth.token
|
|
2264
|
+
};
|
|
2265
|
+
if (!effectiveOptions.token) {
|
|
2266
|
+
throw new Error(
|
|
2267
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
const headers = {
|
|
2271
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
2272
|
+
"Content-Type": "application/json"
|
|
2273
|
+
};
|
|
2274
|
+
const response = await fetch(`${effectiveOptions.endpoint}/resources/list`, {
|
|
2275
|
+
method: "POST",
|
|
2276
|
+
headers,
|
|
2277
|
+
body: JSON.stringify({
|
|
2278
|
+
jsonrpc: "2.0",
|
|
2279
|
+
id: Date.now(),
|
|
2280
|
+
method: "resources/list",
|
|
2281
|
+
params: {}
|
|
2282
|
+
})
|
|
2283
|
+
});
|
|
2284
|
+
if (!response.ok) {
|
|
2285
|
+
const errorText = await response.text();
|
|
2286
|
+
throw new Error(
|
|
2287
|
+
`Failed to list resources: ${response.status} ${response.statusText}
|
|
2288
|
+
${errorText}`
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
const result = await response.json();
|
|
2292
|
+
if (outputJson) {
|
|
2293
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2294
|
+
} else {
|
|
2295
|
+
if (!result.result || !result.result.resources || result.result.resources.length === 0) {
|
|
2296
|
+
log.info("\u{1F4C4} No resources available.");
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
const resources = result.result.resources;
|
|
2300
|
+
log.info(`\u{1F4C4} Found ${resources.length} resource(s):
|
|
2301
|
+
`);
|
|
2302
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
2303
|
+
log.info("\u2502 Resource URI \u2502 Name \u2502 MIME Type \u2502");
|
|
2304
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
2305
|
+
for (const resource of resources) {
|
|
2306
|
+
const uri = resource.uri.substring(0, 36).padEnd(36);
|
|
2307
|
+
const name = (resource.name || "Unnamed").substring(0, 36).padEnd(36);
|
|
2308
|
+
const mimeType = (resource.mimeType || "unknown").substring(0, 10).padEnd(10);
|
|
2309
|
+
log.info(`\u2502 ${uri} \u2502 ${name} \u2502 ${mimeType} \u2502`);
|
|
2310
|
+
}
|
|
2311
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
2312
|
+
for (const resource of resources) {
|
|
2313
|
+
log.info(`\u{1F4C4} ${resource.name || "Unnamed"}:`);
|
|
2314
|
+
log.info(` \u{1F310} URI: ${resource.uri}`);
|
|
2315
|
+
log.info(` \u{1F4DD} Description: ${resource.description || "No description"}`);
|
|
2316
|
+
log.info(` \u{1F4CB} MIME Type: ${resource.mimeType || "unknown"}`);
|
|
2317
|
+
log.info("");
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
log.error(
|
|
2322
|
+
"\u274C Failed to list resources:",
|
|
2323
|
+
error instanceof Error ? error.message : String(error)
|
|
2324
|
+
);
|
|
2325
|
+
process.exit(1);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
async function readResourceCommand(resourceUri, options) {
|
|
2329
|
+
const outputJson = isJsonMode(options);
|
|
2330
|
+
const log = createLogger(options);
|
|
2331
|
+
try {
|
|
2332
|
+
log.info(`\u{1F4D6} Reading resource: ${resourceUri}`);
|
|
2333
|
+
const config = configManager.getConfig();
|
|
2334
|
+
const effectiveOptions = {
|
|
2335
|
+
...options,
|
|
2336
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2337
|
+
token: options.token || config.auth.token
|
|
2338
|
+
};
|
|
2339
|
+
if (!effectiveOptions.token) {
|
|
2340
|
+
throw new Error(
|
|
2341
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
const headers = {
|
|
2345
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
2346
|
+
"Content-Type": "application/json"
|
|
2347
|
+
};
|
|
2348
|
+
const response = await fetch(`${effectiveOptions.endpoint}/resources/read`, {
|
|
2349
|
+
method: "POST",
|
|
2350
|
+
headers,
|
|
2351
|
+
body: JSON.stringify({
|
|
2352
|
+
jsonrpc: "2.0",
|
|
2353
|
+
id: Date.now(),
|
|
2354
|
+
method: "resources/read",
|
|
2355
|
+
params: {
|
|
2356
|
+
uri: resourceUri
|
|
2357
|
+
}
|
|
2358
|
+
})
|
|
2359
|
+
});
|
|
2360
|
+
if (!response.ok) {
|
|
2361
|
+
const errorText = await response.text();
|
|
2362
|
+
throw new Error(
|
|
2363
|
+
`Failed to read resource: ${response.status} ${response.statusText}
|
|
2364
|
+
${errorText}`
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
const result = await response.json();
|
|
2368
|
+
if (outputJson) {
|
|
2369
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2370
|
+
} else {
|
|
2371
|
+
if (result.error) {
|
|
2372
|
+
log.error("\u274C Resource read failed:", result.error.message);
|
|
2373
|
+
if (result.error.data) {
|
|
2374
|
+
log.error(" Details:", JSON.stringify(result.error.data, null, 2));
|
|
2375
|
+
}
|
|
2376
|
+
} else if (result.result && result.result.contents) {
|
|
2377
|
+
log.info("\u2705 Resource read successful!");
|
|
2378
|
+
for (const content of result.result.contents) {
|
|
2379
|
+
if (content.type === "text") {
|
|
2380
|
+
log.info("\u{1F4C4} Content:", content.text);
|
|
2381
|
+
} else {
|
|
2382
|
+
log.info(`\u{1F4C4} Content (${content.type}):`, JSON.stringify(content, null, 2));
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
} catch (error) {
|
|
2388
|
+
log.error(
|
|
2389
|
+
"\u274C Failed to read resource:",
|
|
2390
|
+
error instanceof Error ? error.message : String(error)
|
|
2391
|
+
);
|
|
2392
|
+
process.exit(1);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
async function listResourceTemplatesCommand(options) {
|
|
2396
|
+
const outputJson = isJsonMode(options);
|
|
2397
|
+
const log = createLogger(options);
|
|
2398
|
+
try {
|
|
2399
|
+
log.info("\u{1F4CB} Fetching resource templates...");
|
|
2400
|
+
const config = configManager.getConfig();
|
|
2401
|
+
const effectiveOptions = {
|
|
2402
|
+
...options,
|
|
2403
|
+
endpoint: options.endpoint || config.endpoints.core,
|
|
2404
|
+
token: options.token || config.auth.token
|
|
2405
|
+
};
|
|
2406
|
+
if (!effectiveOptions.token) {
|
|
2407
|
+
throw new Error(
|
|
2408
|
+
'Authentication required. Please login first with "apexmcp login" or provide --token'
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
const headers = {
|
|
2412
|
+
"Authorization": `Bearer ${effectiveOptions.token}`,
|
|
2413
|
+
"Content-Type": "application/json"
|
|
2414
|
+
};
|
|
2415
|
+
const response = await fetch(`${effectiveOptions.endpoint}/resources/templates/list`, {
|
|
2416
|
+
method: "POST",
|
|
2417
|
+
headers,
|
|
2418
|
+
body: JSON.stringify({
|
|
2419
|
+
jsonrpc: "2.0",
|
|
2420
|
+
id: Date.now(),
|
|
2421
|
+
method: "resources/templates/list",
|
|
2422
|
+
params: {}
|
|
2423
|
+
})
|
|
2424
|
+
});
|
|
2425
|
+
if (!response.ok) {
|
|
2426
|
+
const errorText = await response.text();
|
|
2427
|
+
throw new Error(
|
|
2428
|
+
`Failed to list resource templates: ${response.status} ${response.statusText}
|
|
2429
|
+
${errorText}`
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
const result = await response.json();
|
|
2433
|
+
if (outputJson) {
|
|
2434
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2435
|
+
} else {
|
|
2436
|
+
if (!result.result || !result.result.resourceTemplates || result.result.resourceTemplates.length === 0) {
|
|
2437
|
+
log.info("\u{1F4CB} No resource templates available.");
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const templates = result.result.resourceTemplates;
|
|
2441
|
+
log.info(`\u{1F4CB} Found ${templates.length} resource template(s):
|
|
2442
|
+
`);
|
|
2443
|
+
log.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
2444
|
+
log.info("\u2502 Template URI \u2502 Name \u2502 MIME Type \u2502");
|
|
2445
|
+
log.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
2446
|
+
for (const template of templates) {
|
|
2447
|
+
const uri = template.uriTemplate.substring(0, 36).padEnd(36);
|
|
2448
|
+
const name = (template.name || "Unnamed").substring(0, 36).padEnd(36);
|
|
2449
|
+
const mimeType = (template.mimeType || "unknown").substring(0, 10).padEnd(10);
|
|
2450
|
+
log.info(`\u2502 ${uri} \u2502 ${name} \u2502 ${mimeType} \u2502`);
|
|
2451
|
+
}
|
|
2452
|
+
log.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
|
|
2453
|
+
for (const template of templates) {
|
|
2454
|
+
log.info(`\u{1F4CB} ${template.name || "Unnamed"}:`);
|
|
2455
|
+
log.info(` \u{1F310} URI Template: ${template.uriTemplate}`);
|
|
2456
|
+
log.info(` \u{1F4DD} Description: ${template.description || "No description"}`);
|
|
2457
|
+
log.info(` \u{1F4CB} MIME Type: ${template.mimeType || "unknown"}`);
|
|
2458
|
+
log.info("");
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
log.error(
|
|
2463
|
+
"\u274C Failed to list resource templates:",
|
|
2464
|
+
error instanceof Error ? error.message : String(error)
|
|
2465
|
+
);
|
|
2466
|
+
process.exit(1);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
// src/cli.ts
|
|
2471
|
+
import { createRequire } from "module";
|
|
2472
|
+
var program = new Command();
|
|
2473
|
+
var require2 = createRequire(import.meta.url);
|
|
2474
|
+
var { version } = require2("../package.json");
|
|
2475
|
+
program.name("apexmcp").description("CLI tool for deploying MCP servers to ApexMCP").version(version);
|
|
2476
|
+
program.command("deploy").description("Deploy an MCP server").argument("<project-dir>", "Path to the MCP server project directory").option("--env-file <file>", "Environment variables file").option("--domain <domain>", "Custom domain for deployment").option(
|
|
2477
|
+
"--url-strategy <strategy>",
|
|
2478
|
+
"URL generation strategy (readable, obscure, timestamp-only, random)",
|
|
2479
|
+
"readable"
|
|
2480
|
+
).option(
|
|
2481
|
+
"--project-path <path>",
|
|
2482
|
+
"Project identifier path (stable slug used by the deploy service)"
|
|
2483
|
+
).option("--project-name <name>", "Project name for deployment").option("--entry-point <file>", "Explicit entry point file (overrides auto-detection)").option(
|
|
2484
|
+
"--endpoint <url>",
|
|
2485
|
+
"Deploy service endpoint (default: configured endpoint)"
|
|
2486
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(deployCommand);
|
|
2487
|
+
program.command("list").description("List all deployments").option(
|
|
2488
|
+
"--endpoint <url>",
|
|
2489
|
+
"Deploy service endpoint (default: configured endpoint)"
|
|
2490
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listDeploymentsCommand);
|
|
2491
|
+
program.command("status").description("Get deployment status").argument("<deployment-name>", "Deployment name").option(
|
|
2492
|
+
"--endpoint <url>",
|
|
2493
|
+
"Deploy service endpoint (default: configured endpoint)"
|
|
2494
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(getDeploymentCommand);
|
|
2495
|
+
program.command("update").description("Update deployment properties").argument("<deployment-name>", "Deployment name").option("--name <name>", "Update deployment name").option("--domain <domain>", "Update deployment domain").option("--env-file <file>", "Update environment variables from file").option(
|
|
2496
|
+
"--endpoint <url>",
|
|
2497
|
+
"Deploy service endpoint (default: configured endpoint)"
|
|
2498
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(updateDeploymentCommand);
|
|
2499
|
+
program.command("delete").description("Delete a deployment").argument("<deployment-name>", "Deployment name").option("--force", "Skip confirmation prompt").option(
|
|
2500
|
+
"--endpoint <url>",
|
|
2501
|
+
"Deploy service endpoint (default: configured endpoint)"
|
|
2502
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(deleteDeploymentCommand);
|
|
2503
|
+
program.command("validate").description("Validate an MCP project").argument("<project-path>", "Path to the MCP server project").option(
|
|
2504
|
+
"--endpoint <url>",
|
|
2505
|
+
"Deploy service endpoint for API validation (default: configured endpoint)"
|
|
2506
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(validateCommand);
|
|
2507
|
+
program.command("login").description("Authenticate with ApexMCP").option("--token <token>", "Direct JWT token authentication").option("--email <email>", "Email for authentication").option("--password <password>", "Password for authentication").option(
|
|
2508
|
+
"--endpoint <url>",
|
|
2509
|
+
"Core service endpoint",
|
|
2510
|
+
"https://core.apexmcp.dev"
|
|
2511
|
+
).option("--json", "Output JSON for CI/CD consumption").action(loginCommand);
|
|
2512
|
+
program.command("logout").description("Clear authentication credentials").option("--json", "Output JSON for CI/CD consumption").action(logoutCommand);
|
|
2513
|
+
program.command("whoami").description("Show current authentication status and configuration").option(
|
|
2514
|
+
"--endpoint <url>",
|
|
2515
|
+
"Core service endpoint",
|
|
2516
|
+
"https://core.apexmcp.dev"
|
|
2517
|
+
).option("--json", "Output JSON for CI/CD consumption").action(whoamiCommand);
|
|
2518
|
+
program.command("servers").description("List available MCP servers").option(
|
|
2519
|
+
"--endpoint <url>",
|
|
2520
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2521
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listAvailableMcpServersCommand);
|
|
2522
|
+
program.command("servers:enabled").description("List enabled MCP servers for your organization").option(
|
|
2523
|
+
"--endpoint <url>",
|
|
2524
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2525
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listEnabledMcpServersCommand);
|
|
2526
|
+
program.command("servers:enable").description("Enable an MCP server for your organization").argument("<catalog-id>", "MCP server catalog ID").option(
|
|
2527
|
+
"--endpoint <url>",
|
|
2528
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2529
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(enableMcpServerCommand);
|
|
2530
|
+
program.command("servers:disable").description("Disable an MCP server for your organization").argument("<catalog-id>", "MCP server catalog ID").option(
|
|
2531
|
+
"--endpoint <url>",
|
|
2532
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2533
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(disableMcpServerCommand);
|
|
2534
|
+
program.command("servers:info").description("Get detailed information about an MCP server").argument("<catalog-id>", "MCP server catalog ID").option(
|
|
2535
|
+
"--endpoint <url>",
|
|
2536
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2537
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(getMcpServerCommand);
|
|
2538
|
+
program.command("secrets").description("List secrets for an MCP server").argument("<catalog-id>", "MCP server catalog ID").option(
|
|
2539
|
+
"--endpoint <url>",
|
|
2540
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2541
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listPluginSecretsCommand);
|
|
2542
|
+
program.command("secrets:set").description("Set a secret for an MCP server").argument("<catalog-id>", "MCP server catalog ID").argument("<secret-key>", "Secret key").argument("<secret-value>", "Secret value").option(
|
|
2543
|
+
"--endpoint <url>",
|
|
2544
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2545
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(setPluginSecretCommand);
|
|
2546
|
+
program.command("secrets:delete").description("Delete a secret for an MCP server").argument("<catalog-id>", "MCP server catalog ID").argument("<secret-key>", "Secret key").option(
|
|
2547
|
+
"--endpoint <url>",
|
|
2548
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2549
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(deletePluginSecretCommand);
|
|
2550
|
+
program.command("plugins:delete").description("Delete an entire plugin configuration").argument("<catalog-id>", "MCP server catalog ID").option(
|
|
2551
|
+
"--endpoint <url>",
|
|
2552
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2553
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(deletePluginCommand);
|
|
2554
|
+
program.command("tools").description("List available tools from enabled MCP servers").option(
|
|
2555
|
+
"--endpoint <url>",
|
|
2556
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2557
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listToolsCommand);
|
|
2558
|
+
program.command("tools:call").description("Call a tool with specified parameters").argument("<tool-name>", "Name of the tool to call").argument("<params-json>", "Tool parameters as JSON string").option(
|
|
2559
|
+
"--endpoint <url>",
|
|
2560
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2561
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(callToolCommand);
|
|
2562
|
+
program.command("resources").description("List available resources from enabled MCP servers").option(
|
|
2563
|
+
"--endpoint <url>",
|
|
2564
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2565
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listResourcesCommand);
|
|
2566
|
+
program.command("resources:read").description("Read a specific resource").argument("<resource-uri>", "URI of the resource to read").option(
|
|
2567
|
+
"--endpoint <url>",
|
|
2568
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2569
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(readResourceCommand);
|
|
2570
|
+
program.command("resources:templates").description("List available resource templates").option(
|
|
2571
|
+
"--endpoint <url>",
|
|
2572
|
+
"Core service endpoint (default: configured endpoint)"
|
|
2573
|
+
).option("--token <token>", "Authentication token (default: configured token)").option("--json", "Output JSON for CI/CD consumption").action(listResourceTemplatesCommand);
|
|
2574
|
+
program.command("config").description("Manage CLI configuration").argument("<action>", "Action: get, set <key> <value>, reset").argument("[key]", "Configuration key (for set action)").argument("[value]", "Configuration value (for set action)").option("--json", "Output JSON for CI/CD consumption").action((action, key, value, options) => {
|
|
2575
|
+
configCommand(action, key, value, options);
|
|
2576
|
+
});
|
|
2577
|
+
function setupProgram() {
|
|
2578
|
+
if (!process.env.NODE_ENV?.includes("test")) {
|
|
2579
|
+
program.exitOverride();
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
function runMain() {
|
|
2583
|
+
try {
|
|
2584
|
+
program.parse();
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
logger.error(
|
|
2587
|
+
"CLI Error:",
|
|
2588
|
+
error instanceof Error ? error.message : String(error)
|
|
2589
|
+
);
|
|
2590
|
+
process.exit(1);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
setupProgram();
|
|
2594
|
+
runMain();
|
|
2595
|
+
export {
|
|
2596
|
+
program,
|
|
2597
|
+
runMain,
|
|
2598
|
+
setupProgram
|
|
2599
|
+
};
|
|
2600
|
+
//# sourceMappingURL=cli.js.map
|