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.
Files changed (37) hide show
  1. package/README.md +270 -444
  2. package/README.zh-CN.md +273 -447
  3. package/dist/chunks/auto-bootstrap.mjs +358 -0
  4. package/dist/chunks/ccr.mjs +5 -2
  5. package/dist/chunks/claude-wrapper.mjs +442 -0
  6. package/dist/chunks/cloud-sync.mjs +29 -0
  7. package/dist/chunks/constants.mjs +1 -1
  8. package/dist/chunks/context-manager.mjs +641 -0
  9. package/dist/chunks/context.mjs +248 -0
  10. package/dist/chunks/index2.mjs +2 -0
  11. package/dist/chunks/index3.mjs +19 -19
  12. package/dist/chunks/init.mjs +18 -8
  13. package/dist/chunks/marketplace.mjs +6 -2
  14. package/dist/chunks/mcp.mjs +1 -1
  15. package/dist/chunks/menu.mjs +3 -3
  16. package/dist/chunks/notification.mjs +27 -27
  17. package/dist/chunks/package.mjs +1 -1
  18. package/dist/chunks/platform.mjs +70 -21
  19. package/dist/chunks/silent-updater.mjs +396 -0
  20. package/dist/chunks/skills-sync.mjs +1 -1
  21. package/dist/chunks/version-checker.mjs +31 -31
  22. package/dist/cli.mjs +67 -5
  23. package/dist/i18n/locales/en/context.json +32 -0
  24. package/dist/i18n/locales/en/marketplace.json +1 -0
  25. package/dist/i18n/locales/en/mcp.json +12 -1
  26. package/dist/i18n/locales/en/superpowers.json +46 -0
  27. package/dist/i18n/locales/zh-CN/context.json +32 -0
  28. package/dist/i18n/locales/zh-CN/marketplace.json +1 -0
  29. package/dist/i18n/locales/zh-CN/mcp.json +12 -1
  30. package/dist/i18n/locales/zh-CN/superpowers.json +46 -0
  31. package/dist/index.d.mts +2 -2
  32. package/dist/index.d.ts +2 -2
  33. package/dist/shared/ccjk.QbS8EAOd.mjs +1019 -0
  34. package/dist/shared/ccjk.RR9TS76h.mjs +698 -0
  35. package/package.json +4 -1
  36. package/dist/shared/ccjk.Bi-m3LKY.mjs +0 -357
  37. 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 };