agent-shim 7.2.0 → 7.4.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 +34 -24
- package/dist/index.cjs +29 -19
- package/dist/index.mjs +28 -19
- package/dist/types/frontdoor/capture.d.ts +4 -0
- package/dist/types/frontdoor/connect.d.ts +17 -9
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -61051,7 +61051,7 @@ function resolveLaunchIdentity(paths, cwd) {
|
|
|
61051
61051
|
// package.json
|
|
61052
61052
|
var package_default = {
|
|
61053
61053
|
name: "agent-shim",
|
|
61054
|
-
version: "7.
|
|
61054
|
+
version: "7.4.0",
|
|
61055
61055
|
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.",
|
|
61056
61056
|
license: "MIT",
|
|
61057
61057
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -64432,6 +64432,10 @@ function redactBody(chunk) {
|
|
|
64432
64432
|
return redactText(text2);
|
|
64433
64433
|
}
|
|
64434
64434
|
}
|
|
64435
|
+
var CAPTURE_ENV = "AGENT_SHIM_FRONTDOOR_CAPTURE";
|
|
64436
|
+
function captureFromEnv(env, logsDir) {
|
|
64437
|
+
return env[CAPTURE_ENV] === "1" ? createFileCapture(path28.join(logsDir, "frontdoor-capture.jsonl")) : void 0;
|
|
64438
|
+
}
|
|
64435
64439
|
function createFileCapture(file2, now = () => /* @__PURE__ */ new Date()) {
|
|
64436
64440
|
fs11.mkdirSync(path28.dirname(file2), { recursive: true });
|
|
64437
64441
|
let nextId = 0;
|
|
@@ -64855,6 +64859,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
64855
64859
|
|
|
64856
64860
|
// src/frontdoor/connect.ts
|
|
64857
64861
|
var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
64862
|
+
var CONNECT_INTERCEPT_HOSTS = [CONNECT_INTERCEPT_HOST, "platform.claude.com"];
|
|
64858
64863
|
var ROUTED_PATH_PREFIX = "/v1/";
|
|
64859
64864
|
var HTTPS_PORT = 443;
|
|
64860
64865
|
var MAX_PORT = 65535;
|
|
@@ -64894,9 +64899,6 @@ function parseConnectTarget(authority) {
|
|
|
64894
64899
|
}
|
|
64895
64900
|
return { host, port };
|
|
64896
64901
|
}
|
|
64897
|
-
function isInterceptedHost(host, interceptHost) {
|
|
64898
|
-
return host === interceptHost;
|
|
64899
|
-
}
|
|
64900
64902
|
function servedByPipeline(path38) {
|
|
64901
64903
|
return path38?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
64902
64904
|
}
|
|
@@ -64966,28 +64968,34 @@ function caParses(ca) {
|
|
|
64966
64968
|
}
|
|
64967
64969
|
}
|
|
64968
64970
|
async function startConnectServer(config2, effects, preferredPort) {
|
|
64969
|
-
const
|
|
64970
|
-
|
|
64971
|
-
|
|
64972
|
-
|
|
64973
|
-
|
|
64974
|
-
|
|
64975
|
-
|
|
64976
|
-
|
|
64977
|
-
|
|
64978
|
-
httpSession.
|
|
64971
|
+
const onUpgrade = config2.capture === void 0 ? void 0 : (request6) => config2.capture?.upgrade(request6);
|
|
64972
|
+
const sessions = config2.interceptHosts.map((host) => {
|
|
64973
|
+
const handler = (request6, response) => {
|
|
64974
|
+
if (host === config2.routedHost && servedByPipeline(request6.url)) {
|
|
64975
|
+
config2.serveRouted(request6, response);
|
|
64976
|
+
return;
|
|
64977
|
+
}
|
|
64978
|
+
effects.forwardHttp(config2.upstreamFor(host), request6, response, config2.capture?.observePassthrough(request6));
|
|
64979
|
+
};
|
|
64980
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
64981
|
+
const tlsAcceptor = effects.createTlsAcceptor(config2.leafFor(host), (secure) => {
|
|
64982
|
+
httpSession.serve(secure);
|
|
64983
|
+
});
|
|
64984
|
+
return { host, httpSession, tlsAcceptor };
|
|
64979
64985
|
});
|
|
64980
64986
|
const ledger = createConnectionLedger(config2.limits, config2.isLiveCapability);
|
|
64981
64987
|
const listener = await effects.listenLoopback(preferredPort, (socket) => {
|
|
64982
|
-
handleConnect(socket, { config: config2, effects,
|
|
64988
|
+
handleConnect(socket, { config: config2, effects, sessions, ledger });
|
|
64983
64989
|
});
|
|
64984
64990
|
return {
|
|
64985
64991
|
port: listener.port,
|
|
64986
64992
|
close: async () => {
|
|
64987
64993
|
ledger.close();
|
|
64988
64994
|
await listener.close();
|
|
64989
|
-
tlsAcceptor
|
|
64990
|
-
|
|
64995
|
+
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
64996
|
+
tlsAcceptor.close();
|
|
64997
|
+
httpSession.close();
|
|
64998
|
+
}
|
|
64991
64999
|
}
|
|
64992
65000
|
};
|
|
64993
65001
|
}
|
|
@@ -65107,7 +65115,7 @@ function proxyAuthorizationOf(head) {
|
|
|
65107
65115
|
return values.length === 1 ? value : void 0;
|
|
65108
65116
|
}
|
|
65109
65117
|
function routeConnect(socket, head, context) {
|
|
65110
|
-
const { config: config2, effects,
|
|
65118
|
+
const { config: config2, effects, sessions, ledger } = context;
|
|
65111
65119
|
const credential = proxyAuthorizationOf(head);
|
|
65112
65120
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
65113
65121
|
if (capability === void 0 || !config2.isLiveCapability(capability)) {
|
|
@@ -65126,10 +65134,11 @@ function routeConnect(socket, head, context) {
|
|
|
65126
65134
|
refuse(socket, REFUSAL.serviceUnavailable);
|
|
65127
65135
|
return;
|
|
65128
65136
|
}
|
|
65129
|
-
|
|
65137
|
+
const session = sessions.find((entry) => entry.host === target.host);
|
|
65138
|
+
config2.capture?.connect(target, session !== void 0);
|
|
65130
65139
|
socket.write(CONNECT_ESTABLISHED);
|
|
65131
|
-
if (
|
|
65132
|
-
tlsAcceptor.accept(socket);
|
|
65140
|
+
if (session !== void 0) {
|
|
65141
|
+
session.tlsAcceptor.accept(socket);
|
|
65133
65142
|
return;
|
|
65134
65143
|
}
|
|
65135
65144
|
void blindTunnel(socket, target, effects);
|
|
@@ -66957,13 +66966,14 @@ function realFrontDoorSupervisorPorts(paths) {
|
|
|
66957
66966
|
},
|
|
66958
66967
|
startConnectListener: async (preferredPort) => {
|
|
66959
66968
|
const leafFor = createLeafCache(loadCa(), () => /* @__PURE__ */ new Date());
|
|
66960
|
-
const capture = process.env
|
|
66969
|
+
const capture = captureFromEnv(process.env, paths.logsDir);
|
|
66961
66970
|
if (capture !== void 0) {
|
|
66962
66971
|
log(`capture enabled, recording CONNECT targets and piped exchanges to ${import_node_path29.default.join(paths.logsDir, "frontdoor-capture.jsonl")}`);
|
|
66963
66972
|
}
|
|
66964
66973
|
const server = await startConnectServer(
|
|
66965
66974
|
{
|
|
66966
|
-
|
|
66975
|
+
interceptHosts: CONNECT_INTERCEPT_HOSTS,
|
|
66976
|
+
routedHost: CONNECT_INTERCEPT_HOST,
|
|
66967
66977
|
serveRouted: (request6, response) => {
|
|
66968
66978
|
const abort = new AbortController();
|
|
66969
66979
|
response.on("close", () => {
|
|
@@ -66984,7 +66994,7 @@ function realFrontDoorSupervisorPorts(paths) {
|
|
|
66984
66994
|
);
|
|
66985
66995
|
},
|
|
66986
66996
|
leafFor,
|
|
66987
|
-
|
|
66997
|
+
upstreamFor: (host) => ({ host, port: HTTPS_PORT, tls: true }),
|
|
66988
66998
|
isLiveCapability: isLiveToken,
|
|
66989
66999
|
limits: CONNECT_LIMITS,
|
|
66990
67000
|
...capture === void 0 ? {} : { capture }
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AMBIENT_CREDENTIAL_VARS: () => AMBIENT_CREDENTIAL_VARS,
|
|
34
34
|
CONNECT_INTERCEPT_HOST: () => CONNECT_INTERCEPT_HOST,
|
|
35
|
+
CONNECT_INTERCEPT_HOSTS: () => CONNECT_INTERCEPT_HOSTS,
|
|
35
36
|
CONNECT_LIMITS: () => CONNECT_LIMITS,
|
|
36
37
|
FARM_MANIFEST_FILENAME: () => FARM_MANIFEST_FILENAME,
|
|
37
38
|
FrontDoorStartError: () => FrontDoorStartError,
|
|
@@ -2325,6 +2326,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2325
2326
|
|
|
2326
2327
|
// src/frontdoor/connect.ts
|
|
2327
2328
|
var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
2329
|
+
var CONNECT_INTERCEPT_HOSTS = [CONNECT_INTERCEPT_HOST, "platform.claude.com"];
|
|
2328
2330
|
var ROUTED_PATH_PREFIX = "/v1/";
|
|
2329
2331
|
var HTTPS_PORT = 443;
|
|
2330
2332
|
var MAX_PORT = 65535;
|
|
@@ -2364,8 +2366,8 @@ function parseConnectTarget(authority) {
|
|
|
2364
2366
|
}
|
|
2365
2367
|
return { host, port };
|
|
2366
2368
|
}
|
|
2367
|
-
function isInterceptedHost(host,
|
|
2368
|
-
return host
|
|
2369
|
+
function isInterceptedHost(host, interceptHosts) {
|
|
2370
|
+
return interceptHosts.includes(host);
|
|
2369
2371
|
}
|
|
2370
2372
|
function servedByPipeline(path8) {
|
|
2371
2373
|
return path8?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
@@ -2435,28 +2437,34 @@ function caParses(ca) {
|
|
|
2435
2437
|
}
|
|
2436
2438
|
}
|
|
2437
2439
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
httpSession.
|
|
2440
|
+
const onUpgrade = config.capture === void 0 ? void 0 : (request3) => config.capture?.upgrade(request3);
|
|
2441
|
+
const sessions = config.interceptHosts.map((host) => {
|
|
2442
|
+
const handler = (request3, response) => {
|
|
2443
|
+
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2444
|
+
config.serveRouted(request3, response);
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
|
|
2448
|
+
};
|
|
2449
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
2450
|
+
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure) => {
|
|
2451
|
+
httpSession.serve(secure);
|
|
2452
|
+
});
|
|
2453
|
+
return { host, httpSession, tlsAcceptor };
|
|
2448
2454
|
});
|
|
2449
2455
|
const ledger = createConnectionLedger(config.limits, config.isLiveCapability);
|
|
2450
2456
|
const listener = await effects.listenLoopback(preferredPort, (socket) => {
|
|
2451
|
-
handleConnect(socket, { config, effects,
|
|
2457
|
+
handleConnect(socket, { config, effects, sessions, ledger });
|
|
2452
2458
|
});
|
|
2453
2459
|
return {
|
|
2454
2460
|
port: listener.port,
|
|
2455
2461
|
close: async () => {
|
|
2456
2462
|
ledger.close();
|
|
2457
2463
|
await listener.close();
|
|
2458
|
-
tlsAcceptor
|
|
2459
|
-
|
|
2464
|
+
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
2465
|
+
tlsAcceptor.close();
|
|
2466
|
+
httpSession.close();
|
|
2467
|
+
}
|
|
2460
2468
|
}
|
|
2461
2469
|
};
|
|
2462
2470
|
}
|
|
@@ -2576,7 +2584,7 @@ function proxyAuthorizationOf(head) {
|
|
|
2576
2584
|
return values.length === 1 ? value : void 0;
|
|
2577
2585
|
}
|
|
2578
2586
|
function routeConnect(socket, head, context) {
|
|
2579
|
-
const { config, effects,
|
|
2587
|
+
const { config, effects, sessions, ledger } = context;
|
|
2580
2588
|
const credential = proxyAuthorizationOf(head);
|
|
2581
2589
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
2582
2590
|
if (capability === void 0 || !config.isLiveCapability(capability)) {
|
|
@@ -2595,10 +2603,11 @@ function routeConnect(socket, head, context) {
|
|
|
2595
2603
|
refuse(socket, REFUSAL.serviceUnavailable);
|
|
2596
2604
|
return;
|
|
2597
2605
|
}
|
|
2598
|
-
|
|
2606
|
+
const session = sessions.find((entry) => entry.host === target.host);
|
|
2607
|
+
config.capture?.connect(target, session !== void 0);
|
|
2599
2608
|
socket.write(CONNECT_ESTABLISHED);
|
|
2600
|
-
if (
|
|
2601
|
-
tlsAcceptor.accept(socket);
|
|
2609
|
+
if (session !== void 0) {
|
|
2610
|
+
session.tlsAcceptor.accept(socket);
|
|
2602
2611
|
return;
|
|
2603
2612
|
}
|
|
2604
2613
|
void blindTunnel(socket, target, effects);
|
|
@@ -3601,6 +3610,7 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
3601
3610
|
0 && (module.exports = {
|
|
3602
3611
|
AMBIENT_CREDENTIAL_VARS,
|
|
3603
3612
|
CONNECT_INTERCEPT_HOST,
|
|
3613
|
+
CONNECT_INTERCEPT_HOSTS,
|
|
3604
3614
|
CONNECT_LIMITS,
|
|
3605
3615
|
FARM_MANIFEST_FILENAME,
|
|
3606
3616
|
FrontDoorStartError,
|
package/dist/index.mjs
CHANGED
|
@@ -2251,6 +2251,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2251
2251
|
|
|
2252
2252
|
// src/frontdoor/connect.ts
|
|
2253
2253
|
var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
2254
|
+
var CONNECT_INTERCEPT_HOSTS = [CONNECT_INTERCEPT_HOST, "platform.claude.com"];
|
|
2254
2255
|
var ROUTED_PATH_PREFIX = "/v1/";
|
|
2255
2256
|
var HTTPS_PORT = 443;
|
|
2256
2257
|
var MAX_PORT = 65535;
|
|
@@ -2290,8 +2291,8 @@ function parseConnectTarget(authority) {
|
|
|
2290
2291
|
}
|
|
2291
2292
|
return { host, port };
|
|
2292
2293
|
}
|
|
2293
|
-
function isInterceptedHost(host,
|
|
2294
|
-
return host
|
|
2294
|
+
function isInterceptedHost(host, interceptHosts) {
|
|
2295
|
+
return interceptHosts.includes(host);
|
|
2295
2296
|
}
|
|
2296
2297
|
function servedByPipeline(path8) {
|
|
2297
2298
|
return path8?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
@@ -2361,28 +2362,34 @@ function caParses(ca) {
|
|
|
2361
2362
|
}
|
|
2362
2363
|
}
|
|
2363
2364
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2364
|
-
const
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
httpSession.
|
|
2365
|
+
const onUpgrade = config.capture === void 0 ? void 0 : (request3) => config.capture?.upgrade(request3);
|
|
2366
|
+
const sessions = config.interceptHosts.map((host) => {
|
|
2367
|
+
const handler = (request3, response) => {
|
|
2368
|
+
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2369
|
+
config.serveRouted(request3, response);
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
|
|
2373
|
+
};
|
|
2374
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
2375
|
+
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure) => {
|
|
2376
|
+
httpSession.serve(secure);
|
|
2377
|
+
});
|
|
2378
|
+
return { host, httpSession, tlsAcceptor };
|
|
2374
2379
|
});
|
|
2375
2380
|
const ledger = createConnectionLedger(config.limits, config.isLiveCapability);
|
|
2376
2381
|
const listener = await effects.listenLoopback(preferredPort, (socket) => {
|
|
2377
|
-
handleConnect(socket, { config, effects,
|
|
2382
|
+
handleConnect(socket, { config, effects, sessions, ledger });
|
|
2378
2383
|
});
|
|
2379
2384
|
return {
|
|
2380
2385
|
port: listener.port,
|
|
2381
2386
|
close: async () => {
|
|
2382
2387
|
ledger.close();
|
|
2383
2388
|
await listener.close();
|
|
2384
|
-
tlsAcceptor
|
|
2385
|
-
|
|
2389
|
+
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
2390
|
+
tlsAcceptor.close();
|
|
2391
|
+
httpSession.close();
|
|
2392
|
+
}
|
|
2386
2393
|
}
|
|
2387
2394
|
};
|
|
2388
2395
|
}
|
|
@@ -2502,7 +2509,7 @@ function proxyAuthorizationOf(head) {
|
|
|
2502
2509
|
return values.length === 1 ? value : void 0;
|
|
2503
2510
|
}
|
|
2504
2511
|
function routeConnect(socket, head, context) {
|
|
2505
|
-
const { config, effects,
|
|
2512
|
+
const { config, effects, sessions, ledger } = context;
|
|
2506
2513
|
const credential = proxyAuthorizationOf(head);
|
|
2507
2514
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
2508
2515
|
if (capability === void 0 || !config.isLiveCapability(capability)) {
|
|
@@ -2521,10 +2528,11 @@ function routeConnect(socket, head, context) {
|
|
|
2521
2528
|
refuse(socket, REFUSAL.serviceUnavailable);
|
|
2522
2529
|
return;
|
|
2523
2530
|
}
|
|
2524
|
-
|
|
2531
|
+
const session = sessions.find((entry) => entry.host === target.host);
|
|
2532
|
+
config.capture?.connect(target, session !== void 0);
|
|
2525
2533
|
socket.write(CONNECT_ESTABLISHED);
|
|
2526
|
-
if (
|
|
2527
|
-
tlsAcceptor.accept(socket);
|
|
2534
|
+
if (session !== void 0) {
|
|
2535
|
+
session.tlsAcceptor.accept(socket);
|
|
2528
2536
|
return;
|
|
2529
2537
|
}
|
|
2530
2538
|
void blindTunnel(socket, target, effects);
|
|
@@ -3526,6 +3534,7 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
3526
3534
|
export {
|
|
3527
3535
|
AMBIENT_CREDENTIAL_VARS,
|
|
3528
3536
|
CONNECT_INTERCEPT_HOST,
|
|
3537
|
+
CONNECT_INTERCEPT_HOSTS,
|
|
3529
3538
|
CONNECT_LIMITS,
|
|
3530
3539
|
FARM_MANIFEST_FILENAME,
|
|
3531
3540
|
FrontDoorStartError,
|
|
@@ -38,6 +38,10 @@ export declare function redactHeaders(headers: Readonly<IncomingHttpHeaders>): I
|
|
|
38
38
|
* Redacts one body excerpt for the capture file. A body that parses as JSON is redacted structurally (key-named credentials go even when their shape matches no pattern); anything else gets the substring patterns, which still catch the bearer and JWT forms an OAuth refresh or a Remote Control frame carries. Invalid UTF-8 decodes with replacement characters, which is fine: the excerpt exists to be read, not to be re-encoded.
|
|
39
39
|
*/
|
|
40
40
|
export declare function redactBody(chunk: Readonly<Buffer>): string;
|
|
41
|
+
/**
|
|
42
|
+
* The capture the door runs with, or undefined when the environment did not ask for one. Anything but `1` means off, the same exact-value vocabulary the other `AGENT_SHIM_*` diagnostics use, so a stray value never silently records traffic.
|
|
43
|
+
*/
|
|
44
|
+
export declare function captureFromEnv(env: NodeJS.ProcessEnv, logsDir: string): ConnectCapture | undefined;
|
|
41
45
|
/**
|
|
42
46
|
* Builds the file-backed capture: one JSON object per line, appended to `file` (conventionally `logs/frontdoor-capture.jsonl`), never read back by the door itself. The directory is created up front rather than per record, so a capture started against a fresh state root cannot race its own first append.
|
|
43
47
|
*/
|
|
@@ -6,10 +6,14 @@ import type { ConnectCapture, PassthroughObserver } from "./capture";
|
|
|
6
6
|
*
|
|
7
7
|
* Nothing is tunnelled or intercepted for a client that has not authenticated: every CONNECT request must present a live launch's capability as its proxy credential (`Proxy-Authorization: Basic`, which clients derive from the credential in the `HTTPS_PROXY` URL the launcher sets), or it is answered 407 before any target is dialled, so the port is no open proxy for other local processes. Pending and authenticated connections are bounded by `ConnectLimits`.
|
|
8
8
|
*
|
|
9
|
-
* Every authenticated CONNECT target is blind-tunnelled byte for byte EXCEPT the intercept
|
|
9
|
+
* Every authenticated CONNECT target is blind-tunnelled byte for byte EXCEPT the intercept hosts (Claude Code's own API and the claude.ai control plane), whose TLS this surface terminates with leaf certificates signed by a locally generated CA. On the API host's terminated session, the paths the front door routes (`/v1/`) are handed to the same ordered pipeline the provider listener serves (the client's Authorization header passes through untouched), and every other path there, and every path on the control plane's session, is piped by this surface to that session's own real upstream over TLS, so Remote Control's streaming, OAuth refreshes, and unknown endpoints bypass the pipeline entirely. When a `capture` is configured, those piped exchanges and every CONNECT target are recorded through it (see `./capture.ts`); the forwarding itself is identical either way.
|
|
10
10
|
*/
|
|
11
|
-
/** The
|
|
11
|
+
/** The API host: Claude Code's own upstream, the one host whose `/v1/` paths the routed pipeline serves. */
|
|
12
12
|
export declare const CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
13
|
+
/**
|
|
14
|
+
* Every CONNECT host whose TLS this surface terminates, each with its own leaf and its own HTTP parser: the API host above, and `platform.claude.com`, the claude.ai control plane where Remote Control's long-lived channel and its handshake live. Terminating the second one is what makes a Remote Control login rejection observable at all: its bytes cross this surface either way, and blind-tunnelling them hides exactly the exchange that failed. Only the API host's `/v1/` paths ever reach the routed pipeline; everything else on either session is piped to that session's own real upstream, so the pipeline's identification and authorisation are never asked to serve another host's traffic.
|
|
15
|
+
*/
|
|
16
|
+
export declare const CONNECT_INTERCEPT_HOSTS: readonly string[];
|
|
13
17
|
/** Paths under this prefix are what the routed pipeline serves; everything else on the terminated session goes to the real upstream. */
|
|
14
18
|
export declare const ROUTED_PATH_PREFIX = "/v1/";
|
|
15
19
|
/** The port HTTPS is served on, both the default when a CONNECT authority names none and the port non-headroom paths are piped to on the real upstream. */
|
|
@@ -52,8 +56,8 @@ export interface ConnectTarget {
|
|
|
52
56
|
}
|
|
53
57
|
/** Parses a CONNECT authority (`host:port`, or a bare `host` meaning 443), or undefined when it names no host or a non-numeric port. */
|
|
54
58
|
export declare function parseConnectTarget(authority: string): ConnectTarget | undefined;
|
|
55
|
-
/** Whether a CONNECT target's host (port already stripped) is
|
|
56
|
-
export declare function isInterceptedHost(host: string,
|
|
59
|
+
/** Whether a CONNECT target's host (port already stripped) is one whose TLS this proxy terminates. */
|
|
60
|
+
export declare function isInterceptedHost(host: string, interceptHosts: readonly string[]): boolean;
|
|
57
61
|
/** Whether a request path on the terminated session is one the routed pipeline serves. The query string stays part of the path: `/v1/messages?beta=true` is still the pipeline's. */
|
|
58
62
|
export declare function servedByPipeline(path: string | undefined): boolean;
|
|
59
63
|
/**
|
|
@@ -142,16 +146,20 @@ export interface ConnectEffects {
|
|
|
142
146
|
}
|
|
143
147
|
/** Everything the connect surface needs to route, resolved before it starts. */
|
|
144
148
|
export interface ConnectServerConfig {
|
|
145
|
-
/** The CONNECT
|
|
146
|
-
readonly
|
|
149
|
+
/** The CONNECT hosts whose TLS get terminated, each with its own leaf, its own HTTP parser and its own upstream. */
|
|
150
|
+
readonly interceptHosts: readonly string[];
|
|
151
|
+
/**
|
|
152
|
+
* The one intercept host whose `/v1/` paths `serveRouted` handles: the API host, whose pipeline identification, authorisation and headroom hop are about Anthropic sessions and no other host's traffic.
|
|
153
|
+
*/
|
|
154
|
+
readonly routedHost: string;
|
|
147
155
|
/**
|
|
148
156
|
* Serves one routed path (`/v1/...`) from the terminated session: the same ordered pipeline the provider listener hands requests to, so an OAuth session and a provider session run identical identification, middleware and routing.
|
|
149
157
|
*/
|
|
150
158
|
readonly serveRouted: ConnectRequestHandler;
|
|
151
|
-
/** The leaf to terminate
|
|
159
|
+
/** The leaf to terminate each intercept host with, minted and cached per host. */
|
|
152
160
|
readonly leafFor: (host: string) => LeafCert;
|
|
153
|
-
/** Where non-routed paths on
|
|
154
|
-
readonly
|
|
161
|
+
/** Where non-routed paths on one host's terminated session are piped: that host's own real upstream over TLS. */
|
|
162
|
+
readonly upstreamFor: (host: string) => ConnectForwardTarget;
|
|
155
163
|
/** Whether a capability presented as a CONNECT request's proxy credential belongs to a live launch: the same constant-time check against the same registry the routed pipeline's admission makes, read fresh on every call because launches come and go. */
|
|
156
164
|
readonly isLiveCapability: (token: string) => boolean;
|
|
157
165
|
/** The connection deadlines and caps; production passes `CONNECT_LIMITS`. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export { resolveDecisions, topLevelNames, type ResolveDecisionsInput, type ResolvedState } from "./resolve/pipeline";
|
|
12
12
|
export type { CompiledRule, Decision, Diagnostic, EliminatedRule, EntryFact, EntryFacts, FlattenedCascade, Layer, LayerId } from "./resolve/types";
|
|
13
13
|
export { buildEntryFacts, carryOver, FARM_MANIFEST_FILENAME, readFarmManifest, recoverFarm, recoveryDiagnostics, resyncFarm, type BuildEntryFactsParams, type CarryOverParams, type CarryOverResult, type RecoverFarmParams, type RecoveryResult, type ResyncFarmParams, type ResyncFarmResult, } from "./launcher/farm";
|
|
14
|
-
export { CONNECT_INTERCEPT_HOST, CONNECT_LIMITS, createLeafCache, ensureCa, forwardableHeaders, generateCa, isInterceptedHost, mintLeaf, parseConnectTarget, realConnectCertStore, realConnectEffects, ROUTED_PATH_PREFIX, servedByPipeline, startConnectServer, type CaMaterial, type ConnectCertStore, type ConnectEffects, type ConnectLimits, type ConnectServerConfig, type ConnectServerHandle, type ConnectTarget, type LeafCert, } from "./frontdoor/connect";
|
|
14
|
+
export { CONNECT_INTERCEPT_HOST, CONNECT_INTERCEPT_HOSTS, CONNECT_LIMITS, createLeafCache, ensureCa, forwardableHeaders, generateCa, isInterceptedHost, mintLeaf, parseConnectTarget, realConnectCertStore, realConnectEffects, ROUTED_PATH_PREFIX, servedByPipeline, startConnectServer, type CaMaterial, type ConnectCertStore, type ConnectEffects, type ConnectLimits, type ConnectServerConfig, type ConnectServerHandle, type ConnectTarget, type LeafCert, } from "./frontdoor/connect";
|
|
15
15
|
export { ensureFrontDoor, FrontDoorStartError, type EnsureFrontDoorPorts } from "./frontdoor/ensure";
|
|
16
16
|
export { runFrontDoorSupervisor, type FrontDoorSupervisorPorts, type RunFrontDoorSupervisorOptions } from "./frontdoor/supervisor";
|
|
17
17
|
export { listUsageSnapshots, readUsageSnapshot, snapshotPath, UsageSnapshotError, type UsageReadFs } from "./usage/read";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-shim",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.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>",
|