claude-use 4.5.0 → 5.0.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.
@@ -42,6 +42,10 @@ export interface FarmFs {
42
42
  readonly readFileUtf8: (path: string) => string | undefined;
43
43
  /** Writes a file's full contents as UTF-8 text, creating or truncating it. */
44
44
  readonly writeFileUtf8: (path: string, contents: string) => void;
45
+ /** Creates a directory (and any missing parents) readable only by its owner: mode 0700, enforced on the leaf even when it already existed with a wider mode. For a directory holding capabilities another local account must never read. */
46
+ readonly mkdirPrivate: (path: string) => void;
47
+ /** Writes a file's full contents as UTF-8 text readable only by its owner (mode 0600), atomically: a temporary sibling is written at that mode and renamed into place, so no reader ever sees a partial file or a wider mode. */
48
+ readonly writeFilePrivate: (path: string, contents: string) => void;
45
49
  /** Creates a file only if it does not already exist, returning false when it does. This is the exclusive-create the identity lock depends on for its mutual exclusion — a read-then-write pair would race. */
46
50
  readonly writeFileExclusive: (path: string, contents: string) => boolean;
47
51
  /** A stable content hash of one file, or undefined when it is missing or is not a regular file. Used only to recognise farm data that has already been adopted into the canonical tree. */
@@ -89,14 +93,10 @@ export interface LogPort {
89
93
  readonly warn: (message: string) => void;
90
94
  readonly error: (message: string) => void;
91
95
  }
92
- /** The running headroom daemon one launch routed through, as `HeadroomPort.ensure` reports it. */
96
+ /** The running headroom daemon one launch routed through, as `HeadroomPort.ensure` reports it. The front door owns the session's routing; this names the daemon the door's headroom hop forwards to, and the project identity headroom scopes memory to. */
93
97
  export interface HeadroomUp {
94
- /** The loopback port the local headroom proxy listens on. */
98
+ /** The loopback port the headroom daemon listens on: what the door's headroom hop reads live and `HEADROOM_PROXY_URL` names for anything else that wants the daemon. */
95
99
  readonly port: number;
96
- /** The loopback port the supervisor's MITM CONNECT proxy listens on: what an OAuth launch points HTTPS_PROXY at. */
97
- readonly mitmPort: number;
98
- /** Path to the MITM proxy's CA certificate, what an OAuth launch points NODE_EXTRA_CA_CERTS at so the child trusts the terminated TLS. */
99
- readonly caCertPath: string;
100
100
  /** The project identity headroom scopes memory state to: the git repo root of the launch directory, or the directory itself outside a repository. */
101
101
  readonly projectId: string;
102
102
  }
@@ -110,12 +110,24 @@ export interface HeadroomPort {
110
110
  readonly release: () => void;
111
111
  }
112
112
  /**
113
- * Everything the launcher needs from the front-door daemon, injected like `HeadroomPort`: `ensure` brings the door up (spawning its detached supervisor when nothing healthy is serving), registers this launch in its session registry and returns the listener's loopback port; `release` removes that registration when the spawned `claude` exits.
113
+ * Everything the launcher needs from the front-door daemon, injected like `HeadroomPort`: `ensure` brings the door up (spawning its detached supervisor when nothing authenticated is serving), registers this launch in its session registry and returns both listeners' loopback ports plus the CA bundle the child must trust; `release` removes that registration when the spawned `claude` exits.
114
114
  */
115
115
  export interface FrontDoorPort {
116
- readonly ensure: () => {
117
- readonly port: number;
118
- };
116
+ /** `inheritedExtraCaCerts` is the parent environment's own `NODE_EXTRA_CA_CERTS`, which the returned trust bundle keeps (see `resolveTrustBundle`). */
117
+ readonly ensure: (inheritedExtraCaCerts: string | undefined) => FrontDoorUp;
119
118
  readonly release: () => void;
120
119
  }
120
+ /** The running front door one launch routed through, as `FrontDoorPort.ensure` reports it. */
121
+ export interface FrontDoorUp {
122
+ /** The loopback port the provider listener serves HTTPS on: the base URL every provider session points at. */
123
+ readonly port: number;
124
+ /** The loopback port the CONNECT surface listens on: what an OAuth launch points HTTPS_PROXY at. */
125
+ readonly connectPort: number;
126
+ /** The CA bundle every routed child points NODE_EXTRA_CA_CERTS at: claude-use's CA (which signs both the provider listener's leaf and the CONNECT surface's), plus whatever the parent environment already trusted that way. */
127
+ readonly trustBundlePath: string;
128
+ /** Why the trust bundle dropped something the parent environment named, when it did; the launcher logs it. */
129
+ readonly trustWarning?: string;
130
+ /** This launch's capability token, injected alongside the session headers and required by the door before it routes anything. */
131
+ readonly sessionToken: string;
132
+ }
121
133
  export {};
@@ -24,12 +24,6 @@ export interface LayoutPaths {
24
24
  readonly claudeShimFile: string;
25
25
  /** Directory holding the headroom daemon's coordination state: state.json, the start lock, and the session registry. */
26
26
  readonly headroomDir: string;
27
- /** Directory holding the MITM proxy's certificate authority: ca.pem (public, handed to children as NODE_EXTRA_CA_CERTS) and ca.key (mode 0600). */
28
- readonly headroomCaDir: string;
29
- /** Path to the MITM proxy's CA certificate, generated once and stable across restarts so children keep trusting it. */
30
- readonly headroomCaCertFile: string;
31
- /** Path to the MITM proxy's CA private key, mode 0600. */
32
- readonly headroomCaKeyFile: string;
33
27
  /** Path to the headroom supervisor's state.json: pids, port, version, allowlist hash, last error. */
34
28
  readonly headroomStateFile: string;
35
29
  /** Path to the exclusive-create marker guarding "who spawns the supervisor" so concurrent launches start at most one. */
@@ -42,6 +36,14 @@ export interface LayoutPaths {
42
36
  readonly headroomLogPath: string;
43
37
  /** Directory holding the front-door daemon's coordination state: state.json, the start lock, and the session registry. */
44
38
  readonly frontdoorDir: string;
39
+ /** Directory holding the front door's certificate authority, which signs both the provider listener's loopback leaf and the CONNECT surface's intercept leaf: ca.pem (public, handed to children through NODE_EXTRA_CA_CERTS) and ca.key (mode 0600). */
40
+ readonly frontdoorCaDir: string;
41
+ /** Path to the front door's CA certificate, generated once and stable across restarts so children keep trusting it. */
42
+ readonly frontdoorCaCertFile: string;
43
+ /** Path to the front door's CA private key, mode 0600: whoever can read it can impersonate the door to every routed child. */
44
+ readonly frontdoorCaKeyFile: string;
45
+ /** Directory of combined CA bundles: a parent environment's own NODE_EXTRA_CA_CERTS file plus the front door's CA, one file per distinct combination, for children that must keep trusting both. */
46
+ readonly frontdoorCaBundlesDir: string;
45
47
  /** Path to the front door's state.json: its supervisor pid, its port, and the sticky port it last served on. */
46
48
  readonly frontdoorStateFile: string;
47
49
  /** Path to the exclusive-create marker guarding "who spawns the front-door supervisor" so concurrent launches start at most one. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "4.5.0",
3
+ "version": "5.0.1",
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": "Apache-2.0",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",
@@ -1,138 +0,0 @@
1
- import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
2
- import * as net from "node:net";
3
- /**
4
- * A CONNECT proxy the headroom supervisor runs as a second server in its own process, so an OAuth session can have headroom's compression 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.
5
- *
6
- * Every CONNECT target is blind-tunnelled byte for byte EXCEPT the intercept host (Claude Code's own API), whose TLS this proxy terminates with a leaf certificate signed by a locally generated CA. On the terminated session, paths headroom serves are forwarded to headroom's existing loopback port over plain HTTP (compression, per-session stats, and upstream auth unchanged; the client's Authorization header passes through untouched), and every other path is piped by this proxy to the real upstream over TLS, so Remote Control's streaming, OAuth refreshes, and unknown endpoints bypass headroom entirely.
7
- */
8
- /** The one CONNECT host whose TLS gets terminated: Claude Code's own API, the upstream headroom compresses. */
9
- export declare const MITM_INTERCEPT_HOST = "api.anthropic.com";
10
- /** Paths under this prefix are what headroom serves; everything else on the terminated session goes to the real upstream. */
11
- export declare const HEADROOM_SERVED_PATH_PREFIX = "/v1/";
12
- /** 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. */
13
- export declare const HTTPS_PORT = 443;
14
- /** One parsed CONNECT authority, the host lowercased and the port defaulted. */
15
- export interface ConnectTarget {
16
- readonly host: string;
17
- readonly port: number;
18
- }
19
- /** Parses a CONNECT authority (`host:port`, or a bare `host` meaning 443), or undefined when it names no host or a non-numeric port. */
20
- export declare function parseConnectTarget(authority: string): ConnectTarget | undefined;
21
- /** Whether a CONNECT target's host (port already stripped) is the one whose TLS this proxy terminates. */
22
- export declare function isInterceptedHost(host: string, interceptHost: string): boolean;
23
- /** Whether a request path on the terminated session is one headroom serves. The query string stays part of the path: `/v1/messages?beta=true` is still headroom's. */
24
- export declare function servedByHeadroom(path: string | undefined): boolean;
25
- /**
26
- * Headers a forwarded HTTP message keeps: everything except the hop-by-hop set, which belongs to whichever connection carried it and must not leak onto the next one. The `Connection` header's own value can name further hop-by-hop headers (RFC 9110 section 7.6.1), so those go too. Returns a fresh object; the input is the live parser's map and is never mutated.
27
- */
28
- export declare function forwardableHeaders(headers: Readonly<IncomingHttpHeaders>): IncomingHttpHeaders;
29
- /** A CA certificate and its private key, both as PEM. */
30
- export interface CaMaterial {
31
- readonly certPem: string;
32
- readonly keyPem: string;
33
- }
34
- /** A TLS leaf certificate and its private key, both as PEM. */
35
- export interface LeafCert {
36
- readonly certPem: string;
37
- readonly keyPem: string;
38
- }
39
- /** Generates the local CA: one RSA keypair, self-signed, CA-only by basicConstraints and keyUsage, with a random serial. Pure node-forge, no openssl, so the same code works on every platform claude-use ships to. */
40
- export declare function generateCa(now: Readonly<Date>): CaMaterial;
41
- /** Mints the TLS leaf for one host, signed by the CA: its own RSA keypair, the host in the SAN, server-auth usage only. */
42
- export declare function mintLeaf(ca: CaMaterial, host: string, now: Readonly<Date>): LeafCert;
43
- /** Mints each host's leaf once from the CA and reuses it, because a 2048-bit keypair in pure JS costs seconds and nothing about the certificate changes between sessions. */
44
- export declare function createLeafCache(ca: CaMaterial, now: () => Date): (host: string) => LeafCert;
45
- /**
46
- * Persistence for the CA: generated once on the machine's first headroom start and reused forever after, because regenerating it would invalidate every child's `NODE_EXTRA_CA_CERTS` pointing at the old file. The real implementation writes the key with mode 0600; injected fakes keep it in memory.
47
- */
48
- export interface MitmCertStore {
49
- /** The persisted CA, or undefined when none exists yet (or only half of it does: a cert without its key can sign nothing, so it counts as absent). */
50
- readonly loadCa: () => CaMaterial | undefined;
51
- /** Persists both halves of the CA, the key unreadable to other users. */
52
- readonly writeCa: (ca: CaMaterial) => void;
53
- }
54
- /**
55
- * Loads the persisted CA, generating and persisting one when no usable CA exists. A stored pair that node-forge cannot parse is treated as absent and replaced: a truncated write is the one failure mode a regeneration can actually repair, and every child that trusted the old file needs a new `NODE_EXTRA_CA_CERTS` anyway once its CA stops parsing.
56
- */
57
- export declare function ensureCa(store: MitmCertStore, generate: () => CaMaterial): CaMaterial;
58
- /** Where one forwarded request goes, and how. Headroom's loopback port is plain HTTP; the real upstream is TLS. */
59
- interface MitmForwardTarget {
60
- readonly host: string;
61
- readonly port: number;
62
- readonly tls: boolean;
63
- /** The SNI name to present; defaults to `host`. Separated so a test can connect to a local fake while presenting the production hostname. */
64
- readonly servername?: string;
65
- /** Extra trust anchors for the target's certificate, when it is not system-trusted (a test's local fake upstream). */
66
- readonly ca?: readonly string[];
67
- /** Overrides certificate verification; production leaves it at the Node default (verify against system trust or `ca`). */
68
- readonly rejectUnauthorized?: boolean;
69
- }
70
- /** Handles one parsed HTTP request on a terminated TLS session. */
71
- type MitmRequestHandler = (request: IncomingMessage, response: ServerResponse) => void;
72
- /** A bound loopback listener and how to stop it. */
73
- interface MitmListenerHandle {
74
- readonly port: number;
75
- /** Stops accepting, ends every live connection, and resolves once the port is released. */
76
- readonly close: () => Promise<void>;
77
- }
78
- /** Presents one leaf's server-side TLS on CONNECTed sockets. */
79
- interface TlsAcceptor {
80
- /** Starts the TLS handshake on a socket that has just received its CONNECT response. */
81
- readonly accept: (socket: net.Socket) => void;
82
- /** Drops every session this acceptor is terminating. */
83
- readonly close: () => void;
84
- }
85
- /** Parses HTTP over terminated sockets and serves each request through the handler, keep-alive included. */
86
- interface HttpParserSession {
87
- readonly serve: (socket: net.Socket) => void;
88
- readonly close: () => void;
89
- }
90
- /**
91
- * Every effect the MITM proxy performs, injected so the routing decisions and lifecycle run against fakes in unit tests (the SupervisorPorts pattern): TCP listening, TLS termination, HTTP parsing, plain TCP tunnels, and request forwarding. The real implementation is `realMitmEffects`; the TLS round-trip test uses it with redirected targets.
92
- */
93
- export interface MitmEffects {
94
- /** 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. */
95
- readonly listenLoopback: (preferredPort: number | undefined, onSocket: (socket: net.Socket) => void) => Promise<MitmListenerHandle>;
96
- /** Builds the TLS terminator for one leaf, handing each successfully handshaked session to `onSecure`. */
97
- readonly createTlsAcceptor: (leaf: LeafCert, onSecure: (secure: net.Socket) => void) => TlsAcceptor;
98
- /** Builds the HTTP parser bound to one request handler. */
99
- readonly createHttpSession: (handler: MitmRequestHandler) => HttpParserSession;
100
- /** Opens a raw TCP connection for a blind tunnel. */
101
- readonly connectTcp: (host: string, port: number) => Promise<net.Socket>;
102
- /** Streams one request to `target` and the response back, never buffering a body. */
103
- readonly forwardHttp: (target: MitmForwardTarget, request: IncomingMessage, response: ServerResponse) => void;
104
- }
105
- /** Everything the proxy needs to route, resolved before it starts. */
106
- export interface MitmServerConfig {
107
- /** The CONNECT host whose TLS gets terminated. */
108
- readonly interceptHost: string;
109
- /**
110
- * Headroom's loopback port, read per request rather than captured at start-up: the daemon can crash and restart on a different port while this proxy keeps listening, and headroom-served paths must follow it. While it reads undefined (the restart window), headroom-served paths fail fast with 502 rather than silently bypassing headroom to the real upstream.
111
- */
112
- readonly headroomPort: () => number | undefined;
113
- /** The leaf to terminate `interceptHost` with, minted and cached per host. */
114
- readonly leafFor: (host: string) => LeafCert;
115
- /** Where non-headroom paths on the terminated session are piped: the real upstream over TLS. */
116
- readonly upstream: MitmForwardTarget;
117
- }
118
- /** A running MITM proxy. */
119
- export interface MitmServerHandle {
120
- readonly port: number;
121
- /** Stops the listener, drops every live tunnel and terminated session, and resolves once the port is released. */
122
- readonly close: () => Promise<void>;
123
- }
124
- /**
125
- * Starts the MITM proxy: binds the listener, mints the intercept host's leaf, and wires the per-connection routing. Resolves once the listener is bound.
126
- *
127
- * The connection flow: read the CONNECT head (never more of the stream than that, so an early ClientHello glued to the head is pushed back with `unshift` and still seen by whatever consumes the socket next), then either blind-tunnel the target or terminate its TLS and parse HTTP on the session. Every effect flows through `effects`; nothing here touches the network or filesystem itself.
128
- */
129
- export declare function startMitmServer(config: MitmServerConfig, effects: MitmEffects, preferredPort: number | undefined): Promise<MitmServerHandle>;
130
- /** The real `MitmEffects` over node's own net, tls, and http. `connectTcp` opens real connections to the CONNECTed host, so tests that want a blind tunnel redirect it. */
131
- export declare function realMitmEffects(): MitmEffects;
132
- /** The real `MitmCertStore` on the filesystem: `ca.pem` world-readable (it is public material), `ca.key` mode 0600 exactly, chmod'ed after the write because a create's mode bits pass through the process umask. */
133
- export declare function realMitmCertStore(paths: {
134
- readonly headroomCaDir: string;
135
- readonly headroomCaCertFile: string;
136
- readonly headroomCaKeyFile: string;
137
- }): MitmCertStore;
138
- export {};