ccjk 2.0.7 → 2.0.9

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 (39) hide show
  1. package/README.ja.md +6 -6
  2. package/README.ko.md +5 -5
  3. package/README.md +55 -9
  4. package/README.zh-CN.md +9 -9
  5. package/dist/chunks/claude-code-config-manager.mjs +8 -8
  6. package/dist/chunks/claude-code-incremental-manager.mjs +2 -2
  7. package/dist/chunks/codex-config-switch.mjs +4 -4
  8. package/dist/chunks/codex-provider-manager.mjs +2 -2
  9. package/dist/chunks/codex-uninstaller.mjs +3 -3
  10. package/dist/chunks/commands.mjs +2 -2
  11. package/dist/chunks/features.mjs +11 -11
  12. package/dist/chunks/plugin-recommendation.mjs +555 -0
  13. package/dist/chunks/simple-config.mjs +231 -89
  14. package/dist/chunks/skills-sync.mjs +854 -0
  15. package/dist/cli.mjs +8187 -1317
  16. package/dist/i18n/locales/en/agents.json +135 -0
  17. package/dist/i18n/locales/en/claude-md.json +73 -0
  18. package/dist/i18n/locales/en/cloudPlugins.json +118 -0
  19. package/dist/i18n/locales/en/common.json +2 -1
  20. package/dist/i18n/locales/en/hooksSync.json +111 -0
  21. package/dist/i18n/locales/en/mcp.json +31 -6
  22. package/dist/i18n/locales/en/menu.json +60 -1
  23. package/dist/i18n/locales/en/notification.json +306 -0
  24. package/dist/i18n/locales/en/plugins.json +133 -0
  25. package/dist/i18n/locales/en/skillsSync.json +74 -0
  26. package/dist/i18n/locales/zh-CN/agents.json +135 -0
  27. package/dist/i18n/locales/zh-CN/claude-md.json +73 -0
  28. package/dist/i18n/locales/zh-CN/cloudPlugins.json +118 -0
  29. package/dist/i18n/locales/zh-CN/common.json +2 -1
  30. package/dist/i18n/locales/zh-CN/hooksSync.json +111 -0
  31. package/dist/i18n/locales/zh-CN/mcp.json +31 -6
  32. package/dist/i18n/locales/zh-CN/menu.json +60 -1
  33. package/dist/i18n/locales/zh-CN/notification.json +306 -0
  34. package/dist/i18n/locales/zh-CN/plugins.json +133 -0
  35. package/dist/i18n/locales/zh-CN/skillsSync.json +74 -0
  36. package/dist/index.d.mts +18 -18
  37. package/dist/index.d.ts +18 -18
  38. package/dist/index.mjs +190 -188
  39. package/package.json +52 -48
@@ -0,0 +1,555 @@
1
+ import { existsSync, unlinkSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { platform, homedir } from 'node:os';
3
+ import { join } from 'pathe';
4
+
5
+ const DEFAULT_TIMEOUT$1 = 3e4;
6
+ const DEFAULT_RETRY_ATTEMPTS = 3;
7
+ const DEFAULT_RETRY_DELAY = 1e3;
8
+ class CloudApiClient {
9
+ baseUrl;
10
+ timeout;
11
+ authToken;
12
+ userAgent;
13
+ retry;
14
+ /**
15
+ * Create a new CloudApiClient instance
16
+ *
17
+ * @param config - Client configuration
18
+ */
19
+ constructor(config) {
20
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
21
+ this.timeout = config.timeout || DEFAULT_TIMEOUT$1;
22
+ this.authToken = config.authToken;
23
+ this.userAgent = config.userAgent || "CCJK-Client/1.0";
24
+ this.retry = config.retry || {
25
+ maxAttempts: DEFAULT_RETRY_ATTEMPTS,
26
+ delay: DEFAULT_RETRY_DELAY
27
+ };
28
+ }
29
+ // ==========================================================================
30
+ // Configuration Methods
31
+ // ==========================================================================
32
+ /**
33
+ * Set authentication token
34
+ *
35
+ * @param token - Authentication token
36
+ */
37
+ setAuthToken(token) {
38
+ this.authToken = token;
39
+ }
40
+ /**
41
+ * Clear authentication token
42
+ */
43
+ clearAuthToken() {
44
+ this.authToken = void 0;
45
+ }
46
+ /**
47
+ * Get current base URL
48
+ */
49
+ getBaseUrl() {
50
+ return this.baseUrl;
51
+ }
52
+ /**
53
+ * Update base URL
54
+ *
55
+ * @param url - New base URL
56
+ */
57
+ setBaseUrl(url) {
58
+ this.baseUrl = url.replace(/\/$/, "");
59
+ }
60
+ // ==========================================================================
61
+ // Request Methods
62
+ // ==========================================================================
63
+ /**
64
+ * Make an HTTP request to the cloud service
65
+ *
66
+ * @param path - API endpoint path (e.g., '/plugins/recommend')
67
+ * @param options - Request options
68
+ * @returns API response
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const response = await client.request<{ data: string }>('/api/endpoint', {
73
+ * method: 'POST',
74
+ * body: { key: 'value' },
75
+ * timeout: 5000
76
+ * })
77
+ * ```
78
+ */
79
+ async request(path, options) {
80
+ const maxAttempts = options.retry?.maxAttempts || this.retry.maxAttempts;
81
+ const retryDelay = options.retry?.delay || this.retry.delay;
82
+ let lastError = null;
83
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
84
+ try {
85
+ return await this.executeRequest(path, options);
86
+ } catch (error) {
87
+ lastError = error instanceof Error ? error : new Error(String(error));
88
+ if (attempt === maxAttempts || this.isClientError(lastError)) {
89
+ break;
90
+ }
91
+ await this.sleep(retryDelay * attempt);
92
+ }
93
+ }
94
+ return {
95
+ success: false,
96
+ error: lastError?.message || "Request failed",
97
+ code: "REQUEST_FAILED"
98
+ };
99
+ }
100
+ /**
101
+ * Execute a single HTTP request
102
+ *
103
+ * @private
104
+ */
105
+ async executeRequest(path, options) {
106
+ const url = this.buildUrl(path, options.query);
107
+ const timeout = options.timeout || this.timeout;
108
+ const controller = new AbortController();
109
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
110
+ try {
111
+ const headers = this.buildHeaders(options);
112
+ const response = await fetch(url, {
113
+ method: options.method,
114
+ headers,
115
+ body: options.body ? JSON.stringify(options.body) : void 0,
116
+ signal: controller.signal
117
+ });
118
+ clearTimeout(timeoutId);
119
+ const data = await response.json();
120
+ if (!response.ok) {
121
+ return {
122
+ success: false,
123
+ error: data.error || `HTTP ${response.status}: ${response.statusText}`,
124
+ code: data.code || `HTTP_${response.status}`
125
+ };
126
+ }
127
+ return {
128
+ ...data,
129
+ success: true
130
+ };
131
+ } catch (error) {
132
+ clearTimeout(timeoutId);
133
+ if (error instanceof Error) {
134
+ if (error.name === "AbortError") {
135
+ return {
136
+ success: false,
137
+ error: "Request timeout",
138
+ code: "TIMEOUT"
139
+ };
140
+ }
141
+ return {
142
+ success: false,
143
+ error: error.message,
144
+ code: "NETWORK_ERROR"
145
+ };
146
+ }
147
+ return {
148
+ success: false,
149
+ error: String(error),
150
+ code: "UNKNOWN_ERROR"
151
+ };
152
+ }
153
+ }
154
+ // ==========================================================================
155
+ // Convenience Methods
156
+ // ==========================================================================
157
+ /**
158
+ * Make a GET request
159
+ *
160
+ * @param path - API endpoint path
161
+ * @param query - Query parameters
162
+ * @param options - Additional request options
163
+ * @returns API response
164
+ */
165
+ async get(path, query, options) {
166
+ return this.request(path, {
167
+ method: "GET",
168
+ query,
169
+ ...options
170
+ });
171
+ }
172
+ /**
173
+ * Make a POST request
174
+ *
175
+ * @param path - API endpoint path
176
+ * @param body - Request body
177
+ * @param options - Additional request options
178
+ * @returns API response
179
+ */
180
+ async post(path, body, options) {
181
+ return this.request(path, {
182
+ method: "POST",
183
+ body,
184
+ ...options
185
+ });
186
+ }
187
+ /**
188
+ * Make a PUT request
189
+ *
190
+ * @param path - API endpoint path
191
+ * @param body - Request body
192
+ * @param options - Additional request options
193
+ * @returns API response
194
+ */
195
+ async put(path, body, options) {
196
+ return this.request(path, {
197
+ method: "PUT",
198
+ body,
199
+ ...options
200
+ });
201
+ }
202
+ /**
203
+ * Make a DELETE request
204
+ *
205
+ * @param path - API endpoint path
206
+ * @param options - Additional request options
207
+ * @returns API response
208
+ */
209
+ async delete(path, options) {
210
+ return this.request(path, {
211
+ method: "DELETE",
212
+ ...options
213
+ });
214
+ }
215
+ // ==========================================================================
216
+ // Helper Methods
217
+ // ==========================================================================
218
+ /**
219
+ * Build full URL with query parameters
220
+ *
221
+ * @private
222
+ */
223
+ buildUrl(path, query) {
224
+ const url = `${this.baseUrl}${path}`;
225
+ if (!query || Object.keys(query).length === 0) {
226
+ return url;
227
+ }
228
+ const params = new URLSearchParams();
229
+ for (const [key, value] of Object.entries(query)) {
230
+ params.append(key, String(value));
231
+ }
232
+ return `${url}?${params.toString()}`;
233
+ }
234
+ /**
235
+ * Build request headers
236
+ *
237
+ * @private
238
+ */
239
+ buildHeaders(options) {
240
+ const headers = {
241
+ "Content-Type": "application/json",
242
+ "User-Agent": this.userAgent,
243
+ ...options.headers
244
+ };
245
+ const token = options.authToken || this.authToken;
246
+ if (token) {
247
+ headers.Authorization = `Bearer ${token}`;
248
+ }
249
+ return headers;
250
+ }
251
+ /**
252
+ * Check if error is a client error (4xx)
253
+ *
254
+ * @private
255
+ */
256
+ isClientError(error) {
257
+ return error.message.includes("HTTP 4");
258
+ }
259
+ /**
260
+ * Sleep for specified milliseconds
261
+ *
262
+ * @private
263
+ */
264
+ sleep(ms) {
265
+ return new Promise((resolve) => setTimeout(resolve, ms));
266
+ }
267
+ }
268
+
269
+ const CLOUD_API_BASE_URL = "https://api.claudehome.cn";
270
+ const CACHE_DIR = join(homedir(), ".ccjk", "cache");
271
+ const CACHE_FILE = join(CACHE_DIR, "plugin-recommendations.json");
272
+ const CACHE_TTL = 24 * 60 * 60 * 1e3;
273
+ const DEFAULT_TIMEOUT = 1e4;
274
+ class PluginRecommendationService {
275
+ apiClient;
276
+ fallbackData;
277
+ /**
278
+ * Create a new PluginRecommendationService instance
279
+ *
280
+ * @param baseUrl - Cloud API base URL (default: https://api.claudehome.cn)
281
+ * @param fallbackData - Fallback data for offline mode
282
+ */
283
+ constructor(baseUrl = CLOUD_API_BASE_URL, fallbackData = []) {
284
+ this.apiClient = new CloudApiClient({
285
+ baseUrl,
286
+ timeout: DEFAULT_TIMEOUT,
287
+ userAgent: "CCJK-PluginRecommendation/1.0"
288
+ });
289
+ this.fallbackData = fallbackData;
290
+ }
291
+ // ==========================================================================
292
+ // Public Methods
293
+ // ==========================================================================
294
+ /**
295
+ * Get plugin recommendations
296
+ *
297
+ * Fetches recommendations from cache if available and valid,
298
+ * otherwise requests from cloud API with fallback to local data.
299
+ *
300
+ * @param request - Recommendation request parameters
301
+ * @returns Recommendation response
302
+ */
303
+ async getRecommendations(request) {
304
+ const cached = this.getCachedRecommendations(request);
305
+ if (cached) {
306
+ return {
307
+ recommendations: cached.data,
308
+ total: cached.data.length,
309
+ fromCache: true,
310
+ cacheTimestamp: cached.timestamp
311
+ };
312
+ }
313
+ try {
314
+ const response = await this.fetchFromCloud(request);
315
+ if (response.success && response.data) {
316
+ this.cacheRecommendations(request, response.data.recommendations);
317
+ return {
318
+ recommendations: response.data.recommendations,
319
+ total: response.data.total || response.data.recommendations.length,
320
+ fromCache: false,
321
+ algorithmVersion: response.data.algorithmVersion
322
+ };
323
+ }
324
+ } catch (error) {
325
+ console.warn("Cloud API request failed:", error);
326
+ }
327
+ const localRecommendations = this.getLocalRecommendations(request);
328
+ this.cacheRecommendations(request, localRecommendations);
329
+ return {
330
+ recommendations: localRecommendations,
331
+ total: localRecommendations.length,
332
+ fromCache: false
333
+ };
334
+ }
335
+ /**
336
+ * Clear recommendation cache
337
+ *
338
+ * Removes all cached recommendations.
339
+ */
340
+ clearCache() {
341
+ try {
342
+ if (existsSync(CACHE_FILE)) {
343
+ unlinkSync(CACHE_FILE);
344
+ }
345
+ } catch (error) {
346
+ console.warn("Failed to clear cache:", error);
347
+ }
348
+ }
349
+ /**
350
+ * Get cache status
351
+ *
352
+ * Returns information about the current cache state.
353
+ */
354
+ getCacheStatus() {
355
+ try {
356
+ if (!existsSync(CACHE_FILE)) {
357
+ return { exists: false };
358
+ }
359
+ const data = readFileSync(CACHE_FILE, "utf-8");
360
+ const cached = JSON.parse(data);
361
+ const isValid = new Date(cached.expiresAt).getTime() > Date.now();
362
+ return {
363
+ exists: true,
364
+ timestamp: cached.timestamp,
365
+ expiresAt: cached.expiresAt,
366
+ isValid
367
+ };
368
+ } catch {
369
+ return { exists: false };
370
+ }
371
+ }
372
+ /**
373
+ * Set fallback data
374
+ *
375
+ * Updates the local fallback data used when cloud API is unavailable.
376
+ *
377
+ * @param data - Fallback plugin recommendations
378
+ */
379
+ setFallbackData(data) {
380
+ this.fallbackData = data;
381
+ }
382
+ // ==========================================================================
383
+ // Private Methods - Cache Management
384
+ // ==========================================================================
385
+ /**
386
+ * Get cached recommendations if valid
387
+ *
388
+ * @private
389
+ */
390
+ getCachedRecommendations(request) {
391
+ try {
392
+ if (!existsSync(CACHE_FILE)) {
393
+ return null;
394
+ }
395
+ const data = readFileSync(CACHE_FILE, "utf-8");
396
+ const cached = JSON.parse(data);
397
+ if (new Date(cached.expiresAt).getTime() < Date.now()) {
398
+ return null;
399
+ }
400
+ if (!this.requestMatches(cached.request, request)) {
401
+ return null;
402
+ }
403
+ return cached;
404
+ } catch {
405
+ return null;
406
+ }
407
+ }
408
+ /**
409
+ * Cache recommendations to disk
410
+ *
411
+ * @private
412
+ */
413
+ cacheRecommendations(request, recommendations) {
414
+ try {
415
+ if (!existsSync(CACHE_DIR)) {
416
+ mkdirSync(CACHE_DIR, { recursive: true });
417
+ }
418
+ const now = /* @__PURE__ */ new Date();
419
+ const expiresAt = new Date(now.getTime() + CACHE_TTL);
420
+ const cached = {
421
+ data: recommendations,
422
+ timestamp: now.toISOString(),
423
+ request,
424
+ expiresAt: expiresAt.toISOString()
425
+ };
426
+ writeFileSync(CACHE_FILE, JSON.stringify(cached, null, 2), "utf-8");
427
+ } catch (error) {
428
+ console.warn("Failed to cache recommendations:", error);
429
+ }
430
+ }
431
+ /**
432
+ * Check if two requests match for caching purposes
433
+ *
434
+ * @private
435
+ */
436
+ requestMatches(cached, current) {
437
+ return cached.os === current.os && cached.codeTool === current.codeTool && cached.preferredLang === current.preferredLang && cached.category === current.category && JSON.stringify(cached.userTags || []) === JSON.stringify(current.userTags || []);
438
+ }
439
+ // ==========================================================================
440
+ // Private Methods - Cloud API
441
+ // ==========================================================================
442
+ /**
443
+ * Fetch recommendations from cloud API
444
+ *
445
+ * @private
446
+ */
447
+ async fetchFromCloud(request) {
448
+ return this.apiClient.post("/plugins/recommend", {
449
+ os: request.os,
450
+ codeTool: request.codeTool,
451
+ installedPlugins: request.installedPlugins,
452
+ preferredLang: request.preferredLang,
453
+ userTags: request.userTags,
454
+ category: request.category,
455
+ limit: request.limit || 10
456
+ });
457
+ }
458
+ // ==========================================================================
459
+ // Private Methods - Local Fallback
460
+ // ==========================================================================
461
+ /**
462
+ * Get recommendations from local fallback data
463
+ *
464
+ * Filters and scores local plugins based on request parameters.
465
+ *
466
+ * @private
467
+ */
468
+ getLocalRecommendations(request) {
469
+ let recommendations = [...this.fallbackData];
470
+ recommendations = recommendations.filter(
471
+ (plugin) => plugin.compatibility.os.includes(request.os) || plugin.compatibility.os.includes("all")
472
+ );
473
+ recommendations = recommendations.filter(
474
+ (plugin) => plugin.compatibility.codeTools.includes(request.codeTool) || plugin.compatibility.codeTools.includes("all")
475
+ );
476
+ if (request.category) {
477
+ recommendations = recommendations.filter(
478
+ (plugin) => plugin.category === request.category
479
+ );
480
+ }
481
+ if (request.userTags && request.userTags.length > 0) {
482
+ recommendations = recommendations.filter(
483
+ (plugin) => request.userTags.some((tag) => plugin.tags.includes(tag))
484
+ );
485
+ }
486
+ recommendations = recommendations.filter(
487
+ (plugin) => !request.installedPlugins.includes(plugin.id)
488
+ );
489
+ recommendations = recommendations.map((plugin) => {
490
+ const score = this.calculateRecommendationScore(plugin, request);
491
+ return {
492
+ ...plugin,
493
+ recommendationScore: score
494
+ };
495
+ });
496
+ recommendations.sort(
497
+ (a, b) => (b.recommendationScore || 0) - (a.recommendationScore || 0)
498
+ );
499
+ const limit = request.limit || 10;
500
+ return recommendations.slice(0, limit);
501
+ }
502
+ /**
503
+ * Calculate recommendation score for a plugin
504
+ *
505
+ * Combines popularity, rating, and relevance to generate a score.
506
+ *
507
+ * @private
508
+ */
509
+ calculateRecommendationScore(plugin, request) {
510
+ let score = 0;
511
+ score += plugin.popularity / 100 * 40;
512
+ score += plugin.rating / 5 * 30;
513
+ if (request.userTags && request.userTags.length > 0) {
514
+ const matchingTags = plugin.tags.filter(
515
+ (tag) => request.userTags.includes(tag)
516
+ ).length;
517
+ score += matchingTags / request.userTags.length * 20;
518
+ } else {
519
+ score += 10;
520
+ }
521
+ if (plugin.verified) {
522
+ score += 10;
523
+ }
524
+ return Math.round(score);
525
+ }
526
+ }
527
+ let serviceInstance = null;
528
+ function getPluginRecommendationService(fallbackData) {
529
+ if (!serviceInstance) {
530
+ serviceInstance = new PluginRecommendationService(
531
+ CLOUD_API_BASE_URL,
532
+ fallbackData
533
+ );
534
+ }
535
+ return serviceInstance;
536
+ }
537
+ async function getRecommendations(request) {
538
+ const service = getPluginRecommendationService();
539
+ return service.getRecommendations(request);
540
+ }
541
+ function getCurrentPlatform() {
542
+ const p = platform();
543
+ switch (p) {
544
+ case "darwin":
545
+ return "darwin";
546
+ case "win32":
547
+ return "win32";
548
+ case "linux":
549
+ return "linux";
550
+ default:
551
+ return "linux";
552
+ }
553
+ }
554
+
555
+ export { PluginRecommendationService, getCurrentPlatform, getPluginRecommendationService, getRecommendations };