@etiennepasteur/jean-claude 0.1.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/LICENSE +21 -0
- package/README.md +422 -0
- package/dist/cli.mjs +1631 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Etienne Pasteur
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# jean-claude
|
|
2
|
+
|
|
3
|
+
A CLI MITM HTTPS proxy that intercepts a tool's API traffic and rewrites it from a
|
|
4
|
+
YAML file. The tool being intercepted never knows.
|
|
5
|
+
|
|
6
|
+
It was built for one job in particular: **freezing the settings Claude Code
|
|
7
|
+
fetches at startup**, so the ones you control apply instead of the ones the API
|
|
8
|
+
hands back. Everything else it does — capture, stub, patch, redirect — falls out
|
|
9
|
+
of the same machinery and works against any HTTPS client.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Claude Code ──► jean-claude ──► api.anthropic.com
|
|
13
|
+
▲
|
|
14
|
+
└── ~/.config/jean-claude/jean-claude.yaml
|
|
15
|
+
+ responses/settings.GET.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Freezing Claude Code's settings
|
|
19
|
+
|
|
20
|
+
On startup, Claude Code calls `GET https://api.anthropic.com/api/claude_code/settings`
|
|
21
|
+
and applies the managed settings attached to your account. The response looks like
|
|
22
|
+
this:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"uuid": "…",
|
|
27
|
+
"checksum": "sha256:…",
|
|
28
|
+
"settings": {
|
|
29
|
+
"env": { "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" },
|
|
30
|
+
"permissions": { "defaultMode": "plan" }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
jean-claude answers that one request from a local file. It never leaves the
|
|
36
|
+
machine, and the settings stop moving under you between sessions. Every other call
|
|
37
|
+
Claude Code makes — the actual conversation — is relayed untouched.
|
|
38
|
+
|
|
39
|
+
### 1. Set it up
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g @etiennepasteur/jean-claude
|
|
43
|
+
jean-claude init --claude-code
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The package is scoped, but the command it installs is plain `jean-claude` — that
|
|
47
|
+
is what every example below uses.
|
|
48
|
+
|
|
49
|
+
Or without installing anything — `npx` works the same way:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @etiennepasteur/jean-claude init --claude-code
|
|
53
|
+
npx @etiennepasteur/jean-claude run -- claude
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Installing globally is the better default for something you launch every session:
|
|
57
|
+
the dependency tree is ~55 MB, so the first `npx` run is a real download, and even
|
|
58
|
+
warm it adds about half a second of resolution. `npx` is the right call for trying
|
|
59
|
+
it once, or for a throwaway CI step.
|
|
60
|
+
|
|
61
|
+
Either way, that writes `~/.config/jean-claude/` with a CA, a starter stub, and
|
|
62
|
+
this config:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
host: api.anthropic.com
|
|
66
|
+
|
|
67
|
+
rules:
|
|
68
|
+
- name: frozen claude_code settings
|
|
69
|
+
method: GET
|
|
70
|
+
path: /api/claude_code/settings
|
|
71
|
+
respond: ./responses/settings.GET.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Edit the settings
|
|
75
|
+
|
|
76
|
+
Open `~/.config/jean-claude/responses/settings.GET.json` and put what you want
|
|
77
|
+
in the `settings` object. The file is re-read on **every request**, so a change
|
|
78
|
+
applies the next time Claude Code starts — no proxy restart, no reload.
|
|
79
|
+
|
|
80
|
+
To start from what your account actually sends rather than from the scaffolded
|
|
81
|
+
stub, record it once:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
jean-claude run --record ./captures -- claude
|
|
85
|
+
cp ./captures/api.anthropic.com/api/claude_code/settings.GET.json \
|
|
86
|
+
~/.config/jean-claude/responses/settings.GET.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Run it
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
jean-claude run -- claude
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Add `-v` the first time, to see every request rather than only the ones a rule
|
|
96
|
+
touched:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
GET api.anthropic.com/api/claude_code/settings 200 → stub responses/settings.GET.json
|
|
100
|
+
POST api.anthropic.com/v1/messages 200
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
A `→ stub` on the settings line means it worked. If you would rather keep the log
|
|
104
|
+
out of Claude Code's output, run the proxy in its own terminal —
|
|
105
|
+
see [Two terminals](#two-terminals-start--env).
|
|
106
|
+
|
|
107
|
+
## Where everything lives
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
~/.config/jean-claude/ # $XDG_CONFIG_HOME/jean-claude if set
|
|
111
|
+
├── jean-claude.yaml
|
|
112
|
+
├── responses/
|
|
113
|
+
├── ca/ ca.pem ca.key bundle.pem
|
|
114
|
+
└── session.json # only while `start` is running
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The config is resolved in this order:
|
|
118
|
+
|
|
119
|
+
1. `--config <path>`
|
|
120
|
+
2. `jean-claude.yaml` (or `.yml`) in the current directory, then walking up
|
|
121
|
+
3. `~/.config/jean-claude/jean-claude.yaml`
|
|
122
|
+
|
|
123
|
+
So the global rule applies wherever you launch Claude Code from, and a repo that
|
|
124
|
+
drops its own `jean-claude.yaml` at the root overrides it for that project. Paths
|
|
125
|
+
inside a config (`respond:`, `patch: { file: }`) are always relative to that
|
|
126
|
+
config file.
|
|
127
|
+
|
|
128
|
+
`--home <dir>` relocates the whole directory — for a throwaway setup, or to run
|
|
129
|
+
two proxies side by side.
|
|
130
|
+
|
|
131
|
+
## Beyond Claude Code
|
|
132
|
+
|
|
133
|
+
Nothing in the machinery is Anthropic-specific. `jean-claude init` without
|
|
134
|
+
`--claude-code` scaffolds a generic config, and the same rules work against any
|
|
135
|
+
HTTPS client that honours the proxy environment variables:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
jean-claude init
|
|
139
|
+
jean-claude run -- npx your-tool
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The problem it solves in general: you are developing against a third-party API and
|
|
143
|
+
you need to _see_ and _falsify_ what a tool receives, without touching either the
|
|
144
|
+
tool or the server. Reproduce a 500. Test a business case that is absent from the
|
|
145
|
+
dataset. Freeze a flaky response. Or simply find out what the tool actually calls.
|
|
146
|
+
|
|
147
|
+
### 1. Replace the response with a file
|
|
148
|
+
|
|
149
|
+
The request never leaves the machine. This is what the Claude Code rule uses.
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
rules:
|
|
153
|
+
- path: /api/todos
|
|
154
|
+
method: GET
|
|
155
|
+
respond: ./responses/todos.json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The stub file is re-read on **every request**, so editing it takes effect
|
|
159
|
+
immediately — no restart, no reload.
|
|
160
|
+
|
|
161
|
+
Longer form, with status, headers and latency:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
rules:
|
|
165
|
+
- path: /api/todos
|
|
166
|
+
respond:
|
|
167
|
+
file: ./responses/todos.json
|
|
168
|
+
status: 201
|
|
169
|
+
headers: { x-jean-claude: stub }
|
|
170
|
+
delay: 500
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Or an inline body, with no file at all:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
rules:
|
|
177
|
+
- path: /api/health
|
|
178
|
+
respond:
|
|
179
|
+
body: { status: 'ok' }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 2. Patch the real response
|
|
183
|
+
|
|
184
|
+
The request _does_ reach the server; jean-claude edits what comes back. Useful
|
|
185
|
+
when you want the live payload with one field bent, rather than a frozen copy
|
|
186
|
+
that goes stale.
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
rules:
|
|
190
|
+
# Precise edit, and the only option that works on an array response.
|
|
191
|
+
- path: /api/todos
|
|
192
|
+
patch:
|
|
193
|
+
jsonPatch: [{ op: replace, path: /0/title, value: Dining }]
|
|
194
|
+
|
|
195
|
+
# Deep merge, for object responses.
|
|
196
|
+
- path: /api/users/:id
|
|
197
|
+
patch:
|
|
198
|
+
merge: { verified: true }
|
|
199
|
+
|
|
200
|
+
# Simulate a failing, slow server. The original body is preserved
|
|
201
|
+
# unless you replace it.
|
|
202
|
+
- path: /api/flaky
|
|
203
|
+
patch:
|
|
204
|
+
status: 500
|
|
205
|
+
body: { error: 'boom' }
|
|
206
|
+
delay: 2000
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`patch` accepts `status`, `headers` (merged), `replaceHeaders` (wholesale),
|
|
210
|
+
`merge`, `jsonPatch`, `body`, `file` and `delay`.
|
|
211
|
+
|
|
212
|
+
### 3. Rewrite the outgoing request
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
rules:
|
|
216
|
+
- host: auth.example.com
|
|
217
|
+
path: /oauth/token
|
|
218
|
+
request:
|
|
219
|
+
host: staging-auth.example.com
|
|
220
|
+
path: /v2/token
|
|
221
|
+
headers: { authorization: 'Bearer TEST' }
|
|
222
|
+
removeHeaders: [cookie]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`request` combines with `patch` — you can redirect a call _and_ doctor its
|
|
226
|
+
response. It cannot combine with `respond`, which short circuits the request
|
|
227
|
+
before it goes anywhere.
|
|
228
|
+
|
|
229
|
+
## Matching
|
|
230
|
+
|
|
231
|
+
```yaml
|
|
232
|
+
host: api.example.com # default host for every rule below
|
|
233
|
+
port: 8888 # optional, otherwise a free port is picked
|
|
234
|
+
|
|
235
|
+
rules:
|
|
236
|
+
- host: auth.example.com # overrides the default
|
|
237
|
+
method: [POST, PUT] # one method or a list, case-insensitive
|
|
238
|
+
path: /api/users/:id # exact, :param, or a bare * wildcard
|
|
239
|
+
query: { page: '2' } # required params; extra ones are ignored
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
- A rule with no `host` matches **any** host.
|
|
243
|
+
- `*.example.com` matches any subdomain.
|
|
244
|
+
- `path` is compiled by `path-to-regexp`: `/api/todos`, `/api/users/:id`, `/api/*`.
|
|
245
|
+
Use `pathRegex` when you need a raw regular expression.
|
|
246
|
+
- Captured parameters interpolate into the stub path:
|
|
247
|
+
`respond: ./responses/users/{id}.json`.
|
|
248
|
+
- Rules are tried in file order and **the first match wins**. `jean-claude check`
|
|
249
|
+
prints them as resolved.
|
|
250
|
+
- Anything matching no rule is still decrypted, logged and relayed untouched.
|
|
251
|
+
|
|
252
|
+
## Discovering and capturing traffic
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
jean-claude run --record ./captures -- npx your-tool
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
GET api.example.com/api/todos 200 → stub responses/todos.json
|
|
260
|
+
GET api.example.com/api/users/42 200 ⇒ captures/api.example.com/api/users/42.GET.json
|
|
261
|
+
POST api.example.com/api/login 401 ⇒ captures/api.example.com/api/login.POST.json
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Captures contain the **body only**, re-indented, so a captured file drops
|
|
265
|
+
straight into a `respond:` rule — that is the shortcut used to seed the Claude
|
|
266
|
+
Code stub above. When a rule patches a response, the capture is still the
|
|
267
|
+
untouched server response, not what the client saw.
|
|
268
|
+
|
|
269
|
+
By default only affected traffic is logged. Add `-v` to see everything, `-q` to
|
|
270
|
+
see nothing.
|
|
271
|
+
|
|
272
|
+
## Certificates
|
|
273
|
+
|
|
274
|
+
On first run, jean-claude generates a CA in `~/.config/jean-claude/ca/`:
|
|
275
|
+
|
|
276
|
+
| File | Role |
|
|
277
|
+
| ------------------- | ------------------------------------------------------------------------------ |
|
|
278
|
+
| `ca.pem` / `ca.key` | the authority that signs per-host certificates on the fly (`ca.key` is `0600`) |
|
|
279
|
+
| `bundle.pem` | `ca.pem` + the system trust store + any inherited corporate CA |
|
|
280
|
+
|
|
281
|
+
The bundle exists because `SSL_CERT_FILE` and `CURL_CA_BUNDLE` **replace** the
|
|
282
|
+
trust store rather than adding to it — handing a target only our own CA would cut
|
|
283
|
+
it off from every other authority.
|
|
284
|
+
|
|
285
|
+
`jean-claude run` points the child at the bundle, so **no root access and no
|
|
286
|
+
system-wide trust change is needed**. If you do want the CA in the system store
|
|
287
|
+
(for a GUI app, say), `jean-claude ca --install` prints the commands for you to
|
|
288
|
+
run yourself. jean-claude never invokes `sudo` on its own.
|
|
289
|
+
|
|
290
|
+
### Behind a corporate proxy
|
|
291
|
+
|
|
292
|
+
jean-claude reads the environment before rewriting it for the child, and chains
|
|
293
|
+
onto whatever it finds:
|
|
294
|
+
|
|
295
|
+
- `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` → relayed traffic goes out through
|
|
296
|
+
your corporate proxy. Override with `upstream:` in the config (`auto`, `off`,
|
|
297
|
+
or an explicit URL).
|
|
298
|
+
- `NODE_EXTRA_CA_CERTS` → your corporate CA is trusted on outbound connections
|
|
299
|
+
_and_ folded into `bundle.pem` for the child.
|
|
300
|
+
|
|
301
|
+
### When a target refuses the CA
|
|
302
|
+
|
|
303
|
+
A client that pins its certificates will fail the handshake. jean-claude says so
|
|
304
|
+
explicitly rather than leaving you guessing, and the fix is to tunnel that host
|
|
305
|
+
without interception:
|
|
306
|
+
|
|
307
|
+
```yaml
|
|
308
|
+
tlsPassthrough:
|
|
309
|
+
- pinned.example.com
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## How the target is pointed at the proxy
|
|
313
|
+
|
|
314
|
+
`jean-claude run` injects these into the child environment:
|
|
315
|
+
|
|
316
|
+
| Variable | Why |
|
|
317
|
+
| ------------------------------------------------------------------------------------------ | --------------------------------------------------- |
|
|
318
|
+
| `HTTP_PROXY`, `HTTPS_PROXY` (+ lowercase) | the universal convention |
|
|
319
|
+
| `NODE_USE_ENV_PROXY=1` | **required**: Node ignores `HTTPS_PROXY` without it |
|
|
320
|
+
| `NODE_EXTRA_CA_CERTS` | Node targets |
|
|
321
|
+
| `SSL_CERT_FILE`, `CURL_CA_BUNDLE`, `REQUESTS_CA_BUNDLE`, `AWS_CA_BUNDLE`, `GIT_SSL_CAINFO` | OpenSSL, curl, python/requests, AWS CLI, git |
|
|
322
|
+
|
|
323
|
+
`NO_PROXY` is **unset**, not narrowed. The tempting default is
|
|
324
|
+
`localhost,127.0.0.1,::1`, but that makes every localhost target bypass
|
|
325
|
+
jean-claude silently — and intercepting a local dev API is one of the main reasons
|
|
326
|
+
to reach for this tool. A bypass that still produces plausible traffic is the
|
|
327
|
+
worst possible failure mode, so exclusions are opt-in:
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
noProxy:
|
|
331
|
+
- metrics.internal
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Exclusions are printed in the startup banner, because they are the one setting
|
|
335
|
+
that makes traffic invisible.
|
|
336
|
+
|
|
337
|
+
`NODE_USE_ENV_PROXY` only exists in Node **≥ 22.21** or **≥ 24.5**. Below that, a
|
|
338
|
+
Node-based target silently ignores the proxy; jean-claude warns you at startup.
|
|
339
|
+
Claude Code ships as a Node CLI, so this applies to it too.
|
|
340
|
+
|
|
341
|
+
Known gaps worth knowing rather than fighting: Java uses its own keystore
|
|
342
|
+
(`keytool`), Rust tools built on `rustls` embed their roots and ignore every
|
|
343
|
+
environment variable, and anything with pinned certificates needs
|
|
344
|
+
`tlsPassthrough`.
|
|
345
|
+
|
|
346
|
+
## Two terminals: `start` + `env`
|
|
347
|
+
|
|
348
|
+
For a GUI app, a service that is already running, or simply to keep jean-claude's
|
|
349
|
+
log out of your tool's output:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# terminal 1 - the proxy and its log live here
|
|
353
|
+
jean-claude start -v
|
|
354
|
+
|
|
355
|
+
# terminal 2 - your tool
|
|
356
|
+
eval "$(jean-claude env)"
|
|
357
|
+
claude
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`start` records the live session (port, bundle path, pid) in
|
|
361
|
+
`~/.config/jean-claude/session.json`, and `env` reads it — so the second shell
|
|
362
|
+
needs no arguments and no copy-pasting. The file is removed when `start` exits,
|
|
363
|
+
and `env` refuses a stale one rather than handing out a dead port.
|
|
364
|
+
|
|
365
|
+
Note that `eval "$(jean-claude start --export)"` **cannot** work: `start` runs in
|
|
366
|
+
the foreground, so the command substitution would never return. That is exactly
|
|
367
|
+
why `env` exists.
|
|
368
|
+
|
|
369
|
+
Use `jean-claude env -p 8899` to target a session started elsewhere, `--json` for
|
|
370
|
+
machine-readable output. One session is recorded per home; with several
|
|
371
|
+
concurrent proxies, pass `-p` (or a separate `--home`).
|
|
372
|
+
|
|
373
|
+
## Commands
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
jean-claude run -- <command> run a command with its HTTPS traffic intercepted
|
|
377
|
+
jean-claude start run the proxy alone, in its own terminal
|
|
378
|
+
jean-claude env print the environment for a running `start`
|
|
379
|
+
jean-claude check validate the config, print the rules as resolved
|
|
380
|
+
jean-claude ca show the certificate store, and how to trust it
|
|
381
|
+
jean-claude init set up the jean-claude directory: config, stub, CA
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Shared flags: `-c/--config`, `-p/--port`, `-r/--record`, `--home`,
|
|
385
|
+
`-v/--verbose`, `-q/--quiet`, `--no-watch`.
|
|
386
|
+
|
|
387
|
+
`init` takes `--home <dir>` and `--claude-code`.
|
|
388
|
+
|
|
389
|
+
`run` exits with the child's exit code, so it drops into a CI pipeline unchanged.
|
|
390
|
+
|
|
391
|
+
The config file is watched and reloaded on save. A config that fails to parse is
|
|
392
|
+
reported and the previous rules stay in effect. Changing `tlsPassthrough` is the
|
|
393
|
+
one setting that needs a restart.
|
|
394
|
+
|
|
395
|
+
## Development
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
npm install
|
|
399
|
+
npm test # 130 tests, including an end-to-end MITM suite
|
|
400
|
+
npm run typecheck
|
|
401
|
+
npm run lint
|
|
402
|
+
npm run build
|
|
403
|
+
npm run jc -- --help # run from source
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The end-to-end suite runs a plain `node:https` server as the fake upstream,
|
|
407
|
+
holding a leaf certificate minted by jean-claude's own CA, so it needs no network
|
|
408
|
+
access. It asserts the things that actually matter: that a `respond` rule never
|
|
409
|
+
reaches the server, that `patch` does, and that a status-only patch leaves the
|
|
410
|
+
body intact.
|
|
411
|
+
|
|
412
|
+
One gotcha worth knowing if you extend the suite: pointing jean-claude at a
|
|
413
|
+
_second mockttp instance_ stalls for ~15s on the first upstream connection. It is
|
|
414
|
+
an artefact of mockttp talking to mockttp, not of jean-claude — against a real
|
|
415
|
+
HTTPS server the relay costs ~25ms. Hence the plain `node:https` upstream.
|
|
416
|
+
|
|
417
|
+
Built on [mockttp](https://github.com/httptoolkit/mockttp), which does the heavy
|
|
418
|
+
lifting: CONNECT tunnelling, per-host certificate minting, HTTP/2 and WebSockets.
|
|
419
|
+
|
|
420
|
+
## Licence
|
|
421
|
+
|
|
422
|
+
MIT — see [LICENSE](LICENSE).
|