ccjk 2.3.1 → 2.4.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/README.md +270 -444
- package/README.zh-CN.md +273 -447
- package/dist/chunks/auto-bootstrap.mjs +358 -0
- package/dist/chunks/ccr.mjs +5 -2
- package/dist/chunks/claude-wrapper.mjs +442 -0
- package/dist/chunks/cloud-sync.mjs +29 -0
- package/dist/chunks/constants.mjs +1 -1
- package/dist/chunks/context-manager.mjs +641 -0
- package/dist/chunks/context.mjs +248 -0
- package/dist/chunks/index2.mjs +2 -0
- package/dist/chunks/index3.mjs +19 -19
- package/dist/chunks/init.mjs +18 -8
- package/dist/chunks/marketplace.mjs +6 -2
- package/dist/chunks/mcp.mjs +1 -1
- package/dist/chunks/menu.mjs +3 -3
- package/dist/chunks/notification.mjs +27 -27
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/platform.mjs +70 -21
- package/dist/chunks/silent-updater.mjs +396 -0
- package/dist/chunks/skills-sync.mjs +1 -1
- package/dist/chunks/version-checker.mjs +31 -31
- package/dist/cli.mjs +67 -5
- package/dist/i18n/locales/en/context.json +32 -0
- package/dist/i18n/locales/en/marketplace.json +1 -0
- package/dist/i18n/locales/en/mcp.json +12 -1
- package/dist/i18n/locales/en/superpowers.json +46 -0
- package/dist/i18n/locales/zh-CN/context.json +32 -0
- package/dist/i18n/locales/zh-CN/marketplace.json +1 -0
- package/dist/i18n/locales/zh-CN/mcp.json +12 -1
- package/dist/i18n/locales/zh-CN/superpowers.json +46 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/shared/ccjk.QbS8EAOd.mjs +1019 -0
- package/dist/shared/ccjk.RR9TS76h.mjs +698 -0
- package/package.json +4 -1
- package/dist/shared/ccjk.Bi-m3LKY.mjs +0 -357
- package/dist/shared/ccjk.D-RZS4E2.mjs +0 -416
|
@@ -0,0 +1,1019 @@
|
|
|
1
|
+
import ansis from 'ansis';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { af as getMcpService, r as readCodexConfig, Z as applyCodexPlatformCommand, w as writeCodexConfig, e as readMcpConfig, j as buildMcpServerConfig, f as writeMcpConfig, ab as MCP_SERVICE_CONFIGS } from '../chunks/codex.mjs';
|
|
4
|
+
import { ensureI18nInitialized, i18n } from '../chunks/index2.mjs';
|
|
5
|
+
import { ClAUDE_CONFIG_FILE, CODEX_CONFIG_FILE } from '../chunks/constants.mjs';
|
|
6
|
+
import { writeFileAtomic, exists } from '../chunks/fs-operations.mjs';
|
|
7
|
+
import { isWindows, getSystemRoot } from '../chunks/platform.mjs';
|
|
8
|
+
import { existsSync, unlinkSync, statSync, mkdirSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { join } from 'pathe';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_API_URL = "https://api.api.claudehome.cn/v1/mcp-marketplace";
|
|
13
|
+
const REQUEST_TIMEOUT = 3e4;
|
|
14
|
+
const MAX_RETRY_ATTEMPTS = 3;
|
|
15
|
+
const RETRY_DELAY = 1e3;
|
|
16
|
+
const DEFAULT_CACHE_TTL = 36e5;
|
|
17
|
+
const DEFAULT_THROTTLE_INTERVAL = 100;
|
|
18
|
+
const CACHE_VERSION = "1.0.0";
|
|
19
|
+
const CACHE_BASE_DIR = join(homedir(), ".ccjk", "mcp-marketplace", "cache");
|
|
20
|
+
class MarketplaceClient {
|
|
21
|
+
baseUrl;
|
|
22
|
+
apiKey;
|
|
23
|
+
timeout;
|
|
24
|
+
offlineMode;
|
|
25
|
+
enableLogging;
|
|
26
|
+
maxRetries;
|
|
27
|
+
retryDelay;
|
|
28
|
+
cacheTTL;
|
|
29
|
+
enableDeduplication;
|
|
30
|
+
throttleInterval;
|
|
31
|
+
// In-memory cache
|
|
32
|
+
memoryCache;
|
|
33
|
+
// Pending requests for deduplication
|
|
34
|
+
pendingRequests;
|
|
35
|
+
// Last request timestamp for throttling
|
|
36
|
+
lastRequestTime;
|
|
37
|
+
// File-based cache
|
|
38
|
+
cacheDir;
|
|
39
|
+
cacheFile;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.baseUrl = options.baseUrl || DEFAULT_API_URL;
|
|
42
|
+
this.apiKey = options.apiKey;
|
|
43
|
+
this.timeout = options.timeout || REQUEST_TIMEOUT;
|
|
44
|
+
this.offlineMode = options.offlineMode || false;
|
|
45
|
+
this.enableLogging = options.enableLogging || false;
|
|
46
|
+
this.maxRetries = options.maxRetries || MAX_RETRY_ATTEMPTS;
|
|
47
|
+
this.retryDelay = options.retryDelay || RETRY_DELAY;
|
|
48
|
+
this.cacheTTL = options.cacheTTL || DEFAULT_CACHE_TTL;
|
|
49
|
+
this.enableDeduplication = options.enableDeduplication !== false;
|
|
50
|
+
this.throttleInterval = options.throttleInterval || DEFAULT_THROTTLE_INTERVAL;
|
|
51
|
+
this.memoryCache = /* @__PURE__ */ new Map();
|
|
52
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
53
|
+
this.lastRequestTime = 0;
|
|
54
|
+
this.cacheDir = CACHE_BASE_DIR;
|
|
55
|
+
this.cacheFile = join(this.cacheDir, "marketplace.json");
|
|
56
|
+
}
|
|
57
|
+
// ==========================================================================
|
|
58
|
+
// Public API Methods
|
|
59
|
+
// ==========================================================================
|
|
60
|
+
/**
|
|
61
|
+
* Search packages with filters and sorting
|
|
62
|
+
*/
|
|
63
|
+
async search(options = {}) {
|
|
64
|
+
this.log("Searching packages with options:", options);
|
|
65
|
+
const params = this.buildSearchParams(options);
|
|
66
|
+
const response = await this.request("/search", {
|
|
67
|
+
method: "GET",
|
|
68
|
+
params
|
|
69
|
+
});
|
|
70
|
+
if (response.success && response.data) {
|
|
71
|
+
return response.data;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
packages: [],
|
|
75
|
+
total: 0,
|
|
76
|
+
page: options.page || 1,
|
|
77
|
+
limit: options.limit || 20,
|
|
78
|
+
totalPages: 0,
|
|
79
|
+
hasMore: false
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get detailed information about a specific package
|
|
84
|
+
*/
|
|
85
|
+
async getPackage(id) {
|
|
86
|
+
this.log("Getting package:", id);
|
|
87
|
+
const encodedId = encodeURIComponent(id);
|
|
88
|
+
const response = await this.request(`/packages/${encodedId}`, {
|
|
89
|
+
method: "GET"
|
|
90
|
+
});
|
|
91
|
+
return response.success && response.data ? response.data : null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get version history for a package
|
|
95
|
+
*/
|
|
96
|
+
async getVersions(id) {
|
|
97
|
+
this.log("Getting versions for package:", id);
|
|
98
|
+
const encodedId = encodeURIComponent(id);
|
|
99
|
+
const response = await this.request(`/packages/${encodedId}/versions`, {
|
|
100
|
+
method: "GET"
|
|
101
|
+
});
|
|
102
|
+
return response.success && response.data ? response.data : [];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get trending/popular packages
|
|
106
|
+
*/
|
|
107
|
+
async getTrending(limit = 10) {
|
|
108
|
+
this.log("Getting trending packages, limit:", limit);
|
|
109
|
+
const response = await this.request("/trending", {
|
|
110
|
+
method: "GET",
|
|
111
|
+
params: { limit }
|
|
112
|
+
});
|
|
113
|
+
return response.success && response.data ? response.data : [];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get personalized recommendations based on installed packages
|
|
117
|
+
*/
|
|
118
|
+
async getRecommendations(installed) {
|
|
119
|
+
this.log("Getting recommendations for installed packages:", installed);
|
|
120
|
+
const response = await this.request("/recommendations", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
body: JSON.stringify({ installed })
|
|
123
|
+
});
|
|
124
|
+
return response.success && response.data ? response.data : [];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all available categories
|
|
128
|
+
*/
|
|
129
|
+
async getCategories() {
|
|
130
|
+
this.log("Getting categories");
|
|
131
|
+
const response = await this.request("/categories", {
|
|
132
|
+
method: "GET"
|
|
133
|
+
});
|
|
134
|
+
return response.success && response.data ? response.data : [];
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check for updates for installed packages
|
|
138
|
+
*/
|
|
139
|
+
async checkUpdates(installed) {
|
|
140
|
+
this.log("Checking updates for", installed.length, "packages");
|
|
141
|
+
const response = await this.request("/updates/check", {
|
|
142
|
+
method: "POST",
|
|
143
|
+
body: JSON.stringify({ packages: installed })
|
|
144
|
+
});
|
|
145
|
+
return response.success && response.data ? response.data : [];
|
|
146
|
+
}
|
|
147
|
+
// ==========================================================================
|
|
148
|
+
// Cache Management
|
|
149
|
+
// ==========================================================================
|
|
150
|
+
/**
|
|
151
|
+
* Clear all cached data (memory and file)
|
|
152
|
+
*/
|
|
153
|
+
clearCache() {
|
|
154
|
+
this.memoryCache.clear();
|
|
155
|
+
this.pendingRequests.clear();
|
|
156
|
+
try {
|
|
157
|
+
if (existsSync(this.cacheFile)) {
|
|
158
|
+
unlinkSync(this.cacheFile);
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.log("Failed to clear file cache:", error);
|
|
162
|
+
}
|
|
163
|
+
this.log("Cache cleared");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Clear expired cache entries
|
|
167
|
+
*/
|
|
168
|
+
clearExpiredCache() {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
const keysToDelete = [];
|
|
171
|
+
this.memoryCache.forEach((entry, key) => {
|
|
172
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
173
|
+
keysToDelete.push(key);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
keysToDelete.forEach((key) => this.memoryCache.delete(key));
|
|
177
|
+
this.log("Cleared", keysToDelete.length, "expired cache entries");
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get cache statistics
|
|
181
|
+
*/
|
|
182
|
+
getCacheStats() {
|
|
183
|
+
const fileCache = this.loadFileCache();
|
|
184
|
+
let cacheSize = 0;
|
|
185
|
+
try {
|
|
186
|
+
if (existsSync(this.cacheFile)) {
|
|
187
|
+
cacheSize = statSync(this.cacheFile).size;
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
totalPackages: fileCache?.packages.length || 0,
|
|
193
|
+
cacheSize,
|
|
194
|
+
lastUpdated: fileCache?.lastUpdated || null,
|
|
195
|
+
expiresAt: fileCache?.expiresAt || null,
|
|
196
|
+
isExpired: this.isFileCacheExpired(),
|
|
197
|
+
cachedCategories: fileCache?.categories.length || 0
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Set offline mode
|
|
202
|
+
*/
|
|
203
|
+
setOfflineMode(enabled) {
|
|
204
|
+
this.offlineMode = enabled;
|
|
205
|
+
this.log("Offline mode:", enabled ? "enabled" : "disabled");
|
|
206
|
+
}
|
|
207
|
+
// ==========================================================================
|
|
208
|
+
// Private Request Methods
|
|
209
|
+
// ==========================================================================
|
|
210
|
+
/**
|
|
211
|
+
* Make an HTTP request with caching, deduplication, and throttling
|
|
212
|
+
*/
|
|
213
|
+
async request(endpoint, options = {}) {
|
|
214
|
+
const url = this.buildUrl(endpoint, options.params);
|
|
215
|
+
const cacheKey = `${options.method || "GET"}:${url}`;
|
|
216
|
+
if (!options.skipCache && (options.method === "GET" || !options.method)) {
|
|
217
|
+
const cached = this.getFromMemoryCache(cacheKey);
|
|
218
|
+
if (cached) {
|
|
219
|
+
this.log("Memory cache hit:", cacheKey);
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
data: cached,
|
|
223
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (this.enableDeduplication && (options.method === "GET" || !options.method)) {
|
|
228
|
+
const pending = this.pendingRequests.get(cacheKey);
|
|
229
|
+
if (pending) {
|
|
230
|
+
this.log("Deduplicating request:", cacheKey);
|
|
231
|
+
return pending.promise;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (this.offlineMode) {
|
|
235
|
+
const cached = this.getFromMemoryCache(cacheKey);
|
|
236
|
+
if (cached) {
|
|
237
|
+
this.log("Offline mode: returning cached data");
|
|
238
|
+
return {
|
|
239
|
+
success: true,
|
|
240
|
+
data: cached,
|
|
241
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
error: {
|
|
247
|
+
code: "OFFLINE_MODE",
|
|
248
|
+
message: "Offline mode enabled and no cached data available"
|
|
249
|
+
},
|
|
250
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
await this.throttle();
|
|
254
|
+
const requestPromise = this.executeRequest(url, options, cacheKey);
|
|
255
|
+
if (this.enableDeduplication && (options.method === "GET" || !options.method)) {
|
|
256
|
+
this.pendingRequests.set(cacheKey, {
|
|
257
|
+
promise: requestPromise,
|
|
258
|
+
timestamp: Date.now()
|
|
259
|
+
});
|
|
260
|
+
requestPromise.finally(() => {
|
|
261
|
+
this.pendingRequests.delete(cacheKey);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return requestPromise;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Execute HTTP request with retry logic
|
|
268
|
+
*/
|
|
269
|
+
async executeRequest(url, options, cacheKey) {
|
|
270
|
+
let lastError = null;
|
|
271
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
272
|
+
try {
|
|
273
|
+
this.log(`Request attempt ${attempt}/${this.maxRetries}:`, url);
|
|
274
|
+
const response = await this.makeRequest(url, options);
|
|
275
|
+
if (response.success && response.data && (options.method === "GET" || !options.method)) {
|
|
276
|
+
this.setMemoryCache(cacheKey, response.data, this.cacheTTL);
|
|
277
|
+
}
|
|
278
|
+
return response;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
281
|
+
this.log(`Request failed (attempt ${attempt}):`, lastError.message);
|
|
282
|
+
if (lastError.name === "AbortError") {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
if (attempt < this.maxRetries) {
|
|
286
|
+
await this.sleep(this.retryDelay * attempt);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
success: false,
|
|
292
|
+
error: {
|
|
293
|
+
code: "REQUEST_FAILED",
|
|
294
|
+
message: lastError?.message || "Request failed after all retries"
|
|
295
|
+
},
|
|
296
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Make a single HTTP request
|
|
301
|
+
*/
|
|
302
|
+
async makeRequest(url, options) {
|
|
303
|
+
const controller = new AbortController();
|
|
304
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
305
|
+
try {
|
|
306
|
+
const response = await fetch(url, {
|
|
307
|
+
method: options.method || "GET",
|
|
308
|
+
headers: this.getHeaders(),
|
|
309
|
+
body: options.body,
|
|
310
|
+
signal: controller.signal
|
|
311
|
+
});
|
|
312
|
+
clearTimeout(timeoutId);
|
|
313
|
+
this.lastRequestTime = Date.now();
|
|
314
|
+
const data = await response.json();
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: data.error || {
|
|
319
|
+
code: `HTTP_${response.status}`,
|
|
320
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
321
|
+
},
|
|
322
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
...data,
|
|
327
|
+
success: true,
|
|
328
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
329
|
+
};
|
|
330
|
+
} catch (error) {
|
|
331
|
+
clearTimeout(timeoutId);
|
|
332
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
error: {
|
|
338
|
+
code: "NETWORK_ERROR",
|
|
339
|
+
message: error instanceof Error ? error.message : String(error)
|
|
340
|
+
},
|
|
341
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// ==========================================================================
|
|
346
|
+
// Helper Methods
|
|
347
|
+
// ==========================================================================
|
|
348
|
+
/**
|
|
349
|
+
* Build full URL with query parameters
|
|
350
|
+
*/
|
|
351
|
+
buildUrl(endpoint, params) {
|
|
352
|
+
const url = new URL(endpoint, this.baseUrl);
|
|
353
|
+
if (params) {
|
|
354
|
+
for (const [key, value] of Object.entries(params)) {
|
|
355
|
+
if (value !== void 0 && value !== null) {
|
|
356
|
+
if (Array.isArray(value)) {
|
|
357
|
+
value.forEach((v) => url.searchParams.append(key, String(v)));
|
|
358
|
+
} else {
|
|
359
|
+
url.searchParams.append(key, String(value));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return url.toString();
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Build search parameters from options
|
|
368
|
+
*/
|
|
369
|
+
buildSearchParams(options) {
|
|
370
|
+
return {
|
|
371
|
+
q: options.query,
|
|
372
|
+
category: options.category,
|
|
373
|
+
tags: options.tags,
|
|
374
|
+
sortBy: options.sortBy,
|
|
375
|
+
sortOrder: options.sortOrder,
|
|
376
|
+
verified: options.verified,
|
|
377
|
+
verificationStatus: options.verificationStatus,
|
|
378
|
+
author: options.author,
|
|
379
|
+
platform: options.platform,
|
|
380
|
+
codeTool: options.codeTool,
|
|
381
|
+
minRating: options.minRating,
|
|
382
|
+
page: options.page || 1,
|
|
383
|
+
limit: options.limit || 20
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get request headers
|
|
388
|
+
*/
|
|
389
|
+
getHeaders() {
|
|
390
|
+
const headers = {
|
|
391
|
+
"Content-Type": "application/json",
|
|
392
|
+
"User-Agent": "CCJK-MCP-Marketplace-Client/1.0"
|
|
393
|
+
};
|
|
394
|
+
if (this.apiKey) {
|
|
395
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
396
|
+
}
|
|
397
|
+
return headers;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Throttle requests
|
|
401
|
+
*/
|
|
402
|
+
async throttle() {
|
|
403
|
+
const now = Date.now();
|
|
404
|
+
const elapsed = now - this.lastRequestTime;
|
|
405
|
+
const remaining = this.throttleInterval - elapsed;
|
|
406
|
+
if (remaining > 0) {
|
|
407
|
+
await this.sleep(remaining);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Sleep for specified milliseconds
|
|
412
|
+
*/
|
|
413
|
+
sleep(ms) {
|
|
414
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Log message (if logging is enabled)
|
|
418
|
+
*/
|
|
419
|
+
log(...args) {
|
|
420
|
+
if (this.enableLogging) {
|
|
421
|
+
console.log("[MarketplaceClient]", ...args);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// ==========================================================================
|
|
425
|
+
// Memory Cache Methods
|
|
426
|
+
// ==========================================================================
|
|
427
|
+
/**
|
|
428
|
+
* Get data from memory cache
|
|
429
|
+
*/
|
|
430
|
+
getFromMemoryCache(key) {
|
|
431
|
+
const entry = this.memoryCache.get(key);
|
|
432
|
+
if (!entry) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
const now = Date.now();
|
|
436
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
437
|
+
this.memoryCache.delete(key);
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
return entry.data;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Set data in memory cache
|
|
444
|
+
*/
|
|
445
|
+
setMemoryCache(key, data, ttl) {
|
|
446
|
+
this.memoryCache.set(key, {
|
|
447
|
+
data,
|
|
448
|
+
timestamp: Date.now(),
|
|
449
|
+
ttl
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
// ==========================================================================
|
|
453
|
+
// File Cache Methods
|
|
454
|
+
// ==========================================================================
|
|
455
|
+
/**
|
|
456
|
+
* Ensure cache directory exists
|
|
457
|
+
*/
|
|
458
|
+
ensureCacheDir() {
|
|
459
|
+
if (!existsSync(this.cacheDir)) {
|
|
460
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Load cache from file
|
|
465
|
+
*/
|
|
466
|
+
loadFileCache() {
|
|
467
|
+
try {
|
|
468
|
+
if (!existsSync(this.cacheFile)) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
const content = readFileSync(this.cacheFile, "utf-8");
|
|
472
|
+
const cache = JSON.parse(content);
|
|
473
|
+
if (cache.version !== CACHE_VERSION) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
return cache;
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Save cache to file
|
|
483
|
+
*/
|
|
484
|
+
saveFileCache(packages, categories) {
|
|
485
|
+
try {
|
|
486
|
+
this.ensureCacheDir();
|
|
487
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
488
|
+
const expiresAt = new Date(Date.now() + this.cacheTTL).toISOString();
|
|
489
|
+
const cache = {
|
|
490
|
+
version: CACHE_VERSION,
|
|
491
|
+
packages,
|
|
492
|
+
categories,
|
|
493
|
+
createdAt: now,
|
|
494
|
+
expiresAt,
|
|
495
|
+
lastUpdated: now
|
|
496
|
+
};
|
|
497
|
+
writeFileAtomic(this.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
498
|
+
} catch (error) {
|
|
499
|
+
this.log("Failed to save file cache:", error);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Check if file cache is expired
|
|
504
|
+
*/
|
|
505
|
+
isFileCacheExpired() {
|
|
506
|
+
const cache = this.loadFileCache();
|
|
507
|
+
if (!cache) {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
const expiresAt = new Date(cache.expiresAt).getTime();
|
|
511
|
+
return Date.now() >= expiresAt;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
let defaultClientInstance = null;
|
|
515
|
+
function getDefaultMarketplaceClient() {
|
|
516
|
+
if (!defaultClientInstance) {
|
|
517
|
+
defaultClientInstance = new MarketplaceClient();
|
|
518
|
+
}
|
|
519
|
+
return defaultClientInstance;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function detectActiveTool() {
|
|
523
|
+
const hasClaudeConfig = exists(ClAUDE_CONFIG_FILE);
|
|
524
|
+
const hasCodexConfig = exists(CODEX_CONFIG_FILE);
|
|
525
|
+
if (hasClaudeConfig) {
|
|
526
|
+
return "claude-code";
|
|
527
|
+
}
|
|
528
|
+
if (hasCodexConfig) {
|
|
529
|
+
return "codex";
|
|
530
|
+
}
|
|
531
|
+
return "claude-code";
|
|
532
|
+
}
|
|
533
|
+
async function installMcpService(serviceId, tool, apiKey) {
|
|
534
|
+
ensureI18nInitialized();
|
|
535
|
+
const service = await getMcpService(serviceId);
|
|
536
|
+
if (!service) {
|
|
537
|
+
return {
|
|
538
|
+
success: false,
|
|
539
|
+
serviceId,
|
|
540
|
+
serviceName: serviceId,
|
|
541
|
+
error: i18n.t("mcp:installer.serviceNotFound", { id: serviceId })
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
if (service.requiresApiKey && !apiKey) {
|
|
545
|
+
const promptMessage = service.apiKeyPrompt || i18n.t("mcp:apiKeyPrompt");
|
|
546
|
+
const { inputApiKey } = await inquirer.prompt([{
|
|
547
|
+
type: "input",
|
|
548
|
+
name: "inputApiKey",
|
|
549
|
+
message: promptMessage,
|
|
550
|
+
validate: (input) => !!input || i18n.t("api:keyRequired")
|
|
551
|
+
}]);
|
|
552
|
+
if (!inputApiKey) {
|
|
553
|
+
return {
|
|
554
|
+
success: false,
|
|
555
|
+
serviceId,
|
|
556
|
+
serviceName: service.name,
|
|
557
|
+
error: i18n.t("mcp:installer.apiKeyRequired")
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
apiKey = inputApiKey;
|
|
561
|
+
}
|
|
562
|
+
const targetTool = tool || detectActiveTool();
|
|
563
|
+
try {
|
|
564
|
+
if (targetTool === "codex") {
|
|
565
|
+
await installMcpServiceForCodex(serviceId, service.config, apiKey, service.apiKeyEnvVar);
|
|
566
|
+
} else {
|
|
567
|
+
await installMcpServiceForClaudeCode(serviceId, service.config, apiKey, service.apiKeyEnvVar);
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
success: true,
|
|
571
|
+
serviceId,
|
|
572
|
+
serviceName: service.name
|
|
573
|
+
};
|
|
574
|
+
} catch (error) {
|
|
575
|
+
return {
|
|
576
|
+
success: false,
|
|
577
|
+
serviceId,
|
|
578
|
+
serviceName: service.name,
|
|
579
|
+
error: error instanceof Error ? error.message : String(error)
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function installMcpServiceForClaudeCode(serviceId, baseConfig, apiKey, apiKeyEnvVar) {
|
|
584
|
+
let config = readMcpConfig();
|
|
585
|
+
if (!config) {
|
|
586
|
+
config = { mcpServers: {} };
|
|
587
|
+
}
|
|
588
|
+
const serverConfig = buildMcpServerConfig(
|
|
589
|
+
baseConfig,
|
|
590
|
+
apiKey,
|
|
591
|
+
apiKeyEnvVar ? `YOUR_${apiKeyEnvVar}` : "YOUR_API_KEY",
|
|
592
|
+
apiKeyEnvVar
|
|
593
|
+
);
|
|
594
|
+
if (!config.mcpServers) {
|
|
595
|
+
config.mcpServers = {};
|
|
596
|
+
}
|
|
597
|
+
config.mcpServers[serviceId] = serverConfig;
|
|
598
|
+
writeMcpConfig(config);
|
|
599
|
+
}
|
|
600
|
+
async function installMcpServiceForCodex(serviceId, baseConfig, apiKey, apiKeyEnvVar) {
|
|
601
|
+
const existingConfig = readCodexConfig();
|
|
602
|
+
let command = baseConfig.command || serviceId;
|
|
603
|
+
let args = (baseConfig.args || []).map((arg) => String(arg));
|
|
604
|
+
if (serviceId === "serena") {
|
|
605
|
+
const idx = args.indexOf("--context");
|
|
606
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
607
|
+
args[idx + 1] = "codex";
|
|
608
|
+
} else {
|
|
609
|
+
args.push("--context", "codex");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const serviceConfig = { id: serviceId.toLowerCase(), command, args };
|
|
613
|
+
applyCodexPlatformCommand(serviceConfig);
|
|
614
|
+
command = serviceConfig.command;
|
|
615
|
+
args = serviceConfig.args || [];
|
|
616
|
+
const env = { ...baseConfig.env || {} };
|
|
617
|
+
if (isWindows()) {
|
|
618
|
+
const systemRoot = getSystemRoot();
|
|
619
|
+
if (systemRoot) {
|
|
620
|
+
env.SYSTEMROOT = systemRoot;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (apiKey && apiKeyEnvVar) {
|
|
624
|
+
env[apiKeyEnvVar] = apiKey;
|
|
625
|
+
}
|
|
626
|
+
const newService = {
|
|
627
|
+
id: serviceId.toLowerCase(),
|
|
628
|
+
command,
|
|
629
|
+
args,
|
|
630
|
+
env: Object.keys(env).length > 0 ? env : void 0,
|
|
631
|
+
startup_timeout_sec: 30
|
|
632
|
+
};
|
|
633
|
+
const existingServices = existingConfig?.mcpServices || [];
|
|
634
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
635
|
+
for (const svc of existingServices) {
|
|
636
|
+
mergedMap.set(svc.id.toLowerCase(), { ...svc });
|
|
637
|
+
}
|
|
638
|
+
mergedMap.set(newService.id, newService);
|
|
639
|
+
const finalServices = Array.from(mergedMap.values());
|
|
640
|
+
const configData = {
|
|
641
|
+
model: existingConfig?.model || null,
|
|
642
|
+
modelProvider: existingConfig?.modelProvider || null,
|
|
643
|
+
providers: existingConfig?.providers || [],
|
|
644
|
+
mcpServices: finalServices,
|
|
645
|
+
otherConfig: existingConfig?.otherConfig || []
|
|
646
|
+
};
|
|
647
|
+
writeCodexConfig(configData);
|
|
648
|
+
}
|
|
649
|
+
async function uninstallMcpService(serviceId, tool) {
|
|
650
|
+
ensureI18nInitialized();
|
|
651
|
+
const targetTool = tool || detectActiveTool();
|
|
652
|
+
try {
|
|
653
|
+
if (targetTool === "codex") {
|
|
654
|
+
await uninstallMcpServiceFromCodex(serviceId);
|
|
655
|
+
} else {
|
|
656
|
+
await uninstallMcpServiceFromClaudeCode(serviceId);
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
success: true,
|
|
660
|
+
serviceId
|
|
661
|
+
};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
return {
|
|
664
|
+
success: false,
|
|
665
|
+
serviceId,
|
|
666
|
+
error: error instanceof Error ? error.message : String(error)
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async function uninstallMcpServiceFromClaudeCode(serviceId) {
|
|
671
|
+
const config = readMcpConfig();
|
|
672
|
+
if (!config || !config.mcpServers) {
|
|
673
|
+
throw new Error(i18n.t("mcp:installer.noConfig"));
|
|
674
|
+
}
|
|
675
|
+
const normalizedId = serviceId.toLowerCase();
|
|
676
|
+
const existingKey = Object.keys(config.mcpServers).find(
|
|
677
|
+
(key) => key.toLowerCase() === normalizedId
|
|
678
|
+
);
|
|
679
|
+
if (!existingKey) {
|
|
680
|
+
throw new Error(i18n.t("mcp:installer.serviceNotInstalled", { id: serviceId }));
|
|
681
|
+
}
|
|
682
|
+
delete config.mcpServers[existingKey];
|
|
683
|
+
writeMcpConfig(config);
|
|
684
|
+
}
|
|
685
|
+
async function uninstallMcpServiceFromCodex(serviceId) {
|
|
686
|
+
const existingConfig = readCodexConfig();
|
|
687
|
+
if (!existingConfig || !existingConfig.mcpServices) {
|
|
688
|
+
throw new Error(i18n.t("mcp:installer.noConfig"));
|
|
689
|
+
}
|
|
690
|
+
const normalizedId = serviceId.toLowerCase();
|
|
691
|
+
const serviceIndex = existingConfig.mcpServices.findIndex(
|
|
692
|
+
(svc) => svc.id.toLowerCase() === normalizedId
|
|
693
|
+
);
|
|
694
|
+
if (serviceIndex === -1) {
|
|
695
|
+
throw new Error(i18n.t("mcp:installer.serviceNotInstalled", { id: serviceId }));
|
|
696
|
+
}
|
|
697
|
+
existingConfig.mcpServices.splice(serviceIndex, 1);
|
|
698
|
+
writeCodexConfig(existingConfig);
|
|
699
|
+
}
|
|
700
|
+
async function listInstalledMcpServices(tool) {
|
|
701
|
+
ensureI18nInitialized();
|
|
702
|
+
const targetTool = tool || detectActiveTool();
|
|
703
|
+
if (targetTool === "codex") {
|
|
704
|
+
return listInstalledMcpServicesFromCodex();
|
|
705
|
+
} else {
|
|
706
|
+
return listInstalledMcpServicesFromClaudeCode();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function listInstalledMcpServicesFromClaudeCode() {
|
|
710
|
+
const config = readMcpConfig();
|
|
711
|
+
if (!config || !config.mcpServers) {
|
|
712
|
+
return [];
|
|
713
|
+
}
|
|
714
|
+
const services = [];
|
|
715
|
+
for (const [id, serverConfig] of Object.entries(config.mcpServers)) {
|
|
716
|
+
const knownService = MCP_SERVICE_CONFIGS.find(
|
|
717
|
+
(s) => s.id.toLowerCase() === id.toLowerCase()
|
|
718
|
+
);
|
|
719
|
+
services.push({
|
|
720
|
+
id,
|
|
721
|
+
name: knownService?.id || id,
|
|
722
|
+
command: serverConfig.command,
|
|
723
|
+
args: serverConfig.args,
|
|
724
|
+
url: serverConfig.url,
|
|
725
|
+
type: serverConfig.type || "stdio"
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
return services;
|
|
729
|
+
}
|
|
730
|
+
function listInstalledMcpServicesFromCodex() {
|
|
731
|
+
const config = readCodexConfig();
|
|
732
|
+
if (!config || !config.mcpServices) {
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
const services = [];
|
|
736
|
+
for (const svc of config.mcpServices) {
|
|
737
|
+
const knownService = MCP_SERVICE_CONFIGS.find(
|
|
738
|
+
(s) => s.id.toLowerCase() === svc.id.toLowerCase()
|
|
739
|
+
);
|
|
740
|
+
services.push({
|
|
741
|
+
id: svc.id,
|
|
742
|
+
name: knownService?.id || svc.id,
|
|
743
|
+
command: svc.command,
|
|
744
|
+
args: svc.args,
|
|
745
|
+
type: "stdio"
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return services;
|
|
749
|
+
}
|
|
750
|
+
async function isMcpServiceInstalled(serviceId, tool) {
|
|
751
|
+
const installedServices = await listInstalledMcpServices(tool);
|
|
752
|
+
const normalizedId = serviceId.toLowerCase();
|
|
753
|
+
return installedServices.some((svc) => svc.id.toLowerCase() === normalizedId);
|
|
754
|
+
}
|
|
755
|
+
async function displayInstalledMcpServices(tool) {
|
|
756
|
+
ensureI18nInitialized();
|
|
757
|
+
const targetTool = tool || detectActiveTool();
|
|
758
|
+
const services = await listInstalledMcpServices(targetTool);
|
|
759
|
+
if (services.length === 0) {
|
|
760
|
+
console.log(ansis.yellow(i18n.t("mcp:installer.noServicesInstalled")));
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
console.log(ansis.cyan.bold(`
|
|
764
|
+
${i18n.t("mcp:installer.installedServices", { tool: targetTool })}
|
|
765
|
+
`));
|
|
766
|
+
services.forEach((service, idx) => {
|
|
767
|
+
console.log(`${ansis.green(`${idx + 1}.`)} ${ansis.bold(service.name)} ${ansis.dim(`[${service.id}]`)}`);
|
|
768
|
+
if (service.command) {
|
|
769
|
+
console.log(` ${ansis.dim(`Command: ${service.command}`)}`);
|
|
770
|
+
}
|
|
771
|
+
if (service.url) {
|
|
772
|
+
console.log(` ${ansis.dim(`URL: ${service.url}`)}`);
|
|
773
|
+
}
|
|
774
|
+
console.log("");
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function getLocalFallbackServices() {
|
|
779
|
+
return [
|
|
780
|
+
// CCJK managed services (from mcp-services config)
|
|
781
|
+
...MCP_SERVICE_CONFIGS.map((svc) => ({
|
|
782
|
+
name: svc.id,
|
|
783
|
+
description: svc.id,
|
|
784
|
+
// Will be replaced with i18n
|
|
785
|
+
package: svc.config.command || svc.id,
|
|
786
|
+
category: "ccjk",
|
|
787
|
+
serviceId: svc.id,
|
|
788
|
+
requiresApiKey: svc.requiresApiKey
|
|
789
|
+
})),
|
|
790
|
+
// External MCP servers from Awesome MCP Servers (fallback)
|
|
791
|
+
{ name: "Filesystem", description: "Secure file operations", package: "@modelcontextprotocol/server-filesystem", category: "core" },
|
|
792
|
+
{ name: "GitHub", description: "Repository management", package: "@modelcontextprotocol/server-github", category: "dev" },
|
|
793
|
+
{ name: "PostgreSQL", description: "Database operations", package: "@modelcontextprotocol/server-postgres", category: "database" },
|
|
794
|
+
{ name: "Puppeteer", description: "Browser automation", package: "@modelcontextprotocol/server-puppeteer", category: "automation" },
|
|
795
|
+
{ name: "Brave Search", description: "Web search", package: "@modelcontextprotocol/server-brave-search", category: "search" },
|
|
796
|
+
{ name: "Google Maps", description: "Location services", package: "@modelcontextprotocol/server-google-maps", category: "api" },
|
|
797
|
+
{ name: "Slack", description: "Team communication", package: "@modelcontextprotocol/server-slack", category: "communication" },
|
|
798
|
+
{ name: "Memory", description: "Knowledge graph", package: "@modelcontextprotocol/server-memory", category: "ai" }
|
|
799
|
+
];
|
|
800
|
+
}
|
|
801
|
+
function convertToMcpServer(pkg) {
|
|
802
|
+
const lang = i18n.language;
|
|
803
|
+
return {
|
|
804
|
+
name: pkg.name,
|
|
805
|
+
description: pkg.description[lang] || pkg.description.en,
|
|
806
|
+
package: pkg.id,
|
|
807
|
+
category: pkg.category,
|
|
808
|
+
stars: pkg.rating,
|
|
809
|
+
serviceId: pkg.id,
|
|
810
|
+
requiresApiKey: pkg.permissions.some((p) => p.type === "env")
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
async function mcpSearch(keyword, options = {}) {
|
|
814
|
+
const client = getDefaultMarketplaceClient();
|
|
815
|
+
try {
|
|
816
|
+
const searchOptions = {
|
|
817
|
+
query: keyword,
|
|
818
|
+
category: options.category,
|
|
819
|
+
verified: options.verified,
|
|
820
|
+
sortBy: options.sortBy || "relevance",
|
|
821
|
+
limit: options.limit || 50
|
|
822
|
+
};
|
|
823
|
+
const result = await client.search(searchOptions);
|
|
824
|
+
if (result.packages.length === 0) {
|
|
825
|
+
console.log(ansis.yellow(`
|
|
826
|
+
${i18n.t("mcp:market.noResults", { keyword })}`));
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
console.log(ansis.cyan.bold(`
|
|
830
|
+
${i18n.t("mcp:market.searchResults", { count: result.total, keyword })}
|
|
831
|
+
`));
|
|
832
|
+
result.packages.forEach((pkg, idx) => {
|
|
833
|
+
const lang = i18n.language;
|
|
834
|
+
const verifiedBadge = pkg.verified ? ansis.green("\u2713") : ansis.dim("\u25CB");
|
|
835
|
+
console.log(`${ansis.green(`${idx + 1}.`)} ${ansis.bold(pkg.name)} ${verifiedBadge} ${ansis.dim(`[${pkg.category}]`)}`);
|
|
836
|
+
console.log(` ${pkg.description[lang] || pkg.description.en}`);
|
|
837
|
+
console.log(` ${ansis.dim(`\u{1F4E5} ${pkg.downloads.toLocaleString()} | \u2B50 ${pkg.rating.toFixed(1)}/5.0`)}`);
|
|
838
|
+
console.log(` ${ansis.dim(pkg.id)}
|
|
839
|
+
`);
|
|
840
|
+
});
|
|
841
|
+
if (result.hasMore) {
|
|
842
|
+
console.log(ansis.dim(`
|
|
843
|
+
${i18n.t("mcp:market.moreResults", { total: result.total, shown: result.packages.length })}`));
|
|
844
|
+
}
|
|
845
|
+
} catch {
|
|
846
|
+
console.log(ansis.yellow(`
|
|
847
|
+
${i18n.t("mcp:market.apiUnavailable")}`));
|
|
848
|
+
console.log(ansis.dim(i18n.t("mcp:market.usingLocalData")));
|
|
849
|
+
const localServers = getLocalFallbackServices();
|
|
850
|
+
const results = localServers.filter(
|
|
851
|
+
(s) => s.name.toLowerCase().includes(keyword.toLowerCase()) || s.description.toLowerCase().includes(keyword.toLowerCase()) || s.category.toLowerCase().includes(keyword.toLowerCase())
|
|
852
|
+
);
|
|
853
|
+
if (results.length === 0) {
|
|
854
|
+
console.log(ansis.yellow(`
|
|
855
|
+
${i18n.t("mcp:market.noResults", { keyword })}`));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
console.log(ansis.cyan.bold(`
|
|
859
|
+
${i18n.t("mcp:market.searchResults", { count: results.length, keyword })}
|
|
860
|
+
`));
|
|
861
|
+
results.forEach((server, idx) => {
|
|
862
|
+
console.log(`${ansis.green(`${idx + 1}.`)} ${ansis.bold(server.name)} ${ansis.dim(`[${server.category}]`)}`);
|
|
863
|
+
console.log(` ${server.description}`);
|
|
864
|
+
console.log(` ${ansis.dim(server.package)}
|
|
865
|
+
`);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async function mcpTrending(options = {}) {
|
|
870
|
+
const client = getDefaultMarketplaceClient();
|
|
871
|
+
try {
|
|
872
|
+
const trending = await client.getTrending(options.limit || 10);
|
|
873
|
+
if (trending.length === 0) {
|
|
874
|
+
console.log(ansis.yellow(`
|
|
875
|
+
${i18n.t("mcp:market.noTrending")}`));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
console.log(ansis.cyan.bold(`
|
|
879
|
+
${i18n.t("mcp:market.trending")}
|
|
880
|
+
`));
|
|
881
|
+
trending.forEach((pkg, idx) => {
|
|
882
|
+
const lang = i18n.language;
|
|
883
|
+
const verifiedBadge = pkg.verified ? ansis.green("\u2713") : ansis.dim("\u25CB");
|
|
884
|
+
console.log(`${ansis.green(`${idx + 1}.`)} ${ansis.bold(pkg.name)} ${verifiedBadge} ${ansis.dim(`[${pkg.category}]`)}`);
|
|
885
|
+
console.log(` ${pkg.description[lang] || pkg.description.en}`);
|
|
886
|
+
console.log(` ${ansis.dim(`\u{1F4E5} ${pkg.downloads.toLocaleString()} | \u2B50 ${pkg.rating.toFixed(1)}/5.0 (${pkg.ratingCount} ${i18n.t("mcp:market.ratings")})`)}`);
|
|
887
|
+
console.log(` ${ansis.dim(pkg.id)}
|
|
888
|
+
`);
|
|
889
|
+
});
|
|
890
|
+
} catch {
|
|
891
|
+
console.log(ansis.yellow(`
|
|
892
|
+
${i18n.t("mcp:market.apiUnavailable")}`));
|
|
893
|
+
console.log(ansis.dim(i18n.t("mcp:market.usingLocalData")));
|
|
894
|
+
console.log(ansis.cyan.bold(`
|
|
895
|
+
${i18n.t("mcp:market.trending")}
|
|
896
|
+
`));
|
|
897
|
+
const localServers = getLocalFallbackServices();
|
|
898
|
+
const trending = localServers.slice(0, options.limit || 5);
|
|
899
|
+
trending.forEach((server, idx) => {
|
|
900
|
+
console.log(`${ansis.green(`${idx + 1}.`)} ${ansis.bold(server.name)} ${ansis.dim(`[${server.category}]`)}`);
|
|
901
|
+
console.log(` ${server.description}`);
|
|
902
|
+
console.log(` ${ansis.dim(server.package)}
|
|
903
|
+
`);
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async function mcpInstall(serverName, options = {}) {
|
|
908
|
+
const client = getDefaultMarketplaceClient();
|
|
909
|
+
let server = null;
|
|
910
|
+
const localServers = getLocalFallbackServices();
|
|
911
|
+
server = localServers.find((s) => s.name.toLowerCase() === serverName.toLowerCase()) || null;
|
|
912
|
+
if (!server) {
|
|
913
|
+
try {
|
|
914
|
+
const pkg = await client.getPackage(serverName);
|
|
915
|
+
if (pkg) {
|
|
916
|
+
server = convertToMcpServer(pkg);
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (!server) {
|
|
922
|
+
console.log(ansis.red(`
|
|
923
|
+
${i18n.t("mcp:market.serverNotFound", { name: serverName })}`));
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (server.serviceId) {
|
|
927
|
+
const isInstalled = await isMcpServiceInstalled(server.serviceId, options.tool);
|
|
928
|
+
if (isInstalled) {
|
|
929
|
+
console.log(ansis.yellow(`
|
|
930
|
+
${i18n.t("mcp:installer.alreadyInstalled", { name: server.name })}`));
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
console.log(ansis.cyan(`
|
|
934
|
+
${i18n.t("mcp:market.installing", { name: server.name })}`));
|
|
935
|
+
if (server.requiresApiKey) {
|
|
936
|
+
console.log(ansis.dim(i18n.t("mcp:installer.requiresApiKey")));
|
|
937
|
+
}
|
|
938
|
+
console.log("");
|
|
939
|
+
const { confirm } = await inquirer.prompt([{
|
|
940
|
+
type: "confirm",
|
|
941
|
+
name: "confirm",
|
|
942
|
+
message: i18n.t("mcp:market.confirmInstall"),
|
|
943
|
+
default: true
|
|
944
|
+
}]);
|
|
945
|
+
if (!confirm) {
|
|
946
|
+
console.log(ansis.yellow(i18n.t("mcp:market.cancelled")));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const result = await installMcpService(server.serviceId, options.tool);
|
|
950
|
+
if (result.success) {
|
|
951
|
+
console.log(ansis.green(`
|
|
952
|
+
${i18n.t("mcp:market.installSuccess", { name: server.name })}`));
|
|
953
|
+
console.log(ansis.dim(i18n.t("mcp:installer.restartRequired")));
|
|
954
|
+
} else {
|
|
955
|
+
console.log(ansis.red(`
|
|
956
|
+
${i18n.t("mcp:installer.installFailed", { name: server.name })}`));
|
|
957
|
+
if (result.error) {
|
|
958
|
+
console.log(ansis.dim(result.error));
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
} else {
|
|
962
|
+
console.log(ansis.cyan(`
|
|
963
|
+
${i18n.t("mcp:market.installing", { name: server.name })}`));
|
|
964
|
+
console.log(ansis.dim(`Package: ${server.package}
|
|
965
|
+
`));
|
|
966
|
+
const { confirm } = await inquirer.prompt([{
|
|
967
|
+
type: "confirm",
|
|
968
|
+
name: "confirm",
|
|
969
|
+
message: i18n.t("mcp:market.confirmInstall"),
|
|
970
|
+
default: true
|
|
971
|
+
}]);
|
|
972
|
+
if (!confirm) {
|
|
973
|
+
console.log(ansis.yellow(i18n.t("mcp:market.cancelled")));
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
console.log(ansis.green(`
|
|
977
|
+
${i18n.t("mcp:market.installSuccess", { name: server.name })}`));
|
|
978
|
+
console.log(ansis.dim(i18n.t("mcp:market.manualConfig")));
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
async function mcpUninstall(serverName, options = {}) {
|
|
982
|
+
const localServers = getLocalFallbackServices();
|
|
983
|
+
const server = localServers.find((s) => s.name.toLowerCase() === serverName.toLowerCase());
|
|
984
|
+
const serviceId = server?.serviceId || serverName;
|
|
985
|
+
const isInstalled = await isMcpServiceInstalled(serviceId, options.tool);
|
|
986
|
+
if (!isInstalled) {
|
|
987
|
+
console.log(ansis.yellow(`
|
|
988
|
+
${i18n.t("mcp:installer.serviceNotInstalled", { id: serverName })}`));
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const displayName = server?.name || serverName;
|
|
992
|
+
const { confirm } = await inquirer.prompt([{
|
|
993
|
+
type: "confirm",
|
|
994
|
+
name: "confirm",
|
|
995
|
+
message: i18n.t("mcp:market.confirmUninstall", { name: displayName }),
|
|
996
|
+
default: false
|
|
997
|
+
}]);
|
|
998
|
+
if (!confirm) {
|
|
999
|
+
console.log(ansis.yellow(i18n.t("mcp:market.cancelled")));
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const result = await uninstallMcpService(serviceId, options.tool);
|
|
1003
|
+
if (result.success) {
|
|
1004
|
+
console.log(ansis.green(`
|
|
1005
|
+
${i18n.t("mcp:installer.uninstallSuccess", { name: displayName })}`));
|
|
1006
|
+
console.log(ansis.dim(i18n.t("mcp:installer.restartRequired")));
|
|
1007
|
+
} else {
|
|
1008
|
+
console.log(ansis.red(`
|
|
1009
|
+
${i18n.t("mcp:installer.uninstallFailed", { name: displayName })}`));
|
|
1010
|
+
if (result.error) {
|
|
1011
|
+
console.log(ansis.dim(result.error));
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
async function mcpList(options = {}) {
|
|
1016
|
+
await displayInstalledMcpServices(options.tool);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
export { mcpList as a, mcpSearch as b, mcpUninstall as c, mcpTrending as d, mcpInstall as m };
|