ccjk 9.6.1 → 9.8.0

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