ccjk 11.1.0 → 11.1.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.
- package/README.md +6 -0
- package/README.zh-CN.md +3 -0
- package/dist/chunks/notification.mjs +9 -494
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/remote.mjs +346 -27
- package/dist/cli.mjs +23 -2
- package/dist/i18n/locales/en/notification.json +1 -0
- package/dist/i18n/locales/en/remote.json +54 -0
- package/dist/i18n/locales/zh-CN/notification.json +1 -0
- package/dist/i18n/locales/zh-CN/remote.json +54 -0
- package/dist/shared/ccjk.CGcy7cNM.mjs +495 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -171,6 +171,12 @@ ccjk at --status # Check status
|
|
|
171
171
|
ccjk cloud enable --provider github-gist
|
|
172
172
|
ccjk cloud sync
|
|
173
173
|
|
|
174
|
+
# Remote Control (Web/App)
|
|
175
|
+
ccjk remote setup # One-command setup (interactive)
|
|
176
|
+
ccjk remote setup --non-interactive --server-url <url> --auth-token <token> --binding-code <code>
|
|
177
|
+
ccjk remote doctor # Diagnose remote readiness
|
|
178
|
+
ccjk remote status # Quick runtime status
|
|
179
|
+
|
|
174
180
|
# MCP Services
|
|
175
181
|
ccjk mcp install <service>
|
|
176
182
|
ccjk mcp list
|
package/README.zh-CN.md
CHANGED
|
@@ -98,6 +98,9 @@ npx ccjk cloud enable --provider github-gist
|
|
|
98
98
|
```bash
|
|
99
99
|
npx ccjk # 交互式设置
|
|
100
100
|
npx ccjk i # 完整初始化
|
|
101
|
+
npx ccjk remote setup # 一键远程初始化(推荐)
|
|
102
|
+
npx ccjk remote doctor # 远程体检
|
|
103
|
+
npx ccjk remote status # 远程状态
|
|
101
104
|
npx ccjk u # 更新工作流
|
|
102
105
|
npx ccjk sync # 云端同步
|
|
103
106
|
npx ccjk doctor # 健康检查
|
|
@@ -1,512 +1,26 @@
|
|
|
1
1
|
import ansis from 'ansis';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { i18n } from './index2.mjs';
|
|
4
|
+
import { m as maskToken, a as isValidTokenFormat, c as generateDeviceToken, d as decryptToken, e as encryptToken, f as getDeviceInfo, i as isDeviceBound, g as getBindingStatus, u as unbindDevice, b as bindDevice, s as sendNotification } from '../shared/ccjk.CGcy7cNM.mjs';
|
|
5
|
+
import { exec } from 'node:child_process';
|
|
4
6
|
import * as nodeFs from 'node:fs';
|
|
5
|
-
import nodeFs__default
|
|
7
|
+
import nodeFs__default from 'node:fs';
|
|
6
8
|
import * as os from 'node:os';
|
|
7
|
-
import os__default
|
|
8
|
-
import { join } from 'pathe';
|
|
9
|
-
import { writeFileAtomic } from './fs-operations.mjs';
|
|
10
|
-
import { Buffer } from 'node:buffer';
|
|
11
|
-
import crypto from 'node:crypto';
|
|
12
|
-
import { exec } from 'node:child_process';
|
|
9
|
+
import os__default from 'node:os';
|
|
13
10
|
import * as path from 'node:path';
|
|
14
11
|
import path__default from 'node:path';
|
|
15
12
|
import process__default from 'node:process';
|
|
16
13
|
import { promisify } from 'node:util';
|
|
14
|
+
import { writeFileAtomic } from './fs-operations.mjs';
|
|
17
15
|
import { parse, stringify } from 'smol-toml';
|
|
18
16
|
import 'node:url';
|
|
19
17
|
import 'i18next';
|
|
20
18
|
import 'i18next-fs-backend';
|
|
19
|
+
import 'pathe';
|
|
20
|
+
import 'node:buffer';
|
|
21
|
+
import 'node:crypto';
|
|
21
22
|
import 'node:fs/promises';
|
|
22
23
|
|
|
23
|
-
const TOKEN_PREFIX = "ccjk_";
|
|
24
|
-
const TOKEN_LENGTH = 64;
|
|
25
|
-
const TOKEN_VERSION = 1;
|
|
26
|
-
function generateDeviceToken() {
|
|
27
|
-
const randomBytes = crypto.randomBytes(TOKEN_LENGTH / 2);
|
|
28
|
-
const randomHex = randomBytes.toString("hex");
|
|
29
|
-
return `${TOKEN_PREFIX}${TOKEN_VERSION}${randomHex}`;
|
|
30
|
-
}
|
|
31
|
-
function isValidTokenFormat(token) {
|
|
32
|
-
if (!token || typeof token !== "string") {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
if (!token.startsWith(TOKEN_PREFIX)) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
const expectedLength = TOKEN_PREFIX.length + 1 + TOKEN_LENGTH;
|
|
39
|
-
if (token.length !== expectedLength) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
const version = token[TOKEN_PREFIX.length];
|
|
43
|
-
if (!/^\d$/.test(version)) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
const hexPart = token.slice(TOKEN_PREFIX.length + 1);
|
|
47
|
-
if (!/^[a-f0-9]+$/i.test(hexPart)) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
function getDeviceInfo() {
|
|
53
|
-
return {
|
|
54
|
-
name: os__default.hostname(),
|
|
55
|
-
platform: os__default.platform(),
|
|
56
|
-
osVersion: os__default.release(),
|
|
57
|
-
arch: os__default.arch(),
|
|
58
|
-
username: os__default.userInfo().username,
|
|
59
|
-
machineId: generateMachineId()
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function generateMachineId() {
|
|
63
|
-
const components = [
|
|
64
|
-
os__default.hostname(),
|
|
65
|
-
os__default.platform(),
|
|
66
|
-
os__default.arch(),
|
|
67
|
-
os__default.cpus()[0]?.model || "unknown",
|
|
68
|
-
os__default.userInfo().username,
|
|
69
|
-
// Add network interface MAC addresses for uniqueness
|
|
70
|
-
...Object.values(os__default.networkInterfaces()).flat().filter((iface) => iface && !iface.internal && iface.mac !== "00:00:00:00:00:00").map((iface) => iface?.mac).filter(Boolean).slice(0, 3)
|
|
71
|
-
// Limit to first 3 MACs
|
|
72
|
-
];
|
|
73
|
-
const combined = components.join("|");
|
|
74
|
-
return crypto.createHash("sha256").update(combined).digest("hex").slice(0, 32);
|
|
75
|
-
}
|
|
76
|
-
function deriveEncryptionKey() {
|
|
77
|
-
const machineId = generateMachineId();
|
|
78
|
-
const salt = "ccjk-notification-token-v1";
|
|
79
|
-
return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
|
|
80
|
-
}
|
|
81
|
-
function encryptToken(token) {
|
|
82
|
-
const key = deriveEncryptionKey();
|
|
83
|
-
const iv = crypto.randomBytes(16);
|
|
84
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
85
|
-
let encrypted = cipher.update(token, "utf8", "hex");
|
|
86
|
-
encrypted += cipher.final("hex");
|
|
87
|
-
const authTag = cipher.getAuthTag();
|
|
88
|
-
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
89
|
-
}
|
|
90
|
-
function decryptToken(encryptedToken) {
|
|
91
|
-
try {
|
|
92
|
-
const parts = encryptedToken.split(":");
|
|
93
|
-
if (parts.length !== 3) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
const [ivHex, authTagHex, encrypted] = parts;
|
|
97
|
-
const key = deriveEncryptionKey();
|
|
98
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
99
|
-
const authTag = Buffer.from(authTagHex, "hex");
|
|
100
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
101
|
-
decipher.setAuthTag(authTag);
|
|
102
|
-
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
103
|
-
decrypted += decipher.final("utf8");
|
|
104
|
-
return decrypted;
|
|
105
|
-
} catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function maskToken(token) {
|
|
110
|
-
if (!token || token.length < 12) {
|
|
111
|
-
return "***";
|
|
112
|
-
}
|
|
113
|
-
const prefix = token.slice(0, TOKEN_PREFIX.length + 1);
|
|
114
|
-
const suffix = token.slice(-4);
|
|
115
|
-
return `${prefix}***...***${suffix}`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const CLOUD_API_BASE_URL = "https://api.claudehome.cn";
|
|
119
|
-
const DEFAULT_TIMEOUT = 3e4;
|
|
120
|
-
const POLL_TIMEOUT$1 = 6e4;
|
|
121
|
-
const TOKEN_FILE_PATH = join(homedir(), ".ccjk", "cloud-token.json");
|
|
122
|
-
class CCJKCloudClient {
|
|
123
|
-
baseUrl;
|
|
124
|
-
deviceToken = null;
|
|
125
|
-
deviceId = null;
|
|
126
|
-
/**
|
|
127
|
-
* Create a new CCJKCloudClient instance
|
|
128
|
-
*
|
|
129
|
-
* @param baseUrl - Cloud API base URL (default: https://api.claudehome.cn)
|
|
130
|
-
*/
|
|
131
|
-
constructor(baseUrl = CLOUD_API_BASE_URL) {
|
|
132
|
-
this.baseUrl = baseUrl;
|
|
133
|
-
this.loadToken();
|
|
134
|
-
}
|
|
135
|
-
// ==========================================================================
|
|
136
|
-
// Token Management
|
|
137
|
-
// ==========================================================================
|
|
138
|
-
/**
|
|
139
|
-
* Load token from storage file
|
|
140
|
-
*/
|
|
141
|
-
loadToken() {
|
|
142
|
-
try {
|
|
143
|
-
if (existsSync(TOKEN_FILE_PATH)) {
|
|
144
|
-
const data = readFileSync(TOKEN_FILE_PATH, "utf-8");
|
|
145
|
-
const storage = JSON.parse(data);
|
|
146
|
-
this.deviceToken = storage.deviceToken;
|
|
147
|
-
this.deviceId = storage.deviceId || null;
|
|
148
|
-
}
|
|
149
|
-
} catch {
|
|
150
|
-
this.deviceToken = null;
|
|
151
|
-
this.deviceId = null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Save token to storage file
|
|
156
|
-
*/
|
|
157
|
-
saveToken(storage) {
|
|
158
|
-
try {
|
|
159
|
-
const dir = join(homedir(), ".ccjk");
|
|
160
|
-
if (!existsSync(dir)) {
|
|
161
|
-
mkdirSync(dir, { recursive: true });
|
|
162
|
-
}
|
|
163
|
-
writeFileAtomic(TOKEN_FILE_PATH, JSON.stringify(storage, null, 2));
|
|
164
|
-
} catch (error) {
|
|
165
|
-
throw new Error(`Failed to save token: ${error instanceof Error ? error.message : String(error)}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Check if device is bound
|
|
170
|
-
*/
|
|
171
|
-
isBound() {
|
|
172
|
-
return this.deviceToken !== null && this.deviceToken.length > 0;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Get current device token
|
|
176
|
-
*/
|
|
177
|
-
getDeviceToken() {
|
|
178
|
-
return this.deviceToken;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Get current device ID
|
|
182
|
-
*/
|
|
183
|
-
getDeviceId() {
|
|
184
|
-
return this.deviceId;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Clear stored token (unbind device)
|
|
188
|
-
*/
|
|
189
|
-
clearToken() {
|
|
190
|
-
this.deviceToken = null;
|
|
191
|
-
this.deviceId = null;
|
|
192
|
-
try {
|
|
193
|
-
if (existsSync(TOKEN_FILE_PATH)) {
|
|
194
|
-
unlinkSync(TOKEN_FILE_PATH);
|
|
195
|
-
}
|
|
196
|
-
} catch {
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// ==========================================================================
|
|
200
|
-
// Device Binding
|
|
201
|
-
// ==========================================================================
|
|
202
|
-
/**
|
|
203
|
-
* Bind device using a binding code
|
|
204
|
-
*
|
|
205
|
-
* The binding code is obtained from the CCJK mobile app or web dashboard.
|
|
206
|
-
* Once bound, the device can send and receive notifications.
|
|
207
|
-
*
|
|
208
|
-
* @param code - Binding code from mobile app
|
|
209
|
-
* @param deviceInfo - Optional device information (auto-detected if not provided)
|
|
210
|
-
* @returns Bind response with device token
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```typescript
|
|
214
|
-
* const client = new CCJKCloudClient()
|
|
215
|
-
* const result = await client.bind('ABC123')
|
|
216
|
-
* if (result.success) {
|
|
217
|
-
* console.log('Device bound successfully!')
|
|
218
|
-
* }
|
|
219
|
-
* ```
|
|
220
|
-
*/
|
|
221
|
-
async bind(code, deviceInfo) {
|
|
222
|
-
const info = deviceInfo ? { ...getDeviceInfo(), ...deviceInfo } : getDeviceInfo();
|
|
223
|
-
const response = await this.request("/bind/use", {
|
|
224
|
-
method: "POST",
|
|
225
|
-
body: JSON.stringify({
|
|
226
|
-
code,
|
|
227
|
-
deviceInfo: info
|
|
228
|
-
})
|
|
229
|
-
});
|
|
230
|
-
if (response.success && response.data) {
|
|
231
|
-
this.deviceToken = response.data.deviceToken;
|
|
232
|
-
this.deviceId = response.data.deviceId;
|
|
233
|
-
this.saveToken({
|
|
234
|
-
deviceToken: response.data.deviceToken,
|
|
235
|
-
deviceId: response.data.deviceId,
|
|
236
|
-
bindingCode: code,
|
|
237
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
238
|
-
deviceInfo: info
|
|
239
|
-
});
|
|
240
|
-
return {
|
|
241
|
-
success: true,
|
|
242
|
-
deviceToken: response.data.deviceToken,
|
|
243
|
-
deviceId: response.data.deviceId
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
return {
|
|
247
|
-
success: false,
|
|
248
|
-
error: response.error || "Failed to bind device",
|
|
249
|
-
code: response.code
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
// ==========================================================================
|
|
253
|
-
// Notification Sending
|
|
254
|
-
// ==========================================================================
|
|
255
|
-
/**
|
|
256
|
-
* Send a notification to the user
|
|
257
|
-
*
|
|
258
|
-
* @param options - Notification options
|
|
259
|
-
* @returns Notification response
|
|
260
|
-
*
|
|
261
|
-
* @example
|
|
262
|
-
* ```typescript
|
|
263
|
-
* const client = new CCJKCloudClient()
|
|
264
|
-
* await client.notify({
|
|
265
|
-
* title: 'Build Complete',
|
|
266
|
-
* body: 'Your project has been built successfully!',
|
|
267
|
-
* type: 'success'
|
|
268
|
-
* })
|
|
269
|
-
* ```
|
|
270
|
-
*/
|
|
271
|
-
async notify(options) {
|
|
272
|
-
if (!this.deviceToken) {
|
|
273
|
-
return {
|
|
274
|
-
success: false,
|
|
275
|
-
error: 'Device not bound. Please run "ccjk notification bind <code>" first.',
|
|
276
|
-
code: "NOT_BOUND"
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
const response = await this.request("/notify", {
|
|
280
|
-
method: "POST",
|
|
281
|
-
body: JSON.stringify({
|
|
282
|
-
title: options.title,
|
|
283
|
-
body: options.body,
|
|
284
|
-
type: options.type || "info",
|
|
285
|
-
taskId: options.taskId,
|
|
286
|
-
metadata: options.metadata,
|
|
287
|
-
actions: options.actions
|
|
288
|
-
})
|
|
289
|
-
});
|
|
290
|
-
if (response.success && response.data) {
|
|
291
|
-
return {
|
|
292
|
-
success: true,
|
|
293
|
-
notificationId: response.data.notificationId
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
return {
|
|
297
|
-
success: false,
|
|
298
|
-
error: response.error || "Failed to send notification",
|
|
299
|
-
code: response.code
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
// ==========================================================================
|
|
303
|
-
// Reply Polling
|
|
304
|
-
// ==========================================================================
|
|
305
|
-
/**
|
|
306
|
-
* Wait for a reply from the user
|
|
307
|
-
*
|
|
308
|
-
* Uses long-polling to wait for a user reply. The timeout parameter
|
|
309
|
-
* controls how long to wait before returning null.
|
|
310
|
-
*
|
|
311
|
-
* @param timeout - Timeout in milliseconds (default: 30000)
|
|
312
|
-
* @returns User reply or null if timeout
|
|
313
|
-
*
|
|
314
|
-
* @example
|
|
315
|
-
* ```typescript
|
|
316
|
-
* const client = new CCJKCloudClient()
|
|
317
|
-
* const reply = await client.waitForReply(60000) // Wait up to 60 seconds
|
|
318
|
-
* if (reply) {
|
|
319
|
-
* console.log('User replied:', reply.content)
|
|
320
|
-
* }
|
|
321
|
-
* ```
|
|
322
|
-
*/
|
|
323
|
-
async waitForReply(timeout = POLL_TIMEOUT$1) {
|
|
324
|
-
if (!this.deviceToken) {
|
|
325
|
-
throw new Error('Device not bound. Please run "ccjk notification bind <code>" first.');
|
|
326
|
-
}
|
|
327
|
-
const response = await this.request(`/reply/poll?timeout=${timeout}`, {
|
|
328
|
-
method: "GET",
|
|
329
|
-
timeout
|
|
330
|
-
});
|
|
331
|
-
if (response.success && response.data?.reply) {
|
|
332
|
-
return {
|
|
333
|
-
content: response.data.reply.content,
|
|
334
|
-
timestamp: new Date(response.data.reply.timestamp),
|
|
335
|
-
notificationId: response.data.reply.notificationId,
|
|
336
|
-
actionId: response.data.reply.actionId
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
// ==========================================================================
|
|
342
|
-
// Ask and Wait
|
|
343
|
-
// ==========================================================================
|
|
344
|
-
/**
|
|
345
|
-
* Ask the user a question and wait for their reply
|
|
346
|
-
*
|
|
347
|
-
* This is a convenience method that combines notify() and waitForReply().
|
|
348
|
-
* It sends a notification with the question and waits for the user to respond.
|
|
349
|
-
*
|
|
350
|
-
* @param question - Question to ask the user
|
|
351
|
-
* @param options - Additional notification options
|
|
352
|
-
* @param timeout - Timeout in milliseconds (default: 60000)
|
|
353
|
-
* @returns User reply
|
|
354
|
-
*
|
|
355
|
-
* @example
|
|
356
|
-
* ```typescript
|
|
357
|
-
* const client = new CCJKCloudClient()
|
|
358
|
-
* const reply = await client.ask('Deploy to production?', {
|
|
359
|
-
* actions: [
|
|
360
|
-
* { id: 'yes', label: 'Yes', value: 'yes' },
|
|
361
|
-
* { id: 'no', label: 'No', value: 'no' }
|
|
362
|
-
* ]
|
|
363
|
-
* })
|
|
364
|
-
* if (reply.actionId === 'yes') {
|
|
365
|
-
* // Proceed with deployment
|
|
366
|
-
* }
|
|
367
|
-
* ```
|
|
368
|
-
*/
|
|
369
|
-
async ask(question, options, timeout = POLL_TIMEOUT$1) {
|
|
370
|
-
const notifyResult = await this.notify({
|
|
371
|
-
title: options?.title || "CCJK Question",
|
|
372
|
-
body: question,
|
|
373
|
-
type: "info",
|
|
374
|
-
...options
|
|
375
|
-
});
|
|
376
|
-
if (!notifyResult.success) {
|
|
377
|
-
throw new Error(notifyResult.error || "Failed to send question");
|
|
378
|
-
}
|
|
379
|
-
const reply = await this.waitForReply(timeout);
|
|
380
|
-
if (!reply) {
|
|
381
|
-
throw new Error("No reply received within timeout");
|
|
382
|
-
}
|
|
383
|
-
return reply;
|
|
384
|
-
}
|
|
385
|
-
// ==========================================================================
|
|
386
|
-
// Status Check
|
|
387
|
-
// ==========================================================================
|
|
388
|
-
/**
|
|
389
|
-
* Get binding status and device information
|
|
390
|
-
*
|
|
391
|
-
* @returns Binding status information
|
|
392
|
-
*/
|
|
393
|
-
async getStatus() {
|
|
394
|
-
if (!this.deviceToken) {
|
|
395
|
-
return { bound: false };
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
if (existsSync(TOKEN_FILE_PATH)) {
|
|
399
|
-
const data = readFileSync(TOKEN_FILE_PATH, "utf-8");
|
|
400
|
-
const storage = JSON.parse(data);
|
|
401
|
-
return {
|
|
402
|
-
bound: true,
|
|
403
|
-
deviceId: storage.deviceId,
|
|
404
|
-
deviceInfo: storage.deviceInfo,
|
|
405
|
-
lastUsed: storage.lastUsedAt
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
} catch {
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
bound: true,
|
|
412
|
-
deviceId: this.deviceId || void 0
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
// ==========================================================================
|
|
416
|
-
// HTTP Request Helper
|
|
417
|
-
// ==========================================================================
|
|
418
|
-
/**
|
|
419
|
-
* Make an HTTP request to the cloud service
|
|
420
|
-
*/
|
|
421
|
-
async request(path, options) {
|
|
422
|
-
const url = `${this.baseUrl}${path}`;
|
|
423
|
-
const timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
424
|
-
const controller = new AbortController();
|
|
425
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
426
|
-
try {
|
|
427
|
-
const headers = {
|
|
428
|
-
"Content-Type": "application/json"
|
|
429
|
-
};
|
|
430
|
-
if (this.deviceToken) {
|
|
431
|
-
headers["X-Device-Token"] = this.deviceToken;
|
|
432
|
-
}
|
|
433
|
-
const response = await fetch(url, {
|
|
434
|
-
method: options.method,
|
|
435
|
-
headers,
|
|
436
|
-
body: options.body,
|
|
437
|
-
signal: controller.signal
|
|
438
|
-
});
|
|
439
|
-
clearTimeout(timeoutId);
|
|
440
|
-
const data = await response.json();
|
|
441
|
-
if (!response.ok) {
|
|
442
|
-
return {
|
|
443
|
-
success: false,
|
|
444
|
-
error: data.error || `HTTP ${response.status}: ${response.statusText}`,
|
|
445
|
-
code: data.code || `HTTP_${response.status}`
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
if (this.deviceToken && existsSync(TOKEN_FILE_PATH)) {
|
|
449
|
-
try {
|
|
450
|
-
const storageData = readFileSync(TOKEN_FILE_PATH, "utf-8");
|
|
451
|
-
const storage = JSON.parse(storageData);
|
|
452
|
-
storage.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
453
|
-
writeFileAtomic(TOKEN_FILE_PATH, JSON.stringify(storage, null, 2));
|
|
454
|
-
} catch {
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return data;
|
|
458
|
-
} catch (error) {
|
|
459
|
-
clearTimeout(timeoutId);
|
|
460
|
-
if (error instanceof Error) {
|
|
461
|
-
if (error.name === "AbortError") {
|
|
462
|
-
return {
|
|
463
|
-
success: false,
|
|
464
|
-
error: "Request timeout",
|
|
465
|
-
code: "TIMEOUT"
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
return {
|
|
469
|
-
success: false,
|
|
470
|
-
error: error.message,
|
|
471
|
-
code: "NETWORK_ERROR"
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
return {
|
|
475
|
-
success: false,
|
|
476
|
-
error: String(error),
|
|
477
|
-
code: "UNKNOWN_ERROR"
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
let cloudClientInstance = null;
|
|
483
|
-
function getCloudNotificationClient() {
|
|
484
|
-
if (!cloudClientInstance) {
|
|
485
|
-
cloudClientInstance = new CCJKCloudClient();
|
|
486
|
-
}
|
|
487
|
-
return cloudClientInstance;
|
|
488
|
-
}
|
|
489
|
-
async function bindDevice(code, deviceInfo) {
|
|
490
|
-
const client = getCloudNotificationClient();
|
|
491
|
-
return client.bind(code, deviceInfo);
|
|
492
|
-
}
|
|
493
|
-
async function sendNotification(options) {
|
|
494
|
-
const client = getCloudNotificationClient();
|
|
495
|
-
return client.notify(options);
|
|
496
|
-
}
|
|
497
|
-
function isDeviceBound() {
|
|
498
|
-
const client = getCloudNotificationClient();
|
|
499
|
-
return client.isBound();
|
|
500
|
-
}
|
|
501
|
-
async function getBindingStatus() {
|
|
502
|
-
const client = getCloudNotificationClient();
|
|
503
|
-
return client.getStatus();
|
|
504
|
-
}
|
|
505
|
-
function unbindDevice() {
|
|
506
|
-
const client = getCloudNotificationClient();
|
|
507
|
-
client.clearToken();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
24
|
const execAsync = promisify(exec);
|
|
511
25
|
const DEFAULT_CONFIG = {
|
|
512
26
|
shortcutName: "ClaudeNotify",
|
|
@@ -1547,6 +1061,7 @@ async function notificationCommand(action = "menu", args) {
|
|
|
1547
1061
|
await configureThreshold();
|
|
1548
1062
|
break;
|
|
1549
1063
|
case "bind":
|
|
1064
|
+
console.log(ansis.yellow(i18n.t("notification:cloud.migrateToRemoteSetup")));
|
|
1550
1065
|
await handleBind(args?.[0]);
|
|
1551
1066
|
break;
|
|
1552
1067
|
case "unbind":
|
package/dist/chunks/package.mjs
CHANGED