claude-use 7.6.1 → 7.7.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/dist/cli.cjs +33 -11
- package/dist/index.cjs +35 -10
- package/dist/index.mjs +35 -10
- package/dist/types/frontdoor/connect.d.ts +7 -6
- package/dist/types/frontdoor/route.d.ts +112 -0
- package/dist/types/frontdoor/tap.d.ts +2 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -61053,7 +61053,7 @@ function resolveLaunchIdentity(paths, cwd) {
|
|
|
61053
61053
|
// package.json
|
|
61054
61054
|
var package_default = {
|
|
61055
61055
|
name: "claude-use",
|
|
61056
|
-
version: "7.
|
|
61056
|
+
version: "7.7.0",
|
|
61057
61057
|
description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
61058
61058
|
license: "MIT",
|
|
61059
61059
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -64808,7 +64808,7 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
64808
64808
|
}
|
|
64809
64809
|
|
|
64810
64810
|
// src/frontdoor/tap.ts
|
|
64811
|
-
function pumpTapSession(secure, openUpstream, tap) {
|
|
64811
|
+
function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
64812
64812
|
let upstream;
|
|
64813
64813
|
let ended = false;
|
|
64814
64814
|
const end = () => {
|
|
@@ -64840,6 +64840,9 @@ function pumpTapSession(secure, openUpstream, tap) {
|
|
|
64840
64840
|
upstream.destroy();
|
|
64841
64841
|
return;
|
|
64842
64842
|
}
|
|
64843
|
+
if (prefix.length > 0) {
|
|
64844
|
+
upstream.write(prefix);
|
|
64845
|
+
}
|
|
64843
64846
|
if (tap !== void 0) {
|
|
64844
64847
|
secure.on("data", (chunk) => {
|
|
64845
64848
|
tap.onChunk("client-to-server", chunk);
|
|
@@ -65054,7 +65057,19 @@ function caParses(ca) {
|
|
|
65054
65057
|
}
|
|
65055
65058
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
65056
65059
|
async function startConnectServer(config2, effects, preferredPort) {
|
|
65057
|
-
const
|
|
65060
|
+
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
65061
|
+
const onUpgrade = (host) => (request6, socket, head) => {
|
|
65062
|
+
config2.capture?.upgrade(request6);
|
|
65063
|
+
if (head.length > 0) {
|
|
65064
|
+
socket.pause();
|
|
65065
|
+
socket.unshift(head);
|
|
65066
|
+
}
|
|
65067
|
+
const headText = `${request6.method ?? "GET"} ${request6.url ?? "/"} HTTP/1.1\r
|
|
65068
|
+
${request6.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
65069
|
+
`, "")}\r
|
|
65070
|
+
`;
|
|
65071
|
+
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), void 0, Buffer.from(headText, "utf8"));
|
|
65072
|
+
};
|
|
65058
65073
|
const sessions = config2.interceptHosts.map((host) => {
|
|
65059
65074
|
if (config2.tapHosts.includes(host)) {
|
|
65060
65075
|
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
@@ -65068,13 +65083,18 @@ async function startConnectServer(config2, effects, preferredPort) {
|
|
|
65068
65083
|
}
|
|
65069
65084
|
const handler = (request6, response) => {
|
|
65070
65085
|
if (host === config2.routedHost && servedByPipeline(request6.url)) {
|
|
65086
|
+
const tunnelToken = tunnelTokens.get(request6.socket);
|
|
65087
|
+
if (tunnelToken !== void 0 && request6.headers[AUTH_HEADER] === void 0) {
|
|
65088
|
+
request6.headers[AUTH_HEADER] = tunnelToken;
|
|
65089
|
+
}
|
|
65071
65090
|
config2.serveRouted(request6, response);
|
|
65072
65091
|
return;
|
|
65073
65092
|
}
|
|
65074
65093
|
effects.forwardHttp(config2.upstreamFor(host), request6, response, config2.capture?.observePassthrough(request6));
|
|
65075
65094
|
};
|
|
65076
|
-
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
65077
|
-
const tlsAcceptor = effects.createTlsAcceptor(config2.leafFor(host), (secure) => {
|
|
65095
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
65096
|
+
const tlsAcceptor = effects.createTlsAcceptor(config2.leafFor(host), (secure, context) => {
|
|
65097
|
+
tunnelTokens.set(secure, context);
|
|
65078
65098
|
httpSession.serve(secure);
|
|
65079
65099
|
});
|
|
65080
65100
|
return { host, httpSession, tlsAcceptor };
|
|
@@ -65215,6 +65235,7 @@ function routeConnect(socket, head, context) {
|
|
|
65215
65235
|
const credential = proxyAuthorizationOf(head);
|
|
65216
65236
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
65217
65237
|
if (capability === void 0 || !config2.isLiveCapability(capability)) {
|
|
65238
|
+
console.error(`[upg-dbg] refused CONNECT to ${head.split("\r\n", 1)[0]?.split(" ")[1] ?? "?"} (credential ${credential === void 0 ? "absent" : "present but dead"})`);
|
|
65218
65239
|
refuse(socket, REFUSAL.proxyAuthenticationRequired, [`Proxy-Authenticate: Basic realm="${CONNECT_PROXY_REALM}"`]);
|
|
65219
65240
|
return;
|
|
65220
65241
|
}
|
|
@@ -65234,7 +65255,7 @@ function routeConnect(socket, head, context) {
|
|
|
65234
65255
|
config2.capture?.connect(target, session !== void 0);
|
|
65235
65256
|
socket.write(CONNECT_ESTABLISHED);
|
|
65236
65257
|
if (session !== void 0) {
|
|
65237
|
-
session.tlsAcceptor.accept(socket);
|
|
65258
|
+
session.tlsAcceptor.accept(socket, capability);
|
|
65238
65259
|
return;
|
|
65239
65260
|
}
|
|
65240
65261
|
void blindTunnel(socket, target, effects);
|
|
@@ -65346,10 +65367,11 @@ function realConnectEffects() {
|
|
|
65346
65367
|
return await listenOnce(0, onSocket);
|
|
65347
65368
|
},
|
|
65348
65369
|
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
65370
|
+
let pendingContext = "";
|
|
65349
65371
|
const server = tls.createServer(
|
|
65350
65372
|
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
65351
65373
|
(secure) => {
|
|
65352
|
-
onSecure(secure);
|
|
65374
|
+
onSecure(secure, pendingContext);
|
|
65353
65375
|
}
|
|
65354
65376
|
);
|
|
65355
65377
|
server.on("clientError", (_error, socket) => {
|
|
@@ -65357,7 +65379,8 @@ function realConnectEffects() {
|
|
|
65357
65379
|
});
|
|
65358
65380
|
return {
|
|
65359
65381
|
// tls.Server is a net.Server whose connection listener wraps the raw socket in the TLS handshake, and emitting the event by hand is what runs that listener on a socket this process already owns (the CONNECT half of the connection) rather than one the server accepted itself.
|
|
65360
|
-
accept: (socket) => {
|
|
65382
|
+
accept: (socket, context) => {
|
|
65383
|
+
pendingContext = context;
|
|
65361
65384
|
server.emit("connection", socket);
|
|
65362
65385
|
},
|
|
65363
65386
|
close: () => {
|
|
@@ -65371,9 +65394,8 @@ function realConnectEffects() {
|
|
|
65371
65394
|
socket.destroy();
|
|
65372
65395
|
});
|
|
65373
65396
|
if (onUpgrade !== void 0) {
|
|
65374
|
-
server.on("upgrade", (request6, socket) => {
|
|
65375
|
-
onUpgrade(request6);
|
|
65376
|
-
socket.destroy();
|
|
65397
|
+
server.on("upgrade", (request6, socket, head) => {
|
|
65398
|
+
onUpgrade(request6, socket, head);
|
|
65377
65399
|
});
|
|
65378
65400
|
}
|
|
65379
65401
|
return {
|
package/dist/index.cjs
CHANGED
|
@@ -2160,7 +2160,7 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
2160
2160
|
}
|
|
2161
2161
|
|
|
2162
2162
|
// src/frontdoor/tap.ts
|
|
2163
|
-
function pumpTapSession(secure, openUpstream, tap) {
|
|
2163
|
+
function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
2164
2164
|
let upstream;
|
|
2165
2165
|
let ended = false;
|
|
2166
2166
|
const end = () => {
|
|
@@ -2192,6 +2192,9 @@ function pumpTapSession(secure, openUpstream, tap) {
|
|
|
2192
2192
|
upstream.destroy();
|
|
2193
2193
|
return;
|
|
2194
2194
|
}
|
|
2195
|
+
if (prefix.length > 0) {
|
|
2196
|
+
upstream.write(prefix);
|
|
2197
|
+
}
|
|
2195
2198
|
if (tap !== void 0) {
|
|
2196
2199
|
secure.on("data", (chunk) => {
|
|
2197
2200
|
tap.onChunk("client-to-server", chunk);
|
|
@@ -2225,6 +2228,9 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2225
2228
|
return password === "" ? void 0 : password;
|
|
2226
2229
|
}
|
|
2227
2230
|
|
|
2231
|
+
// src/frontdoor/route.ts
|
|
2232
|
+
var AUTH_HEADER = "x-agent-shim-auth";
|
|
2233
|
+
|
|
2228
2234
|
// src/frontdoor/state.ts
|
|
2229
2235
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
2230
2236
|
var import_zod3 = require("zod");
|
|
@@ -2522,7 +2528,19 @@ function caParses(ca) {
|
|
|
2522
2528
|
}
|
|
2523
2529
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2524
2530
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2525
|
-
const
|
|
2531
|
+
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
2532
|
+
const onUpgrade = (host) => (request3, socket, head) => {
|
|
2533
|
+
config.capture?.upgrade(request3);
|
|
2534
|
+
if (head.length > 0) {
|
|
2535
|
+
socket.pause();
|
|
2536
|
+
socket.unshift(head);
|
|
2537
|
+
}
|
|
2538
|
+
const headText = `${request3.method ?? "GET"} ${request3.url ?? "/"} HTTP/1.1\r
|
|
2539
|
+
${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
2540
|
+
`, "")}\r
|
|
2541
|
+
`;
|
|
2542
|
+
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), void 0, Buffer.from(headText, "utf8"));
|
|
2543
|
+
};
|
|
2526
2544
|
const sessions = config.interceptHosts.map((host) => {
|
|
2527
2545
|
if (config.tapHosts.includes(host)) {
|
|
2528
2546
|
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
@@ -2536,13 +2554,18 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2536
2554
|
}
|
|
2537
2555
|
const handler = (request3, response) => {
|
|
2538
2556
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2557
|
+
const tunnelToken = tunnelTokens.get(request3.socket);
|
|
2558
|
+
if (tunnelToken !== void 0 && request3.headers[AUTH_HEADER] === void 0) {
|
|
2559
|
+
request3.headers[AUTH_HEADER] = tunnelToken;
|
|
2560
|
+
}
|
|
2539
2561
|
config.serveRouted(request3, response);
|
|
2540
2562
|
return;
|
|
2541
2563
|
}
|
|
2542
2564
|
effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
|
|
2543
2565
|
};
|
|
2544
|
-
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
2545
|
-
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure) => {
|
|
2566
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
2567
|
+
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure, context) => {
|
|
2568
|
+
tunnelTokens.set(secure, context);
|
|
2546
2569
|
httpSession.serve(secure);
|
|
2547
2570
|
});
|
|
2548
2571
|
return { host, httpSession, tlsAcceptor };
|
|
@@ -2683,6 +2706,7 @@ function routeConnect(socket, head, context) {
|
|
|
2683
2706
|
const credential = proxyAuthorizationOf(head);
|
|
2684
2707
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
2685
2708
|
if (capability === void 0 || !config.isLiveCapability(capability)) {
|
|
2709
|
+
console.error(`[upg-dbg] refused CONNECT to ${head.split("\r\n", 1)[0]?.split(" ")[1] ?? "?"} (credential ${credential === void 0 ? "absent" : "present but dead"})`);
|
|
2686
2710
|
refuse(socket, REFUSAL.proxyAuthenticationRequired, [`Proxy-Authenticate: Basic realm="${CONNECT_PROXY_REALM}"`]);
|
|
2687
2711
|
return;
|
|
2688
2712
|
}
|
|
@@ -2702,7 +2726,7 @@ function routeConnect(socket, head, context) {
|
|
|
2702
2726
|
config.capture?.connect(target, session !== void 0);
|
|
2703
2727
|
socket.write(CONNECT_ESTABLISHED);
|
|
2704
2728
|
if (session !== void 0) {
|
|
2705
|
-
session.tlsAcceptor.accept(socket);
|
|
2729
|
+
session.tlsAcceptor.accept(socket, capability);
|
|
2706
2730
|
return;
|
|
2707
2731
|
}
|
|
2708
2732
|
void blindTunnel(socket, target, effects);
|
|
@@ -2814,10 +2838,11 @@ function realConnectEffects() {
|
|
|
2814
2838
|
return await listenOnce(0, onSocket);
|
|
2815
2839
|
},
|
|
2816
2840
|
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2841
|
+
let pendingContext = "";
|
|
2817
2842
|
const server = tls.createServer(
|
|
2818
2843
|
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2819
2844
|
(secure) => {
|
|
2820
|
-
onSecure(secure);
|
|
2845
|
+
onSecure(secure, pendingContext);
|
|
2821
2846
|
}
|
|
2822
2847
|
);
|
|
2823
2848
|
server.on("clientError", (_error, socket) => {
|
|
@@ -2825,7 +2850,8 @@ function realConnectEffects() {
|
|
|
2825
2850
|
});
|
|
2826
2851
|
return {
|
|
2827
2852
|
// tls.Server is a net.Server whose connection listener wraps the raw socket in the TLS handshake, and emitting the event by hand is what runs that listener on a socket this process already owns (the CONNECT half of the connection) rather than one the server accepted itself.
|
|
2828
|
-
accept: (socket) => {
|
|
2853
|
+
accept: (socket, context) => {
|
|
2854
|
+
pendingContext = context;
|
|
2829
2855
|
server.emit("connection", socket);
|
|
2830
2856
|
},
|
|
2831
2857
|
close: () => {
|
|
@@ -2839,9 +2865,8 @@ function realConnectEffects() {
|
|
|
2839
2865
|
socket.destroy();
|
|
2840
2866
|
});
|
|
2841
2867
|
if (onUpgrade !== void 0) {
|
|
2842
|
-
server.on("upgrade", (request3, socket) => {
|
|
2843
|
-
onUpgrade(request3);
|
|
2844
|
-
socket.destroy();
|
|
2868
|
+
server.on("upgrade", (request3, socket, head) => {
|
|
2869
|
+
onUpgrade(request3, socket, head);
|
|
2845
2870
|
});
|
|
2846
2871
|
}
|
|
2847
2872
|
return {
|
package/dist/index.mjs
CHANGED
|
@@ -2060,7 +2060,7 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
2060
2060
|
}
|
|
2061
2061
|
|
|
2062
2062
|
// src/frontdoor/tap.ts
|
|
2063
|
-
function pumpTapSession(secure, openUpstream, tap) {
|
|
2063
|
+
function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
2064
2064
|
let upstream;
|
|
2065
2065
|
let ended = false;
|
|
2066
2066
|
const end = () => {
|
|
@@ -2092,6 +2092,9 @@ function pumpTapSession(secure, openUpstream, tap) {
|
|
|
2092
2092
|
upstream.destroy();
|
|
2093
2093
|
return;
|
|
2094
2094
|
}
|
|
2095
|
+
if (prefix.length > 0) {
|
|
2096
|
+
upstream.write(prefix);
|
|
2097
|
+
}
|
|
2095
2098
|
if (tap !== void 0) {
|
|
2096
2099
|
secure.on("data", (chunk) => {
|
|
2097
2100
|
tap.onChunk("client-to-server", chunk);
|
|
@@ -2125,6 +2128,9 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2125
2128
|
return password === "" ? void 0 : password;
|
|
2126
2129
|
}
|
|
2127
2130
|
|
|
2131
|
+
// src/frontdoor/route.ts
|
|
2132
|
+
var AUTH_HEADER = "x-agent-shim-auth";
|
|
2133
|
+
|
|
2128
2134
|
// src/frontdoor/state.ts
|
|
2129
2135
|
import path5 from "node:path";
|
|
2130
2136
|
import { z as z3 } from "zod";
|
|
@@ -2422,7 +2428,19 @@ function caParses(ca) {
|
|
|
2422
2428
|
}
|
|
2423
2429
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2424
2430
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2425
|
-
const
|
|
2431
|
+
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
2432
|
+
const onUpgrade = (host) => (request3, socket, head) => {
|
|
2433
|
+
config.capture?.upgrade(request3);
|
|
2434
|
+
if (head.length > 0) {
|
|
2435
|
+
socket.pause();
|
|
2436
|
+
socket.unshift(head);
|
|
2437
|
+
}
|
|
2438
|
+
const headText = `${request3.method ?? "GET"} ${request3.url ?? "/"} HTTP/1.1\r
|
|
2439
|
+
${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
2440
|
+
`, "")}\r
|
|
2441
|
+
`;
|
|
2442
|
+
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), void 0, Buffer.from(headText, "utf8"));
|
|
2443
|
+
};
|
|
2426
2444
|
const sessions = config.interceptHosts.map((host) => {
|
|
2427
2445
|
if (config.tapHosts.includes(host)) {
|
|
2428
2446
|
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
@@ -2436,13 +2454,18 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2436
2454
|
}
|
|
2437
2455
|
const handler = (request3, response) => {
|
|
2438
2456
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2457
|
+
const tunnelToken = tunnelTokens.get(request3.socket);
|
|
2458
|
+
if (tunnelToken !== void 0 && request3.headers[AUTH_HEADER] === void 0) {
|
|
2459
|
+
request3.headers[AUTH_HEADER] = tunnelToken;
|
|
2460
|
+
}
|
|
2439
2461
|
config.serveRouted(request3, response);
|
|
2440
2462
|
return;
|
|
2441
2463
|
}
|
|
2442
2464
|
effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
|
|
2443
2465
|
};
|
|
2444
|
-
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
2445
|
-
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure) => {
|
|
2466
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
2467
|
+
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure, context) => {
|
|
2468
|
+
tunnelTokens.set(secure, context);
|
|
2446
2469
|
httpSession.serve(secure);
|
|
2447
2470
|
});
|
|
2448
2471
|
return { host, httpSession, tlsAcceptor };
|
|
@@ -2583,6 +2606,7 @@ function routeConnect(socket, head, context) {
|
|
|
2583
2606
|
const credential = proxyAuthorizationOf(head);
|
|
2584
2607
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
2585
2608
|
if (capability === void 0 || !config.isLiveCapability(capability)) {
|
|
2609
|
+
console.error(`[upg-dbg] refused CONNECT to ${head.split("\r\n", 1)[0]?.split(" ")[1] ?? "?"} (credential ${credential === void 0 ? "absent" : "present but dead"})`);
|
|
2586
2610
|
refuse(socket, REFUSAL.proxyAuthenticationRequired, [`Proxy-Authenticate: Basic realm="${CONNECT_PROXY_REALM}"`]);
|
|
2587
2611
|
return;
|
|
2588
2612
|
}
|
|
@@ -2602,7 +2626,7 @@ function routeConnect(socket, head, context) {
|
|
|
2602
2626
|
config.capture?.connect(target, session !== void 0);
|
|
2603
2627
|
socket.write(CONNECT_ESTABLISHED);
|
|
2604
2628
|
if (session !== void 0) {
|
|
2605
|
-
session.tlsAcceptor.accept(socket);
|
|
2629
|
+
session.tlsAcceptor.accept(socket, capability);
|
|
2606
2630
|
return;
|
|
2607
2631
|
}
|
|
2608
2632
|
void blindTunnel(socket, target, effects);
|
|
@@ -2714,10 +2738,11 @@ function realConnectEffects() {
|
|
|
2714
2738
|
return await listenOnce(0, onSocket);
|
|
2715
2739
|
},
|
|
2716
2740
|
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2741
|
+
let pendingContext = "";
|
|
2717
2742
|
const server = tls.createServer(
|
|
2718
2743
|
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2719
2744
|
(secure) => {
|
|
2720
|
-
onSecure(secure);
|
|
2745
|
+
onSecure(secure, pendingContext);
|
|
2721
2746
|
}
|
|
2722
2747
|
);
|
|
2723
2748
|
server.on("clientError", (_error, socket) => {
|
|
@@ -2725,7 +2750,8 @@ function realConnectEffects() {
|
|
|
2725
2750
|
});
|
|
2726
2751
|
return {
|
|
2727
2752
|
// tls.Server is a net.Server whose connection listener wraps the raw socket in the TLS handshake, and emitting the event by hand is what runs that listener on a socket this process already owns (the CONNECT half of the connection) rather than one the server accepted itself.
|
|
2728
|
-
accept: (socket) => {
|
|
2753
|
+
accept: (socket, context) => {
|
|
2754
|
+
pendingContext = context;
|
|
2729
2755
|
server.emit("connection", socket);
|
|
2730
2756
|
},
|
|
2731
2757
|
close: () => {
|
|
@@ -2739,9 +2765,8 @@ function realConnectEffects() {
|
|
|
2739
2765
|
socket.destroy();
|
|
2740
2766
|
});
|
|
2741
2767
|
if (onUpgrade !== void 0) {
|
|
2742
|
-
server.on("upgrade", (request3, socket) => {
|
|
2743
|
-
onUpgrade(request3);
|
|
2744
|
-
socket.destroy();
|
|
2768
|
+
server.on("upgrade", (request3, socket, head) => {
|
|
2769
|
+
onUpgrade(request3, socket, head);
|
|
2745
2770
|
});
|
|
2746
2771
|
}
|
|
2747
2772
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type * as net from "node:net";
|
|
3
|
+
import type { Duplex } from "node:stream";
|
|
3
4
|
import type { ConnectCapture, PassthroughObserver } from "./capture";
|
|
4
5
|
/**
|
|
5
6
|
* The front door's CONNECT surface, so an OAuth session can have agent-shim's routing AND Claude Code's Remote Control at once: Remote Control refuses any `ANTHROPIC_BASE_URL` other than the real API, but happily honours `HTTPS_PROXY`, so OAuth launches route at the proxy layer instead.
|
|
@@ -125,8 +126,8 @@ export interface ConnectListenerHandle {
|
|
|
125
126
|
}
|
|
126
127
|
/** Presents one leaf's server-side TLS on CONNECTed sockets. */
|
|
127
128
|
interface TlsAcceptor {
|
|
128
|
-
/** Starts the TLS handshake on a socket that has just received its CONNECT response. */
|
|
129
|
-
readonly accept: (socket: net.Socket) => void;
|
|
129
|
+
/** Starts the TLS handshake on a socket that has just received its CONNECT response, carrying `context` to the handshake's completion untouched: whatever the caller knows about this connection (its tunnel's capability) reaches `onSecure` without the acceptor interpreting it. */
|
|
130
|
+
readonly accept: (socket: net.Socket, context: string) => void;
|
|
130
131
|
/** Drops every session this acceptor is terminating. */
|
|
131
132
|
readonly close: () => void;
|
|
132
133
|
}
|
|
@@ -141,12 +142,12 @@ interface HttpParserSession {
|
|
|
141
142
|
export interface ConnectEffects {
|
|
142
143
|
/** Binds a loopback TCP listener; `preferredPort` is tried first and any free port used when it is taken (bind, do not probe: check-then-bind races). Resolves with the bound port and a close handle. */
|
|
143
144
|
readonly listenLoopback: (preferredPort: number | undefined, onSocket: (socket: net.Socket) => void) => Promise<ConnectListenerHandle>;
|
|
144
|
-
/** Builds the TLS terminator for one leaf, handing each successfully handshaked session to `onSecure
|
|
145
|
-
readonly createTlsAcceptor: (leaf: LeafCert, onSecure: (secure: net.Socket) => void, alpnProtocols?: readonly string[]) => TlsAcceptor;
|
|
145
|
+
/** Builds the TLS terminator for one leaf, handing each successfully handshaked session to `onSecure` together with the context its `accept` call carried. `alpnProtocols`, when given, is advertised during the handshake, which is how a tap host's session can offer HTTP/2 the way its real server does. */
|
|
146
|
+
readonly createTlsAcceptor: (leaf: LeafCert, onSecure: (secure: net.Socket, context: string) => void, alpnProtocols?: readonly string[]) => TlsAcceptor;
|
|
146
147
|
/** Opens a TLS connection to a real host, presenting its name as SNI, for a tap session's upstream half. `clientAlpn`, when given, is the protocol the client negotiated with the surface and the only one offered upstream, so the tap never changes the channel's protocol. */
|
|
147
148
|
readonly connectTlsUpstream: (host: string, port: number, clientAlpn: string | undefined) => Promise<net.Socket>;
|
|
148
|
-
/** Builds the HTTP parser bound to one request handler. `onUpgrade`, when given,
|
|
149
|
-
readonly createHttpSession: (handler: ConnectRequestHandler, onUpgrade?: (request: IncomingMessage) => void) => HttpParserSession;
|
|
149
|
+
/** Builds the HTTP parser bound to one request handler. `onUpgrade`, when given, owns every HTTP upgrade request arriving on a terminated session, socket and already-read bytes included; a session without one lets Node destroy the upgrade, its own no-listener behaviour. */
|
|
150
|
+
readonly createHttpSession: (handler: ConnectRequestHandler, onUpgrade?: (request: IncomingMessage, socket: Duplex, head: Buffer) => void) => HttpParserSession;
|
|
150
151
|
/** Opens a raw TCP connection for a blind tunnel. */
|
|
151
152
|
readonly connectTcp: (host: string, port: number) => Promise<net.Socket>;
|
|
152
153
|
/** Streams one request to `target` and the response back, never buffering a body. When `observer` is given, the forwarding drives it with additive listeners (request and response chunks arrive to both the pipe and the observer) so capturing never changes the forwarding's own flow control. */
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* The agent-shim-internal headers the launcher injects through `ANTHROPIC_CUSTOM_HEADERS` so the front door can identify and route a request's session. None of them may ever leave the machine: the identity step strips every one before a route sees the request, so no upstream, and not even the headroom hop, learns what they say.
|
|
4
|
+
*/
|
|
5
|
+
export declare const INTERNAL_HEADER_NAMES: readonly ["x-agent-shim-identity", "x-agent-shim-session", "x-agent-shim-headroom", "x-agent-shim-auth", "x-agent-shim-hop", "x-agent-shim-hop-id", "x-headroom-project-id", "x-headroom-base-url"];
|
|
6
|
+
/** Carries the launching identity's name (the `@name` the launch resolved), set by the launcher and stripped at the door. */
|
|
7
|
+
export declare const IDENTITY_HEADER = "x-agent-shim-identity";
|
|
8
|
+
/** Carries one launch's session id, generated by the launcher and stripped at the door; response middleware keys its observations by it. */
|
|
9
|
+
export declare const SESSION_HEADER = "x-agent-shim-session";
|
|
10
|
+
/** Set to `1` when the launch resolved headroom on, so the front door knows this session's requests must take the headroom hop; stripped at the door. */
|
|
11
|
+
export declare const HEADROOM_FLAG_HEADER = "x-agent-shim-headroom";
|
|
12
|
+
/**
|
|
13
|
+
* Carries the project identity headroom scopes its memory state to (the git repository root of the launch directory). Also injected by the launcher and stripped at the door: the headroom hop re-sets it on its own request, so it reaches headroom and nowhere else.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PROJECT_ID_HEADER = "x-headroom-project-id";
|
|
16
|
+
/**
|
|
17
|
+
* Headroom's per-request upstream selector, which the child used to carry and the door now owns: the door strips any inbound copy (a leftover from an older launcher's environment would otherwise redirect the daemon behind the door's back) and its hop sets it fresh when a route names an upstream.
|
|
18
|
+
*/
|
|
19
|
+
export declare const HEADROOM_BASE_URL_HEADER = "x-headroom-base-url";
|
|
20
|
+
/**
|
|
21
|
+
* The per-launch capability a client-facing listener requires before it routes anything: a random token the launcher generates, hands the child through `ANTHROPIC_CUSTOM_HEADERS`, and records in the door's session registry, so a loopback process that never launched through agent-shim cannot spend the session's credentials or quota. Stripped at the door like every internal header.
|
|
22
|
+
*/
|
|
23
|
+
export declare const AUTH_HEADER = "x-agent-shim-auth";
|
|
24
|
+
/**
|
|
25
|
+
* The capability the direct listener requires: a per-generation random secret held only in the door process's memory, set by the headroom hop on the requests it forwards back. A loopback process that finds the direct port still cannot use it, and nothing on disk ever holds the secret.
|
|
26
|
+
*/
|
|
27
|
+
export declare const HOP_SECRET_HEADER = "x-agent-shim-hop";
|
|
28
|
+
/**
|
|
29
|
+
* Names the credential custody record one headroom hop holds: a random id the hop sets on the request it hands headroom, alongside placeholders where the real credential headers were. The direct listener exchanges a live id (and only for the provider the record was made for) for the real headers before routing, so the credential itself never crosses headroom or the plain-HTTP leg back.
|
|
30
|
+
*/
|
|
31
|
+
export declare const HOP_ID_HEADER = "x-agent-shim-hop-id";
|
|
32
|
+
/** What the identity step learned about one request from the launcher-injected headers. */
|
|
33
|
+
export interface SessionIdentity {
|
|
34
|
+
/** The identity name the launch resolved, when it resolved one. */
|
|
35
|
+
readonly identity: string | undefined;
|
|
36
|
+
/** The per-launch session id, when the launcher injected one. */
|
|
37
|
+
readonly sessionId: string | undefined;
|
|
38
|
+
/** Whether this launch resolved headroom on, so its requests must pass through the headroom hop before their route. */
|
|
39
|
+
readonly headroom: boolean;
|
|
40
|
+
/** The project identity headroom scopes memory to, re-set on the headroom hop and sent nowhere else. */
|
|
41
|
+
readonly projectId: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** The outcome of the pipeline's first step: what the session is, and the headers that may actually leave the machine. */
|
|
44
|
+
export interface IdentifiedRequest {
|
|
45
|
+
readonly session: SessionIdentity;
|
|
46
|
+
/**
|
|
47
|
+
* The request's headers with every internal header removed. A route forwards these, and only these, onwards: the identity and session headers are for the front door alone and must never reach an upstream, the headroom hop, or a translator's backend call.
|
|
48
|
+
*/
|
|
49
|
+
readonly forwardableHeaders: IncomingHttpHeaders;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The identity step: splits a request's headers into what the front door may learn (the session) and what may leave the machine (everything else). Internal header names are matched case-insensitively, which is how the transport already normalises them.
|
|
53
|
+
*/
|
|
54
|
+
export declare function identifyRequest(headers: Readonly<IncomingHttpHeaders>): IdentifiedRequest;
|
|
55
|
+
/** The path prefix every provider-scoped request sits under: a routed session's base URL is the front door's address plus `/providers/<name>`, so one listener serves any number of providers, each with its own credential and translation settings. */
|
|
56
|
+
export declare const PROVIDER_PATH_PREFIX = "/providers/";
|
|
57
|
+
/** The base URL a provider's sessions are pointed at: HTTPS, because the child sends its provider credential on every request and only a listener presenting a leaf signed by agent-shim's CA may receive it. */
|
|
58
|
+
export declare function providerBaseUrl(port: number, provider: string): string;
|
|
59
|
+
/** The bare origin of a loopback listener, which is what a headroom hop is told to forward to: headroom appends the client's own request path to it. */
|
|
60
|
+
export declare function directOrigin(port: number): string;
|
|
61
|
+
/** Splits `/providers/<name>/<rest>` into the provider name and the rest, or undefined for any other path. */
|
|
62
|
+
export declare function parseProviderPath(path: string): {
|
|
63
|
+
readonly provider: string;
|
|
64
|
+
readonly rest: string;
|
|
65
|
+
} | undefined;
|
|
66
|
+
/** One request as a route sees it: the transport's own request and abort signal, plus what the identity step resolved. */
|
|
67
|
+
export interface RoutedRequest {
|
|
68
|
+
readonly method: string;
|
|
69
|
+
/** The request target exactly as received, path and query together. */
|
|
70
|
+
readonly url: string;
|
|
71
|
+
/** The request's headers with every internal header already stripped: exactly what a route may forward onwards. */
|
|
72
|
+
readonly headers: IncomingHttpHeaders;
|
|
73
|
+
/** The request's body stream: a route either pipes it onward untouched or reads it whole, never both. */
|
|
74
|
+
readonly body: IncomingMessage;
|
|
75
|
+
/** Aborted the moment the client goes away; a route aborts its own upstream work with it. */
|
|
76
|
+
readonly signal: AbortSignal;
|
|
77
|
+
readonly session: SessionIdentity;
|
|
78
|
+
}
|
|
79
|
+
/** The response half a route writes through, so the pipeline can run response middleware at the head and apply backpressure uniformly. */
|
|
80
|
+
export interface RoutedResponse {
|
|
81
|
+
/** Whether the head has already been written. */
|
|
82
|
+
readonly headersSent: boolean;
|
|
83
|
+
/** Writes the response head, running every response observer first. Must be called exactly once, before any write. */
|
|
84
|
+
readonly start: (status: number, headers: Readonly<Record<string, string>>) => void;
|
|
85
|
+
/** Sends the head on its way before any body chunk exists: a streaming response's client should see its headers the moment they are known. */
|
|
86
|
+
readonly flush: () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Writes one body chunk, waiting for the socket to take it when the client is slow (backpressure: a slow client slows the route, and its upstream, instead of growing a buffer without bound). Resolves early, without writing, once the client is gone.
|
|
89
|
+
*/
|
|
90
|
+
readonly write: (chunk: string | Uint8Array) => Promise<void>;
|
|
91
|
+
/** Ends the response. */
|
|
92
|
+
readonly end: () => void;
|
|
93
|
+
/** Drops the connection: an upstream failure after the head went out, where no status can be sent any more. */
|
|
94
|
+
readonly destroy: () => void;
|
|
95
|
+
}
|
|
96
|
+
/** One pluggable destination the front door routes a request to. */
|
|
97
|
+
export interface FrontDoorRoute {
|
|
98
|
+
/** The route's name, for logs and for response middleware context. */
|
|
99
|
+
readonly name: string;
|
|
100
|
+
/**
|
|
101
|
+
* Whether the headroom hop may sit in front of this route. Headroom needs the Anthropic-shaped request, so a protocol translator built on an Anthropic-compatible surface opts in, and a route whose request shape headroom could not understand opts out.
|
|
102
|
+
*/
|
|
103
|
+
readonly headroomEligible: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* The upstream headroom is told to use (its per-request base URL header) when the hop sits in front of this route. When defined it is always this door's direct listener, which is what lets the hop keep the route's credential in custody and the direct listener restore it; a route must never name any other address here. Undefined leaves headroom's own default upstream in charge, which is Claude Code's API.
|
|
106
|
+
*/
|
|
107
|
+
readonly headroomUpstream: string | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Serves the request directly: headroom has already been applied, or does not apply to this route. Writes the response through `response` and resolves once the response has ended.
|
|
110
|
+
*/
|
|
111
|
+
readonly serve: (request: RoutedRequest, response: RoutedResponse) => Promise<void>;
|
|
112
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type * as net from "node:net";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
2
3
|
import type { StreamTap } from "./capture";
|
|
3
4
|
/**
|
|
4
5
|
* Pumps one already-TLS-terminated stream byte for byte to its real host over a fresh TLS connection, teeing every chunk in both directions into `tap` when one is given. This is the instrumentation for a host whose protocol the surface does not parse: the claude.ai control plane carries Remote Control on a channel that mixes shapes (observed under capture: an HTTP/1.1-shaped token request first, then binary HTTP/2 frames around it), so parsing it as either protocol would destroy or distort it. Pass everything through unchanged and record, never interpret: the frames are decoded offline, and a protocol the door does not understand keeps working exactly as the blind tunnel served it.
|
|
5
6
|
*
|
|
6
7
|
* Lifecycle mirrors `blindTunnel`: whoever goes away first takes the other side with it, so neither half of a tap session ever outlives its peer. The tap's listeners ride beside the pipes, additive like every other observer in this surface, so a recording failure fails loudly rather than silently stopping a diagnostic.
|
|
7
8
|
*/
|
|
8
|
-
export declare function pumpTapSession(secure:
|
|
9
|
+
export declare function pumpTapSession(secure: Duplex, openUpstream: (clientAlpn: string | undefined) => Promise<net.Socket>, tap: StreamTap | undefined, prefix?: Readonly<Buffer>): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-use",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joseph Mearman <joseph@mearman.co.uk>",
|