api-tracer-kit 1.0.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +466 -0
  4. package/cli/bin/api-tracer.mjs +266 -0
  5. package/cli/config.mjs +224 -0
  6. package/cli/import.mjs +231 -0
  7. package/cli/index.mjs +10 -0
  8. package/cli/presets.mjs +212 -0
  9. package/cli/report.mjs +346 -0
  10. package/cli/scan.mjs +142 -0
  11. package/cli/server.mjs +1576 -0
  12. package/cli/shape.mjs +90 -0
  13. package/cli/test.mjs +342 -0
  14. package/cli/web/app.css +1424 -0
  15. package/cli/web/app.js +2260 -0
  16. package/cli/web/favicon.svg +5 -0
  17. package/cli/web/index.html +159 -0
  18. package/cli/web/logo.svg +7 -0
  19. package/dist/axios.cjs +856 -0
  20. package/dist/axios.cjs.map +1 -0
  21. package/dist/axios.d.cts +27 -0
  22. package/dist/axios.d.ts +27 -0
  23. package/dist/axios.js +853 -0
  24. package/dist/axios.js.map +1 -0
  25. package/dist/index.cjs +872 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +74 -0
  28. package/dist/index.d.ts +74 -0
  29. package/dist/index.js +857 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/react.cjs +896 -0
  32. package/dist/react.cjs.map +1 -0
  33. package/dist/react.d.cts +22 -0
  34. package/dist/react.d.ts +22 -0
  35. package/dist/react.js +893 -0
  36. package/dist/react.js.map +1 -0
  37. package/dist/tracer-BUWdU2lG.d.ts +76 -0
  38. package/dist/tracer-DG2YUqK0.d.cts +76 -0
  39. package/dist/types-Bl2-K6_g.d.cts +111 -0
  40. package/dist/types-Bl2-K6_g.d.ts +111 -0
  41. package/dist/ui.cjs +1162 -0
  42. package/dist/ui.cjs.map +1 -0
  43. package/dist/ui.d.cts +16 -0
  44. package/dist/ui.d.ts +16 -0
  45. package/dist/ui.js +1157 -0
  46. package/dist/ui.js.map +1 -0
  47. package/package.json +92 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ First release. Extracted from an in-repo API console and made standalone.
6
+
7
+ ### The tracer
8
+
9
+ - Automatic `fetch`, `XMLHttpRequest` and axios interception
10
+ - `ApiTrace` model: request, response, timing, status, error, envelope verdict
11
+ - `FormData`, `URLSearchParams`, JSON, text and binary bodies, each recorded as sent
12
+ - HTTP errors, network failures, timeouts and aborts told apart
13
+ - Credential redaction in headers and bodies, on by default
14
+ - Subscribers, a `TraceStorage` interface and a capped in-memory default
15
+ - `reportTo`, streaming finished traces to the console
16
+ - `destroy()`, restoring every patched global and axios adapter
17
+ - ESM and CJS builds, TypeScript declarations, four tree-shakeable entry points
18
+ - `api-tracer-kit/react` hooks and an `api-tracer-kit/ui` panel
19
+
20
+ ### The console
21
+
22
+ - Preset-driven scanner (`service-object`, `axios-direct`, `fetch-direct`) that
23
+ picks itself by running each preset and keeping whichever reads the codebase
24
+ - Base URLs and the auth header read from the source rather than configured
25
+ - Live recording, endpoint matching, and adoption of calls the scan cannot explain
26
+ - Send, bulk run, and full replay with a plan shown first
27
+ - Response-shape contracts and drift reporting
28
+ - Coverage, and an insight report with Markdown and JSON export
29
+ - HAR / cURL / Postman export, HAR / cURL import
30
+ - `{{variables}}` captured from responses and chained between calls
31
+ - A config-driven multi-step sign-in flow, replacing a hardcoded one
32
+ - Data kept in `<project>/.api-tracer` rather than inside the package
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 api-tracer-kit contributors
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,466 @@
1
+ # api-tracer-kit
2
+
3
+ Trace every API call an application makes — and map, exercise and watch the
4
+ endpoints it has.
5
+
6
+ Two halves that work on their own or together:
7
+
8
+ - **the tracer** — a framework-free library that records every `fetch`, `XMLHttpRequest`
9
+ and axios call, with no change to your API layer and no bundler configuration.
10
+ - **the console** — a CLI and web UI that reads your source into an endpoint
11
+ catalog, receives the tracer's traffic, and lets you re-send any call, watch
12
+ for response-shape drift, and export what it captured.
13
+
14
+ ```bash
15
+ npm install api-tracer-kit
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```ts
21
+ import { apiTracer } from 'api-tracer-kit';
22
+
23
+ apiTracer.init();
24
+ ```
25
+
26
+ That is the whole setup. Every call made afterwards is traced:
27
+
28
+ ```ts
29
+ await fetch('/api/users');
30
+
31
+ apiTracer.getTraces();
32
+ // [{ id, request: { url, method, headers, params, body }, response: { status, headers, body },
33
+ // timing: { startedAt, completedAt, duration }, status: 'success' }]
34
+ ```
35
+
36
+ Nothing happens at import time, so the package is safe to import during SSR, in
37
+ a build step, or in a test. `init()` is the only thing that patches anything.
38
+
39
+ Then, optionally, the console:
40
+
41
+ ```bash
42
+ npx api-tracer start
43
+ ```
44
+
45
+ It reads your source, finds your endpoints, and opens at
46
+ `http://localhost:4400`. Point the tracer at it with `reportTo: true` and your
47
+ app's own traffic fills it in as you click around.
48
+
49
+ ## Features
50
+
51
+ **Tracing**
52
+
53
+ - `fetch`, `XMLHttpRequest` and axios, automatically
54
+ - method, absolute URL, path, parsed query params, headers, request body
55
+ - response status, status text, headers and body
56
+ - start time, completion time and duration
57
+ - HTTP errors, network failures, timeouts and aborts, told apart
58
+ - `FormData`, `URLSearchParams`, JSON, text and binary bodies, each recorded as
59
+ what it was, so a captured call can be replayed the way it was sent
60
+ - files in a `FormData` recorded as `<file: scan.pdf, 20418 bytes>`
61
+ - a unique id per call, and no mixing between concurrent requests
62
+ - credential redaction in headers and bodies, on by default
63
+ - envelope reading, for APIs that answer `200` and put the real verdict in the body
64
+ - subscribers, a replaceable storage layer, and a capped in-memory store by default
65
+
66
+ **Console**
67
+
68
+ - an endpoint catalog read straight out of your source — no hand-maintained list
69
+ - live recording: your app's traffic lands on the endpoint it belongs to, with
70
+ the real params, payload and path ids already filled in
71
+ - send any endpoint, with a Postman-style tabbed editor (params, headers, body,
72
+ capture rules) and a syntax-highlighted JSON editor
73
+ - bulk runs, and a full replay of every captured call, with a plan shown before
74
+ anything is sent
75
+ - response-shape contracts, so a backend that quietly drops a field is caught
76
+ even though it answered `200`
77
+ - coverage: which endpoints your app has never exercised
78
+ - an insight report — coverage, failures, drift, risks, hygiene, auth surface,
79
+ latency, inventory and a trend line — exportable as Markdown or JSON
80
+ - HAR / cURL / Postman export, and HAR / cURL import
81
+ - `{{variables}}`, captured from responses and chained between calls
82
+
83
+ ## Supported environments
84
+
85
+ | | |
86
+ | --- | --- |
87
+ | Browsers | ✅ `fetch`, `XMLHttpRequest`, axios |
88
+ | Node 18+ | ✅ `fetch` and axios; `XMLHttpRequest` is skipped when absent |
89
+ | React / Next.js / Vite / CRA / webpack | ✅ no plugin, loader, alias or config change |
90
+ | SSR and build-time execution | ✅ importing does nothing; `init()` no-ops without a runtime |
91
+ | TypeScript | ✅ types shipped, `strict`-clean |
92
+ | Deno / Bun | ⚠️ untested, but the tracer only uses standard globals |
93
+
94
+ Nothing in the core imports React or axios. Both are optional peer dependencies
95
+ used only by the entry points that need them.
96
+
97
+ ## Entry points
98
+
99
+ ```ts
100
+ import { apiTracer } from 'api-tracer-kit'; // the tracer
101
+ import { useAxios } from 'api-tracer-kit/axios'; // axios integration
102
+ import { useApiTraces } from 'api-tracer-kit/react'; // hooks
103
+ import { ApiTracerPanel } from 'api-tracer-kit/ui'; // in-app panel
104
+ ```
105
+
106
+ Each is bundled separately, so importing the tracer never pulls in React.
107
+
108
+ ## Configuration
109
+
110
+ All of it is optional.
111
+
112
+ ```ts
113
+ apiTracer.init({
114
+ // which transports to patch. Default: all of them that exist.
115
+ transports: ['fetch', 'xhr'],
116
+
117
+ // only trace URLs matching one of these. Strings match by substring.
118
+ include: ['/api/'],
119
+ exclude: [/\/health$/],
120
+
121
+ // header names replaced with "<redacted>"
122
+ redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],
123
+
124
+ // body and query field names replaced with "<redacted>"
125
+ redactFields: /(token|password|secret|api[-_]?key)/i,
126
+
127
+ // bodies bigger than this are recorded as omitted rather than kept
128
+ maxBodyBytes: 200_000,
129
+
130
+ // how many traces to keep; the oldest fall off the end
131
+ maxTraces: 500,
132
+
133
+ // for APIs that answer 200 and put the verdict in the body
134
+ envelope: { codeFields: ['status', 'code'], okField: 'success', failFrom: 400 },
135
+
136
+ // stream finished traces to the console; `true` means http://localhost:4400
137
+ reportTo: true,
138
+
139
+ // replace the in-memory store
140
+ storage: myStorage,
141
+
142
+ onTrace: (trace) => console.log(trace),
143
+ });
144
+ ```
145
+
146
+ There is no config file, and the consuming project needs no changes to its
147
+ webpack, Vite, Babel, TypeScript, Next.js, CRA or ESLint setup.
148
+
149
+ ## API reference
150
+
151
+ ### `apiTracer`
152
+
153
+ | | |
154
+ | --- | --- |
155
+ | `init(options?)` | installs the interceptors. Idempotent — calling it twice does not double-install. |
156
+ | `useAxios(axios)` | traces an axios object and everything `axios.create()` makes from it |
157
+ | `getTraces()` | every trace held, oldest first |
158
+ | `getTrace(id)` | one trace |
159
+ | `subscribe(fn)` | called with each finished trace; returns the unsubscribe function |
160
+ | `clear()` | drops every trace |
161
+ | `remove(id)` | drops one |
162
+ | `destroy()` | restores every patched global; the app behaves exactly as it did before `init()` |
163
+ | `isActive` | whether tracing is installed |
164
+
165
+ Standalone equivalents are exported too, for callers who prefer functions:
166
+ `initApiTracer`, `getApiTraces`, `clearApiTraces`, `subscribeToApiTraces`,
167
+ `destroyApiTracer`.
168
+
169
+ ### `ApiTrace`
170
+
171
+ ```ts
172
+ interface ApiTrace {
173
+ id: string;
174
+ transport: 'fetch' | 'xhr' | 'axios';
175
+ status: 'pending' | 'success' | 'error' | 'network-error' | 'cancelled';
176
+
177
+ request: {
178
+ url: string;
179
+ path: string; // origin + pathname, for grouping
180
+ method: string;
181
+ headers: Record<string, string>;
182
+ params: Record<string, unknown>; // parsed query string
183
+ body?: unknown; // parsed and redacted
184
+ bodyType: 'json' | 'formdata' | 'urlencoded' | 'text' | 'binary' | 'none';
185
+ bodyOmitted?: 'too-large' | 'stream' | 'binary' | 'unreadable';
186
+ };
187
+
188
+ response?: {
189
+ status: number;
190
+ statusText?: string;
191
+ headers: Record<string, string>;
192
+ body?: string;
193
+ bodyOmitted?: 'too-large' | 'stream' | 'binary' | 'unreadable';
194
+ size?: number;
195
+ };
196
+
197
+ timing: { startedAt: number; completedAt?: number; duration?: number };
198
+ error?: { message: string; name?: string; stack?: string };
199
+
200
+ envelopeOk?: boolean; // false when a 2xx carried an error code
201
+ envelopeCode?: number;
202
+ }
203
+ ```
204
+
205
+ ### Storage
206
+
207
+ ```ts
208
+ interface TraceStorage {
209
+ add(trace: ApiTrace): void;
210
+ update(id: string, patch: Partial<ApiTrace>): ApiTrace | undefined;
211
+ get(id: string): ApiTrace | undefined;
212
+ getAll(): ApiTrace[];
213
+ remove(id: string): void;
214
+ clear(): void;
215
+ }
216
+ ```
217
+
218
+ The default is `MemoryTraceStorage`, a capped ring — an unbounded store in a
219
+ long-lived tab is a memory leak. Nothing is persisted; supply your own storage
220
+ if you want it to be.
221
+
222
+ ## React
223
+
224
+ ```tsx
225
+ import { useApiTracer, useApiTraces } from 'api-tracer-kit/react';
226
+
227
+ function App() {
228
+ useApiTracer(); // init once, StrictMode-safe
229
+ const { traces, clear } = useApiTraces();
230
+ return <span>{traces.length} calls</span>;
231
+ }
232
+ ```
233
+
234
+ Updates are batched on an animation frame, so a burst of calls on page load
235
+ re-renders once rather than once per request.
236
+
237
+ ## The in-app panel
238
+
239
+ ```tsx
240
+ import { ApiTracerPanel } from 'api-tracer-kit/ui';
241
+
242
+ <ApiTracerPanel enabled={process.env.NODE_ENV !== 'production'} />;
243
+ ```
244
+
245
+ A floating panel with the call list, per-call detail (request and response
246
+ headers, params, bodies, status, duration, errors), search, method and status
247
+ filters, sorting, expand/collapse and clear. Styles are inline, so there is no
248
+ stylesheet to import and nothing to collide with your app's CSS.
249
+
250
+ ## Axios
251
+
252
+ axios calls are traced two ways.
253
+
254
+ In a browser axios rides on `XMLHttpRequest`, so `apiTracer.init()` alone
255
+ already sees them. For richer data — the params object as axios received it, the
256
+ config, and Node's `http` adapter, which no global patch can reach — attach it
257
+ explicitly:
258
+
259
+ ```ts
260
+ import axios from 'axios';
261
+ import { apiTracer } from 'api-tracer-kit';
262
+
263
+ apiTracer.init();
264
+ apiTracer.useAxios(axios);
265
+ ```
266
+
267
+ One call covers the default export **and every instance `axios.create()` makes
268
+ afterwards**. That matters in codebases that send some requests through per-page
269
+ instances and the rest through the default object — patching only the default
270
+ misses half the traffic.
271
+
272
+ A call seen by both adapters is recorded once, not twice.
273
+
274
+ Why an explicit call at all: an axios instance is a plain object with no global
275
+ to patch, so there is no way for the tracer to reach one it was never handed.
276
+ This is the smallest integration that works, and it is the only one in the
277
+ package.
278
+
279
+ ## SSR
280
+
281
+ Importing the package evaluates no browser global and installs nothing. `init()`
282
+ checks for each runtime capability before patching it, so on a server it simply
283
+ finds nothing to patch and returns.
284
+
285
+ ```ts
286
+ // safe anywhere
287
+ import { apiTracer } from 'api-tracer-kit';
288
+
289
+ if (typeof window !== 'undefined') apiTracer.init();
290
+ ```
291
+
292
+ The guard is not required — `init()` is already a no-op without a runtime — but
293
+ it makes the intent obvious in a Next.js file that runs on both sides.
294
+
295
+ ## Not breaking your API calls
296
+
297
+ The tracer is built so that removing it changes nothing. Specifically:
298
+
299
+ - responses are read through `Response.clone()`, after the original has been
300
+ handed back, so your `.json()` still works
301
+ - streaming responses (`text/event-stream`) are never buffered
302
+ - binary and non-textual responses are described, not read
303
+ - request bodies that are `ReadableStream`s are never consumed
304
+ - bodies over `maxBodyBytes` are recorded as omitted rather than copied
305
+ - errors are rethrown exactly as they arrived; aborts stay `AbortError`
306
+ - `AbortSignal`, redirects, credentials and headers are passed through untouched
307
+ - nothing is retried, and no request is ever made twice
308
+ - XHR is observed with `addEventListener`, never by taking `onload` or
309
+ `onreadystatechange`
310
+ - a subscriber that throws is caught, so a bad listener cannot break a request
311
+ - `destroy()` restores every patched global and every axios adapter
312
+
313
+ ## The console
314
+
315
+ ```bash
316
+ npx api-tracer scan # read the source, write the endpoint catalog
317
+ npx api-tracer serve # the UI, at http://127.0.0.1:4400
318
+ npx api-tracer start # both
319
+ npx api-tracer report # the insight report, Markdown or JSON
320
+ npx api-tracer init # write a config file, if the guesses need help
321
+ ```
322
+
323
+ ### How it finds your endpoints
324
+
325
+ It runs every scanner preset over your codebase and keeps whichever actually
326
+ reads it:
327
+
328
+ | preset | what it reads |
329
+ | --- | --- |
330
+ | `service-object` | `const request = { subUrl: '/users' }; return get(request)` |
331
+ | `axios-direct` | `axios.get('/users')`, `api.post(\`/users/${id}\`, body)` |
332
+ | `fetch-direct` | `fetch('/api/users', { method: 'POST' })` |
333
+
334
+ Base URLs and the auth header name are read from your source too, so your
335
+ environments stay in sync with the app rather than being copied into a config.
336
+
337
+ If none of the presets fit, supply your own in `api-tracer.config.mjs`:
338
+
339
+ ```js
340
+ export default {
341
+ parse: (src, config) => [
342
+ { name, method, subUrl, usesParams, usesData, chunk, index },
343
+ ],
344
+ };
345
+ ```
346
+
347
+ ### Config file
348
+
349
+ Optional. `api-tracer init` writes one filled in with what the scan worked out.
350
+ Every field has a default:
351
+
352
+ ```js
353
+ export default {
354
+ name: 'My API',
355
+ preset: 'service-object',
356
+ sources: ['src'],
357
+ baseUrls: { dev: 'https://dev.example.com/api' },
358
+ auth: { header: 'Authorization' },
359
+ envelope: { codeFields: ['status', 'code'], okField: 'success', failFrom: 400 },
360
+ login: { steps: [/* an optional multi-step sign-in chain */] },
361
+ };
362
+ ```
363
+
364
+ ### Environment variables
365
+
366
+ | | |
367
+ | --- | --- |
368
+ | `API_TRACER_DATA` | where captures are kept (default `<project>/.api-tracer`) |
369
+ | `PORT` / `HOST` | default `4400` / `127.0.0.1` |
370
+ | `BASE_PATH` | serve under a path, e.g. `/api-console`, behind a proxy |
371
+ | `ALLOWED_ORIGINS` | comma-separated origins allowed to post recordings |
372
+ | `CONSOLE_USER` / `CONSOLE_PASS` | basic auth on everything except `/api/record` |
373
+ | `LOCK_RECORDING` / `LOCK_ENV` | stop one person's click changing a shared console |
374
+
375
+ ## Security
376
+
377
+ The tracer sees whatever your API sees, which on some products means
378
+ credentials and personal data. So:
379
+
380
+ - `Authorization`, `Cookie`, `Set-Cookie`, `X-API-Key` and the usual token
381
+ header names are redacted by default, and the list is configurable
382
+ - any field whose **name** looks like a credential (`token`, `password`,
383
+ `secret`, `api_key`, `otp`, …) is replaced with `<redacted>`, keeping the
384
+ surrounding shape so the sample stays useful
385
+ - redaction happens before a trace is stored, so a redacted value is never held
386
+ in memory either
387
+ - the console redacts again before writing anything to disk
388
+ - HAR, cURL and Postman exports carry `<paste your token>` in place of a real
389
+ token, so an export is safe to attach to a ticket
390
+
391
+ The console's data directory holds captured request and response bodies from a
392
+ real environment. Treat it as sensitive: keep it out of version control (it is
393
+ `.api-tracer/`, add it to `.gitignore`) and keep a deployed console behind VPN
394
+ or SSO.
395
+
396
+ ## Limitations
397
+
398
+ Worth knowing before you rely on it.
399
+
400
+ - **An axios instance must be handed over.** There is no global to patch. In a
401
+ browser the XHR adapter catches axios anyway; in Node it cannot.
402
+ - **A request body that is a stream is never read**, because reading it would
403
+ empty it. It is recorded as `omitted: 'stream'`.
404
+ - **Files in a `FormData` are described, not captured**, so a captured upload
405
+ cannot be replayed with its file.
406
+ - **Response bodies over `maxBodyBytes` are omitted**, not truncated — a partial
407
+ JSON body is more misleading than none.
408
+ - **`Response.clone()` buffers.** For a large non-streaming response the clone
409
+ holds a second copy until it is read. The size cap keeps this bounded.
410
+ - **The scanner is regex-based.** It reads the three shapes above well and says
411
+ what it could not read; it does not understand a URL assembled from three
412
+ variables. Live recording covers what the scan misses — an unexplained call
413
+ becomes an endpoint of its own.
414
+ - **The "unused endpoint" count is a substring count**, so `import * as services`
415
+ or a re-export would hide a real usage. Confirm with a grep before deleting.
416
+ - **The console is single-user by design**: one token, one selected environment,
417
+ one set of results.
418
+
419
+ ## Architecture
420
+
421
+ ```
422
+ api-tracer-kit
423
+ ├── src/
424
+ │ ├── core/ tracer, data model, redaction, bodies, URLs, envelopes, runtime detection
425
+ │ ├── adapters/ fetch, xhr, axios, and the suppression that stops double-counting
426
+ │ ├── storage/ the TraceStorage interface and the capped in-memory default
427
+ │ ├── transport/ posting finished traces to a console
428
+ │ ├── ui/ the React panel (optional entry point)
429
+ │ ├── index.ts the public API
430
+ │ ├── axios.ts api-tracer-kit/axios
431
+ │ └── react.ts api-tracer-kit/react
432
+ └── cli/
433
+ ├── bin/ the api-tracer command
434
+ ├── presets.mjs how to find endpoints in a codebase
435
+ ├── config.mjs defaults, and guessing what was not configured
436
+ ├── scan.mjs source -> endpoint catalog
437
+ ├── server.mjs the console: forwards calls, replays, exports, records
438
+ ├── import.mjs HAR and cURL, matched back to the catalog
439
+ ├── shape.mjs response shapes and drift diffing
440
+ ├── report.mjs the insight report model and its Markdown export
441
+ └── web/ the dashboard (no framework, no build step)
442
+ ```
443
+
444
+ The core is framework-free and knows nothing about any particular application.
445
+ The console knows nothing about it either: the catalog supplies the endpoints,
446
+ base URLs, auth header and envelope convention, and the config file supplies the
447
+ sign-in flow if there is one worth automating.
448
+
449
+ ## Development
450
+
451
+ ```bash
452
+ npm install
453
+ npm run build # tsup -> dist/, ESM + CJS + .d.ts
454
+ npm test # the tracer suite, then the console suite
455
+ npm run typecheck
456
+ ```
457
+
458
+ The example project doubles as an end-to-end check:
459
+
460
+ ```bash
461
+ cd examples/basic && npm install && npm start
462
+ ```
463
+
464
+ ## Licence
465
+
466
+ MIT