claude-relay 2.2.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/bin/cli.js +271 -22
- package/lib/config.js +39 -2
- package/lib/daemon.js +53 -1
- package/lib/ipc.js +7 -3
- package/lib/pages.js +15 -1
- package/lib/project.js +324 -27
- package/lib/public/app.js +313 -7
- package/lib/public/css/base.css +5 -0
- package/lib/public/css/diff.css +128 -0
- package/lib/public/css/filebrowser.css +541 -0
- package/lib/public/css/input.css +1 -0
- package/lib/public/css/menus.css +89 -5
- package/lib/public/css/messages.css +84 -49
- package/lib/public/css/overlays.css +40 -0
- package/lib/public/index.html +100 -17
- package/lib/public/modules/diff.js +398 -0
- package/lib/public/modules/filebrowser.js +1023 -11
- package/lib/public/modules/input.js +96 -2
- package/lib/public/modules/notifications.js +29 -3
- package/lib/public/modules/qrcode.js +11 -2
- package/lib/public/modules/rewind.js +51 -2
- package/lib/public/modules/tools.js +43 -104
- package/lib/public/modules/utils.js +10 -2
- package/lib/public/style.css +1 -0
- package/lib/public/sw.js +21 -7
- package/lib/push.js +5 -1
- package/lib/sdk-bridge.js +40 -7
- package/lib/server.js +37 -4
- package/lib/sessions.js +14 -5
- package/lib/terminal.js +2 -1
- package/package.json +1 -1
package/lib/sdk-bridge.js
CHANGED
|
@@ -43,10 +43,12 @@ function createMessageQueue() {
|
|
|
43
43
|
|
|
44
44
|
function createSDKBridge(opts) {
|
|
45
45
|
var cwd = opts.cwd;
|
|
46
|
+
var slug = opts.slug || "";
|
|
46
47
|
var sm = opts.sessionManager; // session manager instance
|
|
47
48
|
var send = opts.send; // broadcast to all clients
|
|
48
49
|
var pushModule = opts.pushModule;
|
|
49
50
|
var getSDK = opts.getSDK;
|
|
51
|
+
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
50
52
|
|
|
51
53
|
function sendAndRecord(session, obj) {
|
|
52
54
|
sm.sendAndRecord(session, obj);
|
|
@@ -139,6 +141,7 @@ function createSDKBridge(opts) {
|
|
|
139
141
|
var q = input.questions[0];
|
|
140
142
|
pushModule.sendPush({
|
|
141
143
|
type: "ask_user",
|
|
144
|
+
slug: slug,
|
|
142
145
|
title: "Claude has a question",
|
|
143
146
|
body: q ? q.question : "Waiting for your response",
|
|
144
147
|
tag: "claude-ask",
|
|
@@ -219,6 +222,7 @@ function createSDKBridge(opts) {
|
|
|
219
222
|
cost: parsed.total_cost_usd,
|
|
220
223
|
duration: parsed.duration_ms,
|
|
221
224
|
usage: parsed.usage || null,
|
|
225
|
+
modelUsage: parsed.modelUsage || null,
|
|
222
226
|
sessionId: parsed.session_id,
|
|
223
227
|
});
|
|
224
228
|
sendAndRecord(session, { type: "done", code: 0 });
|
|
@@ -227,6 +231,7 @@ function createSDKBridge(opts) {
|
|
|
227
231
|
if (preview.length > 140) preview = preview.substring(0, 140) + "...";
|
|
228
232
|
pushModule.sendPush({
|
|
229
233
|
type: "done",
|
|
234
|
+
slug: slug,
|
|
230
235
|
title: session.title || "Claude",
|
|
231
236
|
body: preview || "Response ready",
|
|
232
237
|
tag: "claude-done",
|
|
@@ -298,6 +303,7 @@ function createSDKBridge(opts) {
|
|
|
298
303
|
if (pushModule) {
|
|
299
304
|
pushModule.sendPush({
|
|
300
305
|
type: "permission_request",
|
|
306
|
+
slug: slug,
|
|
301
307
|
requestId: requestId,
|
|
302
308
|
title: permissionPushTitle(toolName, input),
|
|
303
309
|
body: permissionPushBody(toolName, input),
|
|
@@ -331,6 +337,7 @@ function createSDKBridge(opts) {
|
|
|
331
337
|
if (pushModule) {
|
|
332
338
|
pushModule.sendPush({
|
|
333
339
|
type: "error",
|
|
340
|
+
slug: slug,
|
|
334
341
|
title: "Connection Lost",
|
|
335
342
|
body: "Claude process disconnected: " + (err.message || "unknown error"),
|
|
336
343
|
tag: "claude-error",
|
|
@@ -343,6 +350,8 @@ function createSDKBridge(opts) {
|
|
|
343
350
|
session.queryInstance = null;
|
|
344
351
|
session.messageQueue = null;
|
|
345
352
|
session.abortController = null;
|
|
353
|
+
session.pendingPermissions = {};
|
|
354
|
+
session.pendingAskUser = {};
|
|
346
355
|
}
|
|
347
356
|
}
|
|
348
357
|
|
|
@@ -385,7 +394,10 @@ function createSDKBridge(opts) {
|
|
|
385
394
|
try {
|
|
386
395
|
sdk = await getSDK();
|
|
387
396
|
} catch (e) {
|
|
397
|
+
session.isProcessing = false;
|
|
388
398
|
send({ type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
|
|
399
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
400
|
+
sm.broadcastSessionList();
|
|
389
401
|
return;
|
|
390
402
|
}
|
|
391
403
|
|
|
@@ -428,6 +440,11 @@ function createSDKBridge(opts) {
|
|
|
428
440
|
},
|
|
429
441
|
};
|
|
430
442
|
|
|
443
|
+
if (dangerouslySkipPermissions) {
|
|
444
|
+
queryOptions.permissionMode = "bypassPermissions";
|
|
445
|
+
queryOptions.allowDangerouslySkipPermissions = true;
|
|
446
|
+
}
|
|
447
|
+
|
|
431
448
|
if (session.cliSessionId) {
|
|
432
449
|
queryOptions.resume = session.cliSessionId;
|
|
433
450
|
if (session.lastRewindUuid) {
|
|
@@ -436,10 +453,21 @@ function createSDKBridge(opts) {
|
|
|
436
453
|
}
|
|
437
454
|
}
|
|
438
455
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
456
|
+
try {
|
|
457
|
+
session.queryInstance = sdk.query({
|
|
458
|
+
prompt: session.messageQueue,
|
|
459
|
+
options: queryOptions,
|
|
460
|
+
});
|
|
461
|
+
} catch (e) {
|
|
462
|
+
session.isProcessing = false;
|
|
463
|
+
session.queryInstance = null;
|
|
464
|
+
session.messageQueue = null;
|
|
465
|
+
session.abortController = null;
|
|
466
|
+
send({ type: "error", text: "Failed to start query: " + (e.message || e) });
|
|
467
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
468
|
+
sm.broadcastSessionList();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
443
471
|
|
|
444
472
|
processQueryStream(session).catch(function(err) {
|
|
445
473
|
});
|
|
@@ -466,7 +494,7 @@ function createSDKBridge(opts) {
|
|
|
466
494
|
|
|
467
495
|
function permissionPushTitle(toolName, input) {
|
|
468
496
|
if (!input) return "Claude wants to use " + toolName;
|
|
469
|
-
var file = input.file_path ? input.file_path.split(
|
|
497
|
+
var file = input.file_path ? input.file_path.split(/[/\\]/).pop() : "";
|
|
470
498
|
switch (toolName) {
|
|
471
499
|
case "Bash": return "Claude wants to run a command";
|
|
472
500
|
case "Edit": return "Claude wants to edit " + (file || "a file");
|
|
@@ -487,7 +515,7 @@ function createSDKBridge(opts) {
|
|
|
487
515
|
if (toolName === "Bash" && input.command) {
|
|
488
516
|
text = input.command;
|
|
489
517
|
} else if (toolName === "Edit" && input.file_path) {
|
|
490
|
-
text = input.file_path.split(
|
|
518
|
+
text = input.file_path.split(/[/\\]/).pop() + ": " + (input.old_string || "").substring(0, 40) + " \u2192 " + (input.new_string || "").substring(0, 40);
|
|
491
519
|
} else if (toolName === "Write" && input.file_path) {
|
|
492
520
|
text = input.file_path;
|
|
493
521
|
} else if (input.file_path) {
|
|
@@ -515,9 +543,14 @@ function createSDKBridge(opts) {
|
|
|
515
543
|
var mq = createMessageQueue();
|
|
516
544
|
mq.push({ type: "user", message: { role: "user", content: [{ type: "text", text: "hi" }] } });
|
|
517
545
|
mq.end();
|
|
546
|
+
var warmupOptions = { cwd: cwd, settingSources: ["user", "project", "local"], abortController: ac };
|
|
547
|
+
if (dangerouslySkipPermissions) {
|
|
548
|
+
warmupOptions.permissionMode = "bypassPermissions";
|
|
549
|
+
warmupOptions.allowDangerouslySkipPermissions = true;
|
|
550
|
+
}
|
|
518
551
|
var stream = sdk.query({
|
|
519
552
|
prompt: mq,
|
|
520
|
-
options:
|
|
553
|
+
options: warmupOptions,
|
|
521
554
|
});
|
|
522
555
|
for await (var msg of stream) {
|
|
523
556
|
if (msg.type === "system" && msg.subtype === "init") {
|
package/lib/server.js
CHANGED
|
@@ -114,7 +114,7 @@ function stripPrefix(urlPath, slug) {
|
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* Create a multi-project server.
|
|
117
|
-
* opts: { tlsOptions, caPath, pinHash, port, debug }
|
|
117
|
+
* opts: { tlsOptions, caPath, pinHash, port, debug, dangerouslySkipPermissions }
|
|
118
118
|
*/
|
|
119
119
|
function createServer(opts) {
|
|
120
120
|
var tlsOptions = opts.tlsOptions || null;
|
|
@@ -122,6 +122,8 @@ function createServer(opts) {
|
|
|
122
122
|
var pinHash = opts.pinHash || null;
|
|
123
123
|
var portNum = opts.port || 2633;
|
|
124
124
|
var debug = opts.debug || false;
|
|
125
|
+
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
126
|
+
var lanHost = opts.lanHost || null;
|
|
125
127
|
|
|
126
128
|
var authToken = pinHash || null;
|
|
127
129
|
var realVersion = require("../package.json").version;
|
|
@@ -228,8 +230,9 @@ function createServer(opts) {
|
|
|
228
230
|
req.on("data", function (chunk) { body += chunk; });
|
|
229
231
|
req.on("end", function () {
|
|
230
232
|
try {
|
|
231
|
-
var
|
|
232
|
-
|
|
233
|
+
var parsed = JSON.parse(body);
|
|
234
|
+
var sub = parsed.subscription || parsed;
|
|
235
|
+
pushModule.addSubscription(sub, parsed.replaceEndpoint);
|
|
233
236
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
234
237
|
res.end('{"ok":true}');
|
|
235
238
|
} catch (e) {
|
|
@@ -402,7 +405,19 @@ function createServer(opts) {
|
|
|
402
405
|
if (origin) {
|
|
403
406
|
try {
|
|
404
407
|
var originUrl = new URL(origin);
|
|
405
|
-
|
|
408
|
+
var originPort = String(originUrl.port || (originUrl.protocol === "https:" ? "443" : "80"));
|
|
409
|
+
// Extract port from Host header for reverse proxy support.
|
|
410
|
+
// Use URL parser to correctly handle IPv6 addresses (e.g. [::1])
|
|
411
|
+
// and infer default port from origin protocol (not backend tlsOptions)
|
|
412
|
+
// so TLS-terminating proxies on :443 with HTTP backends work.
|
|
413
|
+
var hostPort;
|
|
414
|
+
try {
|
|
415
|
+
var hostUrl = new URL(originUrl.protocol + "//" + (req.headers.host || ""));
|
|
416
|
+
hostPort = String(hostUrl.port || (originUrl.protocol === "https:" ? "443" : "80"));
|
|
417
|
+
} catch (e2) {
|
|
418
|
+
hostPort = String(portNum);
|
|
419
|
+
}
|
|
420
|
+
if (originPort !== String(portNum) && originPort !== hostPort) {
|
|
406
421
|
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
407
422
|
socket.destroy();
|
|
408
423
|
return;
|
|
@@ -447,7 +462,9 @@ function createServer(opts) {
|
|
|
447
462
|
title: title || null,
|
|
448
463
|
pushModule: pushModule,
|
|
449
464
|
debug: debug,
|
|
465
|
+
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
450
466
|
currentVersion: currentVersion,
|
|
467
|
+
lanHost: lanHost,
|
|
451
468
|
getProjectCount: function () { return projects.size; },
|
|
452
469
|
getProjectList: function () {
|
|
453
470
|
var list = [];
|
|
@@ -487,6 +504,20 @@ function createServer(opts) {
|
|
|
487
504
|
authToken = hash;
|
|
488
505
|
}
|
|
489
506
|
|
|
507
|
+
function broadcastAll(msg) {
|
|
508
|
+
projects.forEach(function (ctx) {
|
|
509
|
+
ctx.send(msg);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function destroyAll() {
|
|
514
|
+
projects.forEach(function (ctx, slug) {
|
|
515
|
+
console.log("[server] Destroying project:", slug);
|
|
516
|
+
ctx.destroy();
|
|
517
|
+
});
|
|
518
|
+
projects.clear();
|
|
519
|
+
}
|
|
520
|
+
|
|
490
521
|
return {
|
|
491
522
|
server: server,
|
|
492
523
|
onboardingServer: onboardingServer,
|
|
@@ -496,6 +527,8 @@ function createServer(opts) {
|
|
|
496
527
|
getProjects: getProjects,
|
|
497
528
|
setProjectTitle: setProjectTitle,
|
|
498
529
|
setAuthToken: setAuthToken,
|
|
530
|
+
broadcastAll: broadcastAll,
|
|
531
|
+
destroyAll: destroyAll,
|
|
499
532
|
};
|
|
500
533
|
}
|
|
501
534
|
|
package/lib/sessions.js
CHANGED
|
@@ -25,13 +25,15 @@ function createSessionManager(opts) {
|
|
|
25
25
|
if (!session.cliSessionId) return;
|
|
26
26
|
session.lastActivity = Date.now();
|
|
27
27
|
try {
|
|
28
|
-
var
|
|
28
|
+
var metaObj = {
|
|
29
29
|
type: "meta",
|
|
30
30
|
localId: session.localId,
|
|
31
31
|
cliSessionId: session.cliSessionId,
|
|
32
32
|
title: session.title,
|
|
33
33
|
createdAt: session.createdAt,
|
|
34
|
-
}
|
|
34
|
+
};
|
|
35
|
+
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
36
|
+
var meta = JSON.stringify(metaObj);
|
|
35
37
|
var lines = [meta];
|
|
36
38
|
for (var i = 0; i < session.history.length; i++) {
|
|
37
39
|
lines.push(JSON.stringify(session.history[i]));
|
|
@@ -105,6 +107,7 @@ function createSessionManager(opts) {
|
|
|
105
107
|
lastActivity: loaded[i].mtime || m.createdAt || Date.now(),
|
|
106
108
|
history: loaded[i].history,
|
|
107
109
|
messageUUIDs: messageUUIDs,
|
|
110
|
+
lastRewindUuid: m.lastRewindUuid || null,
|
|
108
111
|
};
|
|
109
112
|
sessions.set(localId, session);
|
|
110
113
|
}
|
|
@@ -278,9 +281,15 @@ function createSessionManager(opts) {
|
|
|
278
281
|
if (sessions.size === 0) {
|
|
279
282
|
createSession();
|
|
280
283
|
} else {
|
|
281
|
-
// Activate the most
|
|
282
|
-
var
|
|
283
|
-
|
|
284
|
+
// Activate the most recently used session
|
|
285
|
+
var allSessions = [...sessions.values()];
|
|
286
|
+
var mostRecent = allSessions[0];
|
|
287
|
+
for (var i = 1; i < allSessions.length; i++) {
|
|
288
|
+
if ((allSessions[i].lastActivity || 0) > (mostRecent.lastActivity || 0)) {
|
|
289
|
+
mostRecent = allSessions[i];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
activeSessionId = mostRecent.localId;
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
function searchSessions(query) {
|
package/lib/terminal.js
CHANGED
|
@@ -8,7 +8,8 @@ try {
|
|
|
8
8
|
function createTerminal(cwd, cols, rows) {
|
|
9
9
|
if (!pty) return null;
|
|
10
10
|
|
|
11
|
-
var shell = process.env.SHELL
|
|
11
|
+
var shell = process.env.SHELL
|
|
12
|
+
|| (process.platform === "win32" ? process.env.COMSPEC || "cmd.exe" : "/bin/bash");
|
|
12
13
|
var term = pty.spawn(shell, [], {
|
|
13
14
|
name: "xterm-256color",
|
|
14
15
|
cols: cols || 80,
|