ccjk 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/auto-bootstrap.mjs +358 -0
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/silent-updater.mjs +396 -0
- package/dist/cli.mjs +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { release, platform, type, homedir, hostname } from 'node:os';
|
|
4
|
+
import { join } from 'pathe';
|
|
5
|
+
import { CCJK_CONFIG_DIR } from './constants.mjs';
|
|
6
|
+
import './index2.mjs';
|
|
7
|
+
import 'node:process';
|
|
8
|
+
import 'node:url';
|
|
9
|
+
import 'i18next';
|
|
10
|
+
import 'i18next-fs-backend';
|
|
11
|
+
|
|
12
|
+
const CLOUD_CONFIG_DIR = join(CCJK_CONFIG_DIR, "cloud");
|
|
13
|
+
const DEVICE_CONFIG_FILE = join(CLOUD_CONFIG_DIR, "device.json");
|
|
14
|
+
const CLOUD_STATE_FILE = join(CLOUD_CONFIG_DIR, "state.json");
|
|
15
|
+
const CLOUD_API_ENDPOINT = "https://api.ccjk.dev/v1";
|
|
16
|
+
const AUTO_SYNC_INTERVAL = 30 * 60 * 1e3;
|
|
17
|
+
const AUTO_UPGRADE_CHECK_INTERVAL = 6 * 60 * 60 * 1e3;
|
|
18
|
+
function generateDeviceFingerprint() {
|
|
19
|
+
const data = [
|
|
20
|
+
platform(),
|
|
21
|
+
type(),
|
|
22
|
+
homedir().split("/").length.toString(),
|
|
23
|
+
// 只用路径深度,不用实际路径
|
|
24
|
+
hostname().length.toString()
|
|
25
|
+
// 只用主机名长度,不用实际名称
|
|
26
|
+
].join("|");
|
|
27
|
+
return createHash("sha256").update(data).digest("hex").substring(0, 32);
|
|
28
|
+
}
|
|
29
|
+
function getOrCreateDeviceInfo() {
|
|
30
|
+
ensureCloudConfigDir();
|
|
31
|
+
if (existsSync(DEVICE_CONFIG_FILE)) {
|
|
32
|
+
try {
|
|
33
|
+
const data = readFileSync(DEVICE_CONFIG_FILE, "utf-8");
|
|
34
|
+
const device2 = JSON.parse(data);
|
|
35
|
+
device2.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
36
|
+
writeFileSync(DEVICE_CONFIG_FILE, JSON.stringify(device2, null, 2));
|
|
37
|
+
return device2;
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const device = {
|
|
42
|
+
deviceId: randomUUID(),
|
|
43
|
+
fingerprint: generateDeviceFingerprint(),
|
|
44
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46
|
+
osType: platform(),
|
|
47
|
+
osVersion: release(),
|
|
48
|
+
ccjkVersion: getCcjkVersion()
|
|
49
|
+
};
|
|
50
|
+
writeFileSync(DEVICE_CONFIG_FILE, JSON.stringify(device, null, 2));
|
|
51
|
+
return device;
|
|
52
|
+
}
|
|
53
|
+
function getCcjkVersion() {
|
|
54
|
+
try {
|
|
55
|
+
const packagePath = join(__dirname, "../../../package.json");
|
|
56
|
+
if (existsSync(packagePath)) {
|
|
57
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
58
|
+
return pkg.version || "unknown";
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
return "unknown";
|
|
63
|
+
}
|
|
64
|
+
function ensureCloudConfigDir() {
|
|
65
|
+
if (!existsSync(CLOUD_CONFIG_DIR)) {
|
|
66
|
+
mkdirSync(CLOUD_CONFIG_DIR, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getCloudState() {
|
|
70
|
+
ensureCloudConfigDir();
|
|
71
|
+
if (existsSync(CLOUD_STATE_FILE)) {
|
|
72
|
+
try {
|
|
73
|
+
const data = readFileSync(CLOUD_STATE_FILE, "utf-8");
|
|
74
|
+
return JSON.parse(data);
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
initialized: false,
|
|
80
|
+
autoSyncEnabled: true,
|
|
81
|
+
silentUpgradeEnabled: true,
|
|
82
|
+
lastSyncAt: null,
|
|
83
|
+
lastUpgradeCheckAt: null,
|
|
84
|
+
lastUpgradedAt: null,
|
|
85
|
+
syncStats: {
|
|
86
|
+
totalSyncs: 0,
|
|
87
|
+
successfulSyncs: 0,
|
|
88
|
+
failedSyncs: 0
|
|
89
|
+
},
|
|
90
|
+
upgradeStats: {
|
|
91
|
+
totalChecks: 0,
|
|
92
|
+
upgradesApplied: 0,
|
|
93
|
+
upgradesFailed: 0
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function saveCloudState(state) {
|
|
98
|
+
ensureCloudConfigDir();
|
|
99
|
+
writeFileSync(CLOUD_STATE_FILE, JSON.stringify(state, null, 2));
|
|
100
|
+
}
|
|
101
|
+
function updateCloudState(updates) {
|
|
102
|
+
const state = getCloudState();
|
|
103
|
+
const newState = { ...state, ...updates };
|
|
104
|
+
saveCloudState(newState);
|
|
105
|
+
return newState;
|
|
106
|
+
}
|
|
107
|
+
async function autoBootstrap() {
|
|
108
|
+
try {
|
|
109
|
+
const state = getCloudState();
|
|
110
|
+
if (!state.initialized) {
|
|
111
|
+
await initializeCloudServices();
|
|
112
|
+
}
|
|
113
|
+
await performHandshake();
|
|
114
|
+
if (state.silentUpgradeEnabled) {
|
|
115
|
+
await checkAndPerformSilentUpgrade();
|
|
116
|
+
}
|
|
117
|
+
if (state.autoSyncEnabled) {
|
|
118
|
+
await performAutoSync();
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function initializeCloudServices() {
|
|
124
|
+
const device = getOrCreateDeviceInfo();
|
|
125
|
+
updateCloudState({
|
|
126
|
+
initialized: true,
|
|
127
|
+
autoSyncEnabled: true,
|
|
128
|
+
silentUpgradeEnabled: true
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
await registerDevice(device);
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function registerDevice(device) {
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
138
|
+
try {
|
|
139
|
+
await fetch(`${CLOUD_API_ENDPOINT}/devices/register`, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Type": "application/json",
|
|
143
|
+
"User-Agent": `CCJK/${device.ccjkVersion}`
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
deviceId: device.deviceId,
|
|
147
|
+
fingerprint: device.fingerprint,
|
|
148
|
+
osType: device.osType,
|
|
149
|
+
osVersion: device.osVersion,
|
|
150
|
+
ccjkVersion: device.ccjkVersion
|
|
151
|
+
}),
|
|
152
|
+
signal: controller.signal
|
|
153
|
+
});
|
|
154
|
+
} finally {
|
|
155
|
+
clearTimeout(timeoutId);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function performHandshake() {
|
|
159
|
+
const device = getOrCreateDeviceInfo();
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetch(`${CLOUD_API_ENDPOINT}/handshake`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
"User-Agent": `CCJK/${device.ccjkVersion}`,
|
|
168
|
+
"X-Device-ID": device.deviceId
|
|
169
|
+
},
|
|
170
|
+
body: JSON.stringify({
|
|
171
|
+
deviceId: device.deviceId,
|
|
172
|
+
ccjkVersion: device.ccjkVersion
|
|
173
|
+
}),
|
|
174
|
+
signal: controller.signal
|
|
175
|
+
});
|
|
176
|
+
if (response.ok) {
|
|
177
|
+
return await response.json();
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
} finally {
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
async function checkAndPerformSilentUpgrade() {
|
|
186
|
+
const state = getCloudState();
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
if (state.lastUpgradeCheckAt) {
|
|
189
|
+
const lastCheck = new Date(state.lastUpgradeCheckAt).getTime();
|
|
190
|
+
if (now - lastCheck < AUTO_UPGRADE_CHECK_INTERVAL) {
|
|
191
|
+
return { success: true, upgraded: false };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
updateCloudState({
|
|
195
|
+
lastUpgradeCheckAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
196
|
+
upgradeStats: {
|
|
197
|
+
...state.upgradeStats,
|
|
198
|
+
totalChecks: state.upgradeStats.totalChecks + 1
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
const updateInfo = await checkForUpdates();
|
|
203
|
+
if (updateInfo.hasUpdate) {
|
|
204
|
+
const result = await performSilentUpgrade(updateInfo.latestVersion);
|
|
205
|
+
if (result.success && result.upgraded) {
|
|
206
|
+
updateCloudState({
|
|
207
|
+
lastUpgradedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
208
|
+
upgradeStats: {
|
|
209
|
+
...getCloudState().upgradeStats,
|
|
210
|
+
upgradesApplied: getCloudState().upgradeStats.upgradesApplied + 1
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
return { success: true, upgraded: false };
|
|
217
|
+
} catch (error) {
|
|
218
|
+
updateCloudState({
|
|
219
|
+
upgradeStats: {
|
|
220
|
+
...getCloudState().upgradeStats,
|
|
221
|
+
upgradesFailed: getCloudState().upgradeStats.upgradesFailed + 1
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
upgraded: false,
|
|
227
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function checkForUpdates() {
|
|
232
|
+
const currentVersion = getCcjkVersion();
|
|
233
|
+
const controller = new AbortController();
|
|
234
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
235
|
+
try {
|
|
236
|
+
const response = await fetch("https://registry.npmjs.org/ccjk/latest", {
|
|
237
|
+
signal: controller.signal
|
|
238
|
+
});
|
|
239
|
+
if (response.ok) {
|
|
240
|
+
const data = await response.json();
|
|
241
|
+
const latestVersion = data.version;
|
|
242
|
+
return {
|
|
243
|
+
hasUpdate: isNewerVersion(latestVersion, currentVersion),
|
|
244
|
+
latestVersion,
|
|
245
|
+
currentVersion
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
} finally {
|
|
250
|
+
clearTimeout(timeoutId);
|
|
251
|
+
}
|
|
252
|
+
return { hasUpdate: false, latestVersion: currentVersion, currentVersion };
|
|
253
|
+
}
|
|
254
|
+
function isNewerVersion(latest, current) {
|
|
255
|
+
const latestParts = latest.split(".").map(Number);
|
|
256
|
+
const currentParts = current.split(".").map(Number);
|
|
257
|
+
for (let i = 0; i < 3; i++) {
|
|
258
|
+
const l = latestParts[i] || 0;
|
|
259
|
+
const c = currentParts[i] || 0;
|
|
260
|
+
if (l > c)
|
|
261
|
+
return true;
|
|
262
|
+
if (l < c)
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
async function performSilentUpgrade(targetVersion) {
|
|
268
|
+
const currentVersion = getCcjkVersion();
|
|
269
|
+
try {
|
|
270
|
+
const { exec } = await import('tinyexec');
|
|
271
|
+
const result = await exec("npm", ["update", "-g", "ccjk"], {
|
|
272
|
+
timeout: 6e4
|
|
273
|
+
// 60 秒超时
|
|
274
|
+
});
|
|
275
|
+
if (result.exitCode === 0) {
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
upgraded: true,
|
|
279
|
+
fromVersion: currentVersion,
|
|
280
|
+
toVersion: targetVersion
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
upgraded: false,
|
|
286
|
+
error: result.stderr || "Upgrade failed"
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
upgraded: false,
|
|
292
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function performAutoSync() {
|
|
297
|
+
const state = getCloudState();
|
|
298
|
+
const now = Date.now();
|
|
299
|
+
if (state.lastSyncAt) {
|
|
300
|
+
const lastSync = new Date(state.lastSyncAt).getTime();
|
|
301
|
+
if (now - lastSync < AUTO_SYNC_INTERVAL) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
await syncToCloud();
|
|
307
|
+
updateCloudState({
|
|
308
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
309
|
+
syncStats: {
|
|
310
|
+
...state.syncStats,
|
|
311
|
+
totalSyncs: state.syncStats.totalSyncs + 1,
|
|
312
|
+
successfulSyncs: state.syncStats.successfulSyncs + 1
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
} catch {
|
|
316
|
+
updateCloudState({
|
|
317
|
+
syncStats: {
|
|
318
|
+
...state.syncStats,
|
|
319
|
+
totalSyncs: state.syncStats.totalSyncs + 1,
|
|
320
|
+
failedSyncs: state.syncStats.failedSyncs + 1
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function syncToCloud() {
|
|
326
|
+
const device = getOrCreateDeviceInfo();
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
329
|
+
try {
|
|
330
|
+
const syncData = await collectSyncData();
|
|
331
|
+
await fetch(`${CLOUD_API_ENDPOINT}/sync`, {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers: {
|
|
334
|
+
"Content-Type": "application/json",
|
|
335
|
+
"User-Agent": `CCJK/${device.ccjkVersion}`,
|
|
336
|
+
"X-Device-ID": device.deviceId
|
|
337
|
+
},
|
|
338
|
+
body: JSON.stringify(syncData),
|
|
339
|
+
signal: controller.signal
|
|
340
|
+
});
|
|
341
|
+
} finally {
|
|
342
|
+
clearTimeout(timeoutId);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function collectSyncData() {
|
|
346
|
+
const device = getOrCreateDeviceInfo();
|
|
347
|
+
return {
|
|
348
|
+
deviceId: device.deviceId,
|
|
349
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
350
|
+
// 只同步匿名化的使用统计
|
|
351
|
+
stats: {
|
|
352
|
+
osType: device.osType,
|
|
353
|
+
ccjkVersion: device.ccjkVersion
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export { AUTO_SYNC_INTERVAL, AUTO_UPGRADE_CHECK_INTERVAL, CLOUD_API_ENDPOINT, CLOUD_CONFIG_DIR, CLOUD_STATE_FILE, DEVICE_CONFIG_FILE, autoBootstrap, autoBootstrap as bootstrap, checkAndPerformSilentUpgrade as checkUpgrade, getCloudState, getOrCreateDeviceInfo, saveCloudState, performAutoSync as sync, updateCloudState };
|
package/dist/chunks/package.mjs
CHANGED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
import process__default from 'node:process';
|
|
4
|
+
import { join } from 'pathe';
|
|
5
|
+
import { CCJK_CONFIG_DIR } from './constants.mjs';
|
|
6
|
+
import { getCloudState, updateCloudState } from './auto-bootstrap.mjs';
|
|
7
|
+
import './index2.mjs';
|
|
8
|
+
import 'node:url';
|
|
9
|
+
import 'i18next';
|
|
10
|
+
import 'i18next-fs-backend';
|
|
11
|
+
import 'node:crypto';
|
|
12
|
+
|
|
13
|
+
const UPGRADE_LOG_DIR = join(CCJK_CONFIG_DIR, "cloud", "logs");
|
|
14
|
+
const UPGRADE_LOG_FILE = join(UPGRADE_LOG_DIR, "upgrades.log");
|
|
15
|
+
const UPGRADE_LOCK_FILE = join(CCJK_CONFIG_DIR, "cloud", ".upgrade.lock");
|
|
16
|
+
const UPGRADE_CHECK_INTERVAL = 6 * 60 * 60 * 1e3;
|
|
17
|
+
const UPGRADE_TIMEOUT = 5 * 60 * 1e3;
|
|
18
|
+
async function checkAllToolVersions() {
|
|
19
|
+
const results = [];
|
|
20
|
+
const [ccjk, claudeCode, ccr] = await Promise.all([
|
|
21
|
+
checkCcjkVersion(),
|
|
22
|
+
checkClaudeCodeVersion(),
|
|
23
|
+
checkCcrVersion()
|
|
24
|
+
]);
|
|
25
|
+
results.push(ccjk, claudeCode, ccr);
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
async function checkCcjkVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const currentVersion = getCurrentCcjkVersion();
|
|
31
|
+
const latestVersion = await fetchLatestNpmVersion("ccjk");
|
|
32
|
+
return {
|
|
33
|
+
tool: "ccjk",
|
|
34
|
+
installed: true,
|
|
35
|
+
currentVersion,
|
|
36
|
+
latestVersion,
|
|
37
|
+
needsUpdate: latestVersion ? isNewerVersion(latestVersion, currentVersion) : false,
|
|
38
|
+
installMethod: "npm"
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
tool: "ccjk",
|
|
43
|
+
installed: true,
|
|
44
|
+
currentVersion: getCurrentCcjkVersion(),
|
|
45
|
+
latestVersion: null,
|
|
46
|
+
needsUpdate: false,
|
|
47
|
+
installMethod: "npm"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function checkClaudeCodeVersion() {
|
|
52
|
+
try {
|
|
53
|
+
const { exec } = await import('tinyexec');
|
|
54
|
+
const result = await exec("claude", ["--version"], { timeout: 5e3 });
|
|
55
|
+
if (result.exitCode !== 0) {
|
|
56
|
+
return {
|
|
57
|
+
tool: "claude-code",
|
|
58
|
+
installed: false,
|
|
59
|
+
currentVersion: null,
|
|
60
|
+
latestVersion: null,
|
|
61
|
+
needsUpdate: false,
|
|
62
|
+
installMethod: "unknown"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const currentVersion = result.stdout.trim().replace(/^v/, "");
|
|
66
|
+
const latestVersion = await fetchLatestNpmVersion("@anthropic-ai/claude-code");
|
|
67
|
+
const installMethod = await detectClaudeCodeInstallMethod();
|
|
68
|
+
return {
|
|
69
|
+
tool: "claude-code",
|
|
70
|
+
installed: true,
|
|
71
|
+
currentVersion,
|
|
72
|
+
latestVersion,
|
|
73
|
+
needsUpdate: latestVersion ? isNewerVersion(latestVersion, currentVersion) : false,
|
|
74
|
+
installMethod
|
|
75
|
+
};
|
|
76
|
+
} catch {
|
|
77
|
+
return {
|
|
78
|
+
tool: "claude-code",
|
|
79
|
+
installed: false,
|
|
80
|
+
currentVersion: null,
|
|
81
|
+
latestVersion: null,
|
|
82
|
+
needsUpdate: false,
|
|
83
|
+
installMethod: "unknown"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function checkCcrVersion() {
|
|
88
|
+
try {
|
|
89
|
+
const { exec } = await import('tinyexec');
|
|
90
|
+
const result = await exec("ccr", ["--version"], { timeout: 5e3 });
|
|
91
|
+
if (result.exitCode !== 0) {
|
|
92
|
+
return {
|
|
93
|
+
tool: "ccr",
|
|
94
|
+
installed: false,
|
|
95
|
+
currentVersion: null,
|
|
96
|
+
latestVersion: null,
|
|
97
|
+
needsUpdate: false,
|
|
98
|
+
installMethod: "unknown"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const currentVersion = result.stdout.trim().replace(/^v/, "");
|
|
102
|
+
const latestVersion = await fetchLatestNpmVersion("@musistudio/claude-code-router");
|
|
103
|
+
return {
|
|
104
|
+
tool: "ccr",
|
|
105
|
+
installed: true,
|
|
106
|
+
currentVersion,
|
|
107
|
+
latestVersion,
|
|
108
|
+
needsUpdate: latestVersion ? isNewerVersion(latestVersion, currentVersion) : false,
|
|
109
|
+
installMethod: "npm"
|
|
110
|
+
};
|
|
111
|
+
} catch {
|
|
112
|
+
return {
|
|
113
|
+
tool: "ccr",
|
|
114
|
+
installed: false,
|
|
115
|
+
currentVersion: null,
|
|
116
|
+
latestVersion: null,
|
|
117
|
+
needsUpdate: false,
|
|
118
|
+
installMethod: "unknown"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function getCurrentCcjkVersion() {
|
|
123
|
+
try {
|
|
124
|
+
const packagePath = join(__dirname, "../../../package.json");
|
|
125
|
+
if (existsSync(packagePath)) {
|
|
126
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
127
|
+
return pkg.version || "unknown";
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
return "unknown";
|
|
132
|
+
}
|
|
133
|
+
async function fetchLatestNpmVersion(packageName) {
|
|
134
|
+
const controller = new AbortController();
|
|
135
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
138
|
+
signal: controller.signal
|
|
139
|
+
});
|
|
140
|
+
if (response.ok) {
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
return data.version;
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
} finally {
|
|
146
|
+
clearTimeout(timeoutId);
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
async function detectClaudeCodeInstallMethod() {
|
|
151
|
+
try {
|
|
152
|
+
const { exec } = await import('tinyexec');
|
|
153
|
+
if (platform() === "darwin") {
|
|
154
|
+
const brewResult = await exec("brew", ["list", "--cask", "claude-code"], { timeout: 5e3 });
|
|
155
|
+
if (brewResult.exitCode === 0) {
|
|
156
|
+
return "homebrew";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const npmResult = await exec("npm", ["list", "-g", "@anthropic-ai/claude-code"], { timeout: 5e3 });
|
|
160
|
+
if (npmResult.exitCode === 0 && npmResult.stdout.includes("@anthropic-ai/claude-code")) {
|
|
161
|
+
return "npm";
|
|
162
|
+
}
|
|
163
|
+
return "curl";
|
|
164
|
+
} catch {
|
|
165
|
+
return "unknown";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function isNewerVersion(latest, current) {
|
|
169
|
+
if (!latest || !current || current === "unknown")
|
|
170
|
+
return false;
|
|
171
|
+
const latestParts = latest.split(".").map(Number);
|
|
172
|
+
const currentParts = current.split(".").map(Number);
|
|
173
|
+
for (let i = 0; i < 3; i++) {
|
|
174
|
+
const l = latestParts[i] || 0;
|
|
175
|
+
const c = currentParts[i] || 0;
|
|
176
|
+
if (l > c)
|
|
177
|
+
return true;
|
|
178
|
+
if (l < c)
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
async function performSilentUpgradeAll() {
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
const results = [];
|
|
186
|
+
if (isUpgradeLocked()) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
results: [],
|
|
190
|
+
totalDuration: 0,
|
|
191
|
+
upgradedCount: 0,
|
|
192
|
+
failedCount: 0
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
createUpgradeLock();
|
|
197
|
+
const versions = await checkAllToolVersions();
|
|
198
|
+
const toolsToUpgrade = versions.filter((v) => v.needsUpdate && v.installed);
|
|
199
|
+
for (const tool of toolsToUpgrade) {
|
|
200
|
+
const result = await upgradeTool(tool);
|
|
201
|
+
results.push(result);
|
|
202
|
+
logUpgrade({
|
|
203
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
204
|
+
tool: tool.tool,
|
|
205
|
+
fromVersion: tool.currentVersion || "unknown",
|
|
206
|
+
toVersion: tool.latestVersion || "unknown",
|
|
207
|
+
success: result.success,
|
|
208
|
+
error: result.error,
|
|
209
|
+
duration: result.duration
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const totalDuration = Date.now() - startTime;
|
|
213
|
+
const upgradedCount = results.filter((r) => r.upgraded).length;
|
|
214
|
+
const failedCount = results.filter((r) => !r.success).length;
|
|
215
|
+
const state = getCloudState();
|
|
216
|
+
updateCloudState({
|
|
217
|
+
lastUpgradeCheckAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
218
|
+
upgradeStats: {
|
|
219
|
+
totalChecks: state.upgradeStats.totalChecks + 1,
|
|
220
|
+
upgradesApplied: state.upgradeStats.upgradesApplied + upgradedCount,
|
|
221
|
+
upgradesFailed: state.upgradeStats.upgradesFailed + failedCount
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
success: failedCount === 0,
|
|
226
|
+
results,
|
|
227
|
+
totalDuration,
|
|
228
|
+
upgradedCount,
|
|
229
|
+
failedCount
|
|
230
|
+
};
|
|
231
|
+
} finally {
|
|
232
|
+
releaseUpgradeLock();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function upgradeTool(info) {
|
|
236
|
+
const startTime = Date.now();
|
|
237
|
+
try {
|
|
238
|
+
switch (info.tool) {
|
|
239
|
+
case "ccjk":
|
|
240
|
+
return await upgradeCcjk(info, startTime);
|
|
241
|
+
case "claude-code":
|
|
242
|
+
return await upgradeClaudeCode(info, startTime);
|
|
243
|
+
case "ccr":
|
|
244
|
+
return await upgradeCcr(info, startTime);
|
|
245
|
+
default:
|
|
246
|
+
return {
|
|
247
|
+
tool: info.tool,
|
|
248
|
+
success: false,
|
|
249
|
+
upgraded: false,
|
|
250
|
+
error: "Unknown tool",
|
|
251
|
+
duration: Date.now() - startTime
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
tool: info.tool,
|
|
257
|
+
success: false,
|
|
258
|
+
upgraded: false,
|
|
259
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
260
|
+
duration: Date.now() - startTime
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function upgradeCcjk(info, startTime) {
|
|
265
|
+
const { exec } = await import('tinyexec');
|
|
266
|
+
const result = await exec("npm", ["update", "-g", "ccjk"], {
|
|
267
|
+
timeout: UPGRADE_TIMEOUT
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
tool: "ccjk",
|
|
271
|
+
success: result.exitCode === 0,
|
|
272
|
+
upgraded: result.exitCode === 0,
|
|
273
|
+
fromVersion: info.currentVersion || void 0,
|
|
274
|
+
toVersion: info.latestVersion || void 0,
|
|
275
|
+
error: result.exitCode !== 0 ? result.stderr : void 0,
|
|
276
|
+
duration: Date.now() - startTime
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async function upgradeClaudeCode(info, startTime) {
|
|
280
|
+
const { exec } = await import('tinyexec');
|
|
281
|
+
let result;
|
|
282
|
+
switch (info.installMethod) {
|
|
283
|
+
case "homebrew":
|
|
284
|
+
result = await exec("brew", ["upgrade", "--cask", "claude-code"], {
|
|
285
|
+
timeout: UPGRADE_TIMEOUT
|
|
286
|
+
});
|
|
287
|
+
break;
|
|
288
|
+
case "npm":
|
|
289
|
+
result = await exec("npm", ["update", "-g", "@anthropic-ai/claude-code"], {
|
|
290
|
+
timeout: UPGRADE_TIMEOUT
|
|
291
|
+
});
|
|
292
|
+
break;
|
|
293
|
+
case "curl":
|
|
294
|
+
default:
|
|
295
|
+
result = await exec("claude", ["update"], {
|
|
296
|
+
timeout: UPGRADE_TIMEOUT
|
|
297
|
+
});
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
tool: "claude-code",
|
|
302
|
+
success: result.exitCode === 0,
|
|
303
|
+
upgraded: result.exitCode === 0,
|
|
304
|
+
fromVersion: info.currentVersion || void 0,
|
|
305
|
+
toVersion: info.latestVersion || void 0,
|
|
306
|
+
error: result.exitCode !== 0 ? result.stderr : void 0,
|
|
307
|
+
duration: Date.now() - startTime
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
async function upgradeCcr(info, startTime) {
|
|
311
|
+
const { exec } = await import('tinyexec');
|
|
312
|
+
const result = await exec("npm", ["update", "-g", "@musistudio/claude-code-router"], {
|
|
313
|
+
timeout: UPGRADE_TIMEOUT
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
tool: "ccr",
|
|
317
|
+
success: result.exitCode === 0,
|
|
318
|
+
upgraded: result.exitCode === 0,
|
|
319
|
+
fromVersion: info.currentVersion || void 0,
|
|
320
|
+
toVersion: info.latestVersion || void 0,
|
|
321
|
+
error: result.exitCode !== 0 ? result.stderr : void 0,
|
|
322
|
+
duration: Date.now() - startTime
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function isUpgradeLocked() {
|
|
326
|
+
if (!existsSync(UPGRADE_LOCK_FILE)) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const lockData = JSON.parse(readFileSync(UPGRADE_LOCK_FILE, "utf-8"));
|
|
331
|
+
const lockTime = new Date(lockData.timestamp).getTime();
|
|
332
|
+
const now = Date.now();
|
|
333
|
+
if (now - lockTime > 10 * 60 * 1e3) {
|
|
334
|
+
releaseUpgradeLock();
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
} catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function createUpgradeLock() {
|
|
343
|
+
ensureLogDir();
|
|
344
|
+
writeFileSync(UPGRADE_LOCK_FILE, JSON.stringify({
|
|
345
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
346
|
+
pid: process__default.pid
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
function releaseUpgradeLock() {
|
|
350
|
+
try {
|
|
351
|
+
if (existsSync(UPGRADE_LOCK_FILE)) {
|
|
352
|
+
unlinkSync(UPGRADE_LOCK_FILE);
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function ensureLogDir() {
|
|
358
|
+
if (!existsSync(UPGRADE_LOG_DIR)) {
|
|
359
|
+
mkdirSync(UPGRADE_LOG_DIR, { recursive: true });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function logUpgrade(entry) {
|
|
363
|
+
ensureLogDir();
|
|
364
|
+
const logLine = [
|
|
365
|
+
entry.timestamp,
|
|
366
|
+
entry.tool,
|
|
367
|
+
entry.fromVersion,
|
|
368
|
+
"->",
|
|
369
|
+
entry.toVersion,
|
|
370
|
+
entry.success ? "SUCCESS" : "FAILED",
|
|
371
|
+
entry.error || "",
|
|
372
|
+
`${entry.duration}ms`
|
|
373
|
+
].join(" | ");
|
|
374
|
+
appendFileSync(UPGRADE_LOG_FILE, `${logLine}
|
|
375
|
+
`);
|
|
376
|
+
}
|
|
377
|
+
function shouldCheckForUpgrades() {
|
|
378
|
+
const state = getCloudState();
|
|
379
|
+
if (!state.silentUpgradeEnabled) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
if (!state.lastUpgradeCheckAt) {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
const lastCheck = new Date(state.lastUpgradeCheckAt).getTime();
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
return now - lastCheck >= UPGRADE_CHECK_INTERVAL;
|
|
388
|
+
}
|
|
389
|
+
async function checkAndUpgradeIfNeeded() {
|
|
390
|
+
if (!shouldCheckForUpgrades()) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
return performSilentUpgradeAll();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export { UPGRADE_CHECK_INTERVAL, UPGRADE_LOCK_FILE, UPGRADE_LOG_DIR, UPGRADE_LOG_FILE, UPGRADE_TIMEOUT, checkAndUpgradeIfNeeded as autoUpgrade, checkAllToolVersions, checkAndUpgradeIfNeeded, checkAllToolVersions as checkVersions, performSilentUpgradeAll, shouldCheckForUpgrades, performSilentUpgradeAll as upgradeAll };
|
package/dist/cli.mjs
CHANGED
|
@@ -877,10 +877,22 @@ function customizeHelpLazy(_sections, version) {
|
|
|
877
877
|
return newSections;
|
|
878
878
|
}
|
|
879
879
|
async function runLazyCli() {
|
|
880
|
+
bootstrapCloudServices();
|
|
880
881
|
const cac = (await import('cac')).default;
|
|
881
882
|
const cli = cac("ccjk");
|
|
882
883
|
await setupCommandsLazy(cli);
|
|
883
884
|
cli.parse();
|
|
884
885
|
}
|
|
886
|
+
function bootstrapCloudServices() {
|
|
887
|
+
setImmediate(async () => {
|
|
888
|
+
try {
|
|
889
|
+
const { autoBootstrap } = await import('./chunks/auto-bootstrap.mjs');
|
|
890
|
+
await autoBootstrap();
|
|
891
|
+
const { autoUpgrade } = await import('./chunks/silent-updater.mjs');
|
|
892
|
+
await autoUpgrade();
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
885
897
|
|
|
886
898
|
runLazyCli().catch(console.error);
|
package/package.json
CHANGED