ccjk 2.0.20 → 2.2.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 (71) hide show
  1. package/README.md +601 -35
  2. package/README.zh-CN.md +651 -0
  3. package/dist/chunks/api.mjs +100 -0
  4. package/dist/chunks/auto-updater.mjs +252 -0
  5. package/dist/chunks/ccjk-config.mjs +261 -0
  6. package/dist/chunks/ccr.mjs +77 -0
  7. package/dist/chunks/ccu.mjs +36 -0
  8. package/dist/chunks/check-updates.mjs +93 -0
  9. package/dist/chunks/claude-code-config-manager.mjs +28 -21
  10. package/dist/chunks/claude-code-incremental-manager.mjs +26 -18
  11. package/dist/chunks/claude-config.mjs +228 -0
  12. package/dist/chunks/codex.mjs +2134 -0
  13. package/dist/chunks/commands.mjs +2 -15
  14. package/dist/chunks/commit.mjs +119 -0
  15. package/dist/chunks/config-consolidator.mjs +281 -0
  16. package/dist/chunks/config-switch.mjs +302 -0
  17. package/dist/chunks/constants.mjs +156 -0
  18. package/dist/chunks/doctor.mjs +708 -0
  19. package/dist/chunks/features.mjs +35 -640
  20. package/dist/chunks/features2.mjs +661 -0
  21. package/dist/chunks/fs-operations.mjs +180 -0
  22. package/dist/chunks/index.mjs +3082 -0
  23. package/dist/chunks/index2.mjs +145 -0
  24. package/dist/chunks/init.mjs +2468 -0
  25. package/dist/chunks/interview.mjs +2916 -0
  26. package/dist/chunks/json-config.mjs +59 -0
  27. package/dist/chunks/marketplace.mjs +258 -0
  28. package/dist/chunks/mcp-doctor.mjs +160 -0
  29. package/dist/chunks/mcp-market.mjs +475 -0
  30. package/dist/chunks/mcp-performance.mjs +110 -0
  31. package/dist/chunks/mcp-profile.mjs +220 -0
  32. package/dist/chunks/mcp-release.mjs +138 -0
  33. package/dist/chunks/menu.mjs +3599 -0
  34. package/dist/chunks/notification.mjs +2336 -0
  35. package/dist/chunks/onboarding.mjs +711 -0
  36. package/dist/chunks/package.mjs +4 -0
  37. package/dist/chunks/permission-manager.mjs +210 -0
  38. package/dist/chunks/platform.mjs +321 -0
  39. package/dist/chunks/prompts.mjs +228 -0
  40. package/dist/chunks/session.mjs +355 -0
  41. package/dist/chunks/shencha.mjs +320 -0
  42. package/dist/chunks/skills-sync.mjs +4 -13
  43. package/dist/chunks/team.mjs +51 -0
  44. package/dist/chunks/tools.mjs +169 -0
  45. package/dist/chunks/uninstall.mjs +784 -0
  46. package/dist/chunks/update.mjs +104 -0
  47. package/dist/chunks/upgrade-manager.mjs +197 -0
  48. package/dist/chunks/workflows.mjs +100 -0
  49. package/dist/cli.mjs +581 -15348
  50. package/dist/i18n/locales/zh-CN/cli.json +1 -1
  51. package/dist/i18n/locales/zh-CN/common.json +1 -1
  52. package/dist/index.mjs +43 -2062
  53. package/dist/shared/ccjk.-FoZ3zat.mjs +761 -0
  54. package/dist/shared/ccjk.B7169qud.mjs +25 -0
  55. package/dist/shared/ccjk.BhKlRJ0h.mjs +114 -0
  56. package/dist/shared/ccjk.Bi-m3LKY.mjs +357 -0
  57. package/dist/shared/ccjk.COdsoe-Y.mjs +64 -0
  58. package/dist/shared/ccjk.CUdzQluX.mjs +46 -0
  59. package/dist/shared/ccjk.Cy-RH2qV.mjs +506 -0
  60. package/dist/shared/ccjk.DGjQxTq_.mjs +34 -0
  61. package/dist/shared/ccjk.DJM5aVQJ.mjs +586 -0
  62. package/dist/shared/ccjk.DhBeLRzf.mjs +28 -0
  63. package/dist/shared/ccjk.DwDtZ5cK.mjs +266 -0
  64. package/dist/shared/ccjk.n_AtlHzB.mjs +186 -0
  65. package/dist/shared/ccjk.qYAnUMuy.mjs +749 -0
  66. package/package.json +29 -25
  67. package/dist/chunks/codex-config-switch.mjs +0 -429
  68. package/dist/chunks/codex-provider-manager.mjs +0 -234
  69. package/dist/chunks/codex-uninstaller.mjs +0 -406
  70. package/dist/chunks/plugin-recommendation.mjs +0 -575
  71. package/dist/chunks/simple-config.mjs +0 -10950
@@ -0,0 +1,506 @@
1
+ import { exec } from 'node:child_process';
2
+ import * as nodeFs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import process from 'node:process';
5
+ import { promisify } from 'node:util';
6
+ import semver from 'semver';
7
+ import { getPlatform, findCommandPath, getHomebrewCommandPaths } from '../chunks/platform.mjs';
8
+
9
+ const execAsync = promisify(exec);
10
+ async function getInstalledVersion(command, maxRetries = 3) {
11
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
12
+ try {
13
+ let stdout;
14
+ try {
15
+ const result = await execAsync(`${command} -v`);
16
+ stdout = result.stdout;
17
+ } catch {
18
+ const result = await execAsync(`${command} --version`);
19
+ stdout = result.stdout;
20
+ }
21
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
22
+ return versionMatch ? versionMatch[1] : null;
23
+ } catch {
24
+ if (attempt === maxRetries) {
25
+ return null;
26
+ }
27
+ await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ async function getLatestVersion(packageName, maxRetries = 3) {
33
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
34
+ try {
35
+ const { stdout } = await execAsync(`npm view ${packageName} version`);
36
+ return stdout.trim();
37
+ } catch {
38
+ if (attempt === maxRetries) {
39
+ return null;
40
+ }
41
+ await new Promise((resolve) => setTimeout(resolve, 200 * attempt));
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ async function getClaudeCodeInstallationSource() {
47
+ if (getPlatform() !== "macos") {
48
+ const commandPath2 = await findCommandPath("claude");
49
+ return {
50
+ isHomebrew: false,
51
+ commandPath: commandPath2,
52
+ source: commandPath2 ? "other" : "not-found"
53
+ };
54
+ }
55
+ const commandPath = await findCommandPath("claude");
56
+ if (!commandPath) {
57
+ return { isHomebrew: false, commandPath: null, source: "not-found" };
58
+ }
59
+ const isFromCaskroom = commandPath.includes("/Caskroom/claude-code/");
60
+ if (isFromCaskroom) {
61
+ return { isHomebrew: true, commandPath, source: "homebrew-cask" };
62
+ }
63
+ try {
64
+ const { stdout: realPath } = await execAsync(`readlink -f "${commandPath}" 2>/dev/null || realpath "${commandPath}" 2>/dev/null || echo "${commandPath}"`);
65
+ const resolvedPath = realPath.trim();
66
+ if (resolvedPath.includes("/Caskroom/claude-code/")) {
67
+ return { isHomebrew: true, commandPath, source: "homebrew-cask" };
68
+ }
69
+ } catch {
70
+ }
71
+ if (commandPath.includes("/node_modules/") || commandPath.includes("/npm/") || commandPath.includes("/Cellar/node/")) {
72
+ return { isHomebrew: false, commandPath, source: "npm" };
73
+ }
74
+ return { isHomebrew: false, commandPath, source: "other" };
75
+ }
76
+ async function detectAllClaudeCodeInstallations() {
77
+ const installations = [];
78
+ const checkedPaths = /* @__PURE__ */ new Set();
79
+ const activeCommandPath = await findCommandPath("claude");
80
+ let activeResolvedPath = null;
81
+ if (activeCommandPath) {
82
+ try {
83
+ const { stdout } = await execAsync(`readlink -f "${activeCommandPath}" 2>/dev/null || realpath "${activeCommandPath}" 2>/dev/null || echo "${activeCommandPath}"`);
84
+ activeResolvedPath = stdout.trim();
85
+ } catch {
86
+ activeResolvedPath = activeCommandPath;
87
+ }
88
+ }
89
+ async function getVersionFromPath(path) {
90
+ try {
91
+ const { stdout } = await execAsync(`"${path}" -v 2>/dev/null || "${path}" --version 2>/dev/null`);
92
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
93
+ return versionMatch ? versionMatch[1] : null;
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+ function isActivePath(path) {
99
+ if (!activeResolvedPath)
100
+ return false;
101
+ return path === activeResolvedPath || path === activeCommandPath;
102
+ }
103
+ async function addInstallation(path, source) {
104
+ let resolvedPath = path;
105
+ try {
106
+ const { stdout } = await execAsync(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
107
+ resolvedPath = stdout.trim();
108
+ } catch {
109
+ }
110
+ if (checkedPaths.has(resolvedPath))
111
+ return;
112
+ checkedPaths.add(resolvedPath);
113
+ if (!nodeFs.existsSync(path))
114
+ return;
115
+ const version = await getVersionFromPath(path);
116
+ installations.push({
117
+ source,
118
+ path,
119
+ version,
120
+ isActive: isActivePath(path) || isActivePath(resolvedPath)
121
+ });
122
+ }
123
+ if (activeCommandPath && nodeFs.existsSync(activeCommandPath)) {
124
+ let activeSource = "other";
125
+ if (activeResolvedPath?.includes("/Caskroom/claude-code/")) {
126
+ activeSource = "homebrew-cask";
127
+ } else if (activeResolvedPath?.includes("/node_modules/") || activeResolvedPath?.includes("/npm/") || activeResolvedPath?.includes("/fnm_multishells/") || activeResolvedPath?.includes("/.nvm/") || activeResolvedPath?.includes("/Cellar/node/") || activeCommandPath.includes("/fnm_multishells/") || activeCommandPath.includes("/.nvm/")) {
128
+ activeSource = "npm";
129
+ }
130
+ await addInstallation(activeCommandPath, activeSource);
131
+ }
132
+ if (getPlatform() === "macos") {
133
+ const homebrewPaths = await getHomebrewCommandPaths("claude");
134
+ for (const path of homebrewPaths) {
135
+ if (path.includes("/Caskroom/claude-code/")) {
136
+ await addInstallation(path, "homebrew-cask");
137
+ } else if (path.includes("/Cellar/node/")) {
138
+ await addInstallation(path, "npm-homebrew-node");
139
+ }
140
+ }
141
+ try {
142
+ await execAsync("brew list --cask claude-code");
143
+ const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
144
+ for (const prefix of homebrewPrefixes) {
145
+ const caskroomPath = `${prefix}/Caskroom/claude-code`;
146
+ if (nodeFs.existsSync(caskroomPath)) {
147
+ const versions = nodeFs.readdirSync(caskroomPath).filter((v) => !v.startsWith("."));
148
+ for (const version of versions) {
149
+ const claudePath = `${caskroomPath}/${version}/claude`;
150
+ await addInstallation(claudePath, "homebrew-cask");
151
+ }
152
+ }
153
+ }
154
+ } catch {
155
+ }
156
+ }
157
+ const npmGlobalPaths = [
158
+ "/usr/local/bin/claude",
159
+ "/usr/bin/claude",
160
+ "/opt/homebrew/bin/claude",
161
+ `${process.env.HOME}/.npm-global/bin/claude`,
162
+ `${process.env.HOME}/.local/bin/claude`
163
+ ];
164
+ for (const path$1 of npmGlobalPaths) {
165
+ let exists = false;
166
+ let isBrokenSymlink = false;
167
+ try {
168
+ if (nodeFs.existsSync(path$1)) {
169
+ exists = true;
170
+ } else {
171
+ const stats = nodeFs.lstatSync(path$1);
172
+ if (stats.isSymbolicLink()) {
173
+ isBrokenSymlink = true;
174
+ }
175
+ }
176
+ } catch {
177
+ }
178
+ if (exists || isBrokenSymlink) {
179
+ let resolvedPath = path$1;
180
+ try {
181
+ const { stdout } = await execAsync(`readlink -f "${path$1}" 2>/dev/null || realpath "${path$1}" 2>/dev/null || echo "${path$1}"`);
182
+ resolvedPath = stdout.trim();
183
+ } catch {
184
+ }
185
+ if (resolvedPath.includes("/node_modules/") || resolvedPath.includes("/npm/")) {
186
+ await addInstallation(path$1, "npm");
187
+ } else if (resolvedPath.includes("/Caskroom/")) ; else if (isBrokenSymlink) {
188
+ const potentialLibPath = path$1.replace("/bin/claude", "/lib/node_modules/@anthropic-ai/claude-code");
189
+ if (nodeFs.existsSync(potentialLibPath)) {
190
+ try {
191
+ const pkgJsonPath = path.join(potentialLibPath, "package.json");
192
+ if (nodeFs.existsSync(pkgJsonPath)) {
193
+ const pkg = JSON.parse(nodeFs.readFileSync(pkgJsonPath, "utf8"));
194
+ installations.push({
195
+ source: "npm",
196
+ path: path$1,
197
+ version: pkg.version,
198
+ isActive: isActivePath(path$1)
199
+ });
200
+ checkedPaths.add(path$1);
201
+ }
202
+ } catch {
203
+ }
204
+ } else {
205
+ await addInstallation(path$1, "other");
206
+ }
207
+ } else {
208
+ await addInstallation(path$1, "other");
209
+ }
210
+ }
211
+ }
212
+ const globalModulesPaths = [
213
+ "/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code",
214
+ "/usr/local/lib/node_modules/@anthropic-ai/claude-code",
215
+ `${process.env.HOME}/.npm-global/lib/node_modules/@anthropic-ai/claude-code`
216
+ ];
217
+ for (const libPath of globalModulesPaths) {
218
+ if (nodeFs.existsSync(libPath) && !checkedPaths.has(libPath)) {
219
+ try {
220
+ const pkgJsonPath = path.join(libPath, "package.json");
221
+ if (nodeFs.existsSync(pkgJsonPath)) {
222
+ const pkg = JSON.parse(nodeFs.readFileSync(pkgJsonPath, "utf8"));
223
+ const binPath = libPath.replace("/lib/node_modules/@anthropic-ai/claude-code", "/bin/claude");
224
+ if (!checkedPaths.has(binPath)) {
225
+ installations.push({
226
+ source: "npm",
227
+ path: nodeFs.existsSync(binPath) ? binPath : libPath,
228
+ // Use bin path if exists, else lib path
229
+ version: pkg.version,
230
+ isActive: false
231
+ });
232
+ checkedPaths.add(libPath);
233
+ }
234
+ }
235
+ } catch {
236
+ }
237
+ }
238
+ }
239
+ if (getPlatform() === "macos") {
240
+ const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
241
+ for (const prefix of homebrewPrefixes) {
242
+ const cellarNodePath = `${prefix}/Cellar/node`;
243
+ if (nodeFs.existsSync(cellarNodePath)) {
244
+ try {
245
+ const versions = nodeFs.readdirSync(cellarNodePath);
246
+ for (const version of versions) {
247
+ const claudePath = `${cellarNodePath}/${version}/bin/claude`;
248
+ await addInstallation(claudePath, "npm-homebrew-node");
249
+ }
250
+ } catch {
251
+ }
252
+ }
253
+ }
254
+ }
255
+ return installations;
256
+ }
257
+ async function checkDuplicateInstallations() {
258
+ const installations = await detectAllClaudeCodeInstallations();
259
+ const activeInstallation = installations.find((i) => i.isActive) || null;
260
+ const inactiveInstallations = installations.filter((i) => !i.isActive);
261
+ const homebrewInstallation = installations.find((i) => i.source === "homebrew-cask") || null;
262
+ const npmInstallation = installations.find((i) => i.source === "npm" || i.source === "npm-homebrew-node") || null;
263
+ const hasDuplicates = homebrewInstallation !== null && npmInstallation !== null;
264
+ const recommendation = hasDuplicates ? "remove-npm" : "none";
265
+ return {
266
+ hasDuplicates,
267
+ installations,
268
+ activeInstallation,
269
+ inactiveInstallations,
270
+ homebrewInstallation,
271
+ npmInstallation,
272
+ recommendation
273
+ };
274
+ }
275
+ function getSourceDisplayName(source, i18n) {
276
+ const sourceMap = {
277
+ "homebrew-cask": i18n.t("installation:sourceHomebrewCask"),
278
+ "npm": i18n.t("installation:sourceNpm"),
279
+ "npm-homebrew-node": i18n.t("installation:sourceNpmHomebrewNode"),
280
+ "curl": i18n.t("installation:sourceCurl"),
281
+ "other": i18n.t("installation:sourceOther")
282
+ };
283
+ return sourceMap[source] || source;
284
+ }
285
+ async function performNpmRemovalAndActivateHomebrew(_npmInstallation, homebrewInstallation, tinyExec, i18n, ansis) {
286
+ const ora = (await import('ora')).default;
287
+ const spinner = ora(i18n.t("installation:removingDuplicateInstallation")).start();
288
+ try {
289
+ const { wrapCommandWithSudo } = await import('../chunks/platform.mjs');
290
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["uninstall", "-g", "@anthropic-ai/claude-code"]);
291
+ if (usedSudo) {
292
+ spinner.info(i18n.t("installation:usingSudo"));
293
+ spinner.start();
294
+ }
295
+ await tinyExec(command, args);
296
+ spinner.succeed(i18n.t("installation:duplicateRemoved"));
297
+ if (homebrewInstallation && !homebrewInstallation.isActive) {
298
+ console.log("");
299
+ console.log(ansis.cyan(`\u{1F517} ${i18n.t("installation:activatingHomebrew")}`));
300
+ const { createHomebrewSymlink } = await import('../chunks/init.mjs').then(function (n) { return n.G; });
301
+ const symlinkResult = await createHomebrewSymlink("claude", homebrewInstallation.path);
302
+ if (symlinkResult.success) {
303
+ console.log(ansis.green(`\u2714 ${i18n.t("installation:symlinkCreated", { path: symlinkResult.symlinkPath || "/usr/local/bin/claude" })}`));
304
+ } else {
305
+ console.log(ansis.yellow(`\u26A0 ${i18n.t("installation:manualSymlinkHint")}`));
306
+ if (symlinkResult.error) {
307
+ console.log(ansis.gray(` ${symlinkResult.error}`));
308
+ } else {
309
+ const homebrewBin = nodeFs.existsSync("/opt/homebrew/bin") ? "/opt/homebrew/bin" : "/usr/local/bin";
310
+ console.log(ansis.gray(` sudo ln -sf "${homebrewInstallation.path}" ${homebrewBin}/claude`));
311
+ }
312
+ }
313
+ }
314
+ return { hadDuplicates: true, resolved: true, action: "removed-npm" };
315
+ } catch (error) {
316
+ spinner.fail(i18n.t("installation:duplicateRemovalFailed"));
317
+ if (error instanceof Error) {
318
+ console.error(ansis.gray(error.message));
319
+ }
320
+ return { hadDuplicates: true, resolved: false, action: "kept-both" };
321
+ }
322
+ }
323
+ async function handleDuplicateInstallations(skipPrompt = false) {
324
+ const { ensureI18nInitialized, format, i18n } = await import('../chunks/index2.mjs');
325
+ const ansis = (await import('ansis')).default;
326
+ ensureI18nInitialized();
327
+ const duplicateInfo = await checkDuplicateInstallations();
328
+ if (!duplicateInfo.hasDuplicates) {
329
+ return { hadDuplicates: false, resolved: true, action: "no-duplicates" };
330
+ }
331
+ const { npmInstallation, homebrewInstallation } = duplicateInfo;
332
+ console.log("");
333
+ console.log(ansis.yellow.bold(i18n.t("installation:duplicateInstallationsDetected")));
334
+ console.log(ansis.gray(i18n.t("installation:duplicateInstallationsWarning")));
335
+ console.log("");
336
+ if (homebrewInstallation) {
337
+ const isActive = homebrewInstallation.isActive;
338
+ const statusIcon = isActive ? "\u2705" : "\u26A0\uFE0F";
339
+ const statusColor = isActive ? ansis.green : ansis.yellow;
340
+ console.log(ansis.cyan.bold(`\u{1F37A} Homebrew Cask ${i18n.t("installation:recommendedMethod")}:`));
341
+ console.log(ansis.white(` ${i18n.t("installation:installationSource")}: ${statusColor(getSourceDisplayName(homebrewInstallation.source, i18n))}`));
342
+ console.log(ansis.white(` ${i18n.t("installation:installationPath")}: ${ansis.gray(homebrewInstallation.path)}`));
343
+ if (homebrewInstallation.version) {
344
+ console.log(ansis.white(` ${i18n.t("installation:installationVersion")}: ${ansis.cyan(homebrewInstallation.version)}`));
345
+ }
346
+ console.log(ansis.white(` ${statusIcon} ${isActive ? i18n.t("installation:currentActiveInstallation") : i18n.t("installation:inactiveInstallations")}`));
347
+ console.log("");
348
+ }
349
+ if (npmInstallation) {
350
+ const isActive = npmInstallation.isActive;
351
+ console.log(ansis.yellow.bold(`\u{1F4E6} npm ${i18n.t("installation:notRecommended")}:`));
352
+ console.log(ansis.white(` ${i18n.t("installation:installationSource")}: ${ansis.yellow(getSourceDisplayName(npmInstallation.source, i18n))}`));
353
+ console.log(ansis.white(` ${i18n.t("installation:installationPath")}: ${ansis.gray(npmInstallation.path)}`));
354
+ if (npmInstallation.version) {
355
+ console.log(ansis.white(` ${i18n.t("installation:installationVersion")}: ${ansis.cyan(npmInstallation.version)}`));
356
+ if (homebrewInstallation?.version && npmInstallation.version !== homebrewInstallation.version) {
357
+ console.log(ansis.red(` ${format(i18n.t("installation:versionMismatchWarning"), {
358
+ npmVersion: npmInstallation.version,
359
+ homebrewVersion: homebrewInstallation.version
360
+ })}`));
361
+ }
362
+ }
363
+ if (isActive) {
364
+ console.log(ansis.white(` \u26A0\uFE0F ${i18n.t("installation:currentActiveInstallation")}`));
365
+ }
366
+ console.log("");
367
+ }
368
+ console.log(ansis.cyan(`\u{1F4A1} ${i18n.t("installation:recommendRemoveNpm")}`));
369
+ console.log("");
370
+ if (!npmInstallation) {
371
+ return { hadDuplicates: true, resolved: false, action: "kept-both" };
372
+ }
373
+ const { exec: tinyExec } = await import('tinyexec');
374
+ if (skipPrompt) {
375
+ console.log(ansis.cyan(`\u{1F504} ${i18n.t("installation:autoRemovingNpm")}`));
376
+ return await performNpmRemovalAndActivateHomebrew(
377
+ npmInstallation,
378
+ homebrewInstallation,
379
+ tinyExec,
380
+ i18n,
381
+ ansis
382
+ );
383
+ }
384
+ const inquirer = (await import('inquirer')).default;
385
+ const sourceDisplayName = getSourceDisplayName(npmInstallation.source, i18n);
386
+ const confirmMessage = format(i18n.t("installation:confirmRemoveDuplicate"), { source: sourceDisplayName });
387
+ const { action } = await inquirer.prompt([
388
+ {
389
+ type: "list",
390
+ name: "action",
391
+ message: confirmMessage,
392
+ choices: [
393
+ {
394
+ name: `\u2705 ${i18n.t("common:yes")} - ${i18n.t("installation:removingDuplicateInstallation")}`,
395
+ value: "remove"
396
+ },
397
+ {
398
+ name: `\u274C ${i18n.t("installation:keepBothInstallations")}`,
399
+ value: "keep"
400
+ }
401
+ ]
402
+ }
403
+ ]);
404
+ if (action === "keep") {
405
+ console.log(ansis.gray(i18n.t("installation:duplicateWarningContinue")));
406
+ return { hadDuplicates: true, resolved: false, action: "kept-both" };
407
+ }
408
+ return await performNpmRemovalAndActivateHomebrew(
409
+ npmInstallation,
410
+ homebrewInstallation,
411
+ tinyExec,
412
+ i18n,
413
+ ansis
414
+ );
415
+ }
416
+ async function getHomebrewClaudeCodeVersion() {
417
+ try {
418
+ const { stdout } = await execAsync("brew info --cask claude-code --json=v2");
419
+ const info = JSON.parse(stdout);
420
+ if (info.casks && info.casks.length > 0) {
421
+ return info.casks[0].version;
422
+ }
423
+ return null;
424
+ } catch {
425
+ return null;
426
+ }
427
+ }
428
+ function compareVersions(current, latest) {
429
+ if (!semver.valid(current) || !semver.valid(latest)) {
430
+ return -1;
431
+ }
432
+ return semver.compare(current, latest);
433
+ }
434
+ function shouldUpdate(current, latest) {
435
+ return compareVersions(current, latest) < 0;
436
+ }
437
+ async function checkCcrVersion() {
438
+ const currentVersion = await getInstalledVersion("ccr");
439
+ const latestVersion = await getLatestVersion("@musistudio/claude-code-router");
440
+ return {
441
+ installed: currentVersion !== null,
442
+ currentVersion,
443
+ latestVersion,
444
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
445
+ };
446
+ }
447
+ async function checkClaudeCodeVersion() {
448
+ let currentVersion = await getInstalledVersion("claude");
449
+ const initialGetVersionSuccess = currentVersion !== null;
450
+ let installationInfo = await getClaudeCodeInstallationSource();
451
+ if (!currentVersion) {
452
+ const installations = await detectAllClaudeCodeInstallations();
453
+ const bestInstall = installations.find((i) => i.source === "homebrew-cask") || installations.find((i) => i.source === "npm") || installations[0];
454
+ if (bestInstall && bestInstall.version) {
455
+ currentVersion = bestInstall.version;
456
+ installationInfo = {
457
+ isHomebrew: bestInstall.source === "homebrew-cask",
458
+ commandPath: bestInstall.path,
459
+ source: bestInstall.source === "npm" ? "npm" : bestInstall.source === "homebrew-cask" ? "homebrew-cask" : "other"
460
+ };
461
+ }
462
+ }
463
+ const { isHomebrew, commandPath, source: installationSource } = installationInfo;
464
+ let latestVersion;
465
+ if (isHomebrew) {
466
+ latestVersion = await getHomebrewClaudeCodeVersion();
467
+ } else {
468
+ latestVersion = await getLatestVersion("@anthropic-ai/claude-code");
469
+ }
470
+ const needsUpdate = (currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false) || !initialGetVersionSuccess && currentVersion !== null;
471
+ return {
472
+ installed: currentVersion !== null,
473
+ currentVersion,
474
+ latestVersion,
475
+ needsUpdate,
476
+ isHomebrew,
477
+ commandPath,
478
+ installationSource,
479
+ isBroken: !initialGetVersionSuccess && currentVersion !== null
480
+ };
481
+ }
482
+ async function checkCometixLineVersion() {
483
+ const currentVersion = await getInstalledVersion("ccline");
484
+ const latestVersion = await getLatestVersion("@cometix/ccline");
485
+ return {
486
+ installed: currentVersion !== null,
487
+ currentVersion,
488
+ latestVersion,
489
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
490
+ };
491
+ }
492
+ async function checkClaudeCodeVersionAndPrompt(skipPrompt = false) {
493
+ try {
494
+ const versionInfo = await checkClaudeCodeVersion();
495
+ if (!versionInfo.needsUpdate) {
496
+ return;
497
+ }
498
+ const { updateClaudeCode } = await import('../chunks/auto-updater.mjs');
499
+ await updateClaudeCode(false, skipPrompt);
500
+ } catch (error) {
501
+ const errorMessage = error instanceof Error ? error.message : String(error);
502
+ console.warn(`Claude Code version check failed: ${errorMessage}`);
503
+ }
504
+ }
505
+
506
+ export { checkClaudeCodeVersion as a, checkCcrVersion as b, checkClaudeCodeVersionAndPrompt as c, checkCometixLineVersion as d, handleDuplicateInstallations as h };
@@ -0,0 +1,34 @@
1
+ import { pathExists } from 'fs-extra';
2
+ import trash from 'trash';
3
+
4
+ async function moveToTrash(paths) {
5
+ const pathArray = Array.isArray(paths) ? paths : [paths];
6
+ const results = [];
7
+ for (const path of pathArray) {
8
+ try {
9
+ const exists = await pathExists(path);
10
+ if (!exists) {
11
+ results.push({
12
+ success: false,
13
+ path,
14
+ error: "Path does not exist"
15
+ });
16
+ continue;
17
+ }
18
+ await trash(path);
19
+ results.push({
20
+ success: true,
21
+ path
22
+ });
23
+ } catch (error) {
24
+ results.push({
25
+ success: false,
26
+ path,
27
+ error: error.message || "Unknown error occurred"
28
+ });
29
+ }
30
+ }
31
+ return results;
32
+ }
33
+
34
+ export { moveToTrash as m };