@sitar_fiercer4c/skills 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 +5 -0
- package/README.md +75 -0
- package/bin/install.js +45 -0
- package/package.json +29 -0
- package/skills/architecture-walkthrough/SKILL.md +223 -0
- package/skills/architecture-walkthrough/references/sections.md +29 -0
- package/skills/architecture-walkthrough/scripts/check_structure.py +200 -0
- package/skills/autotest-webapp-ui/SKILL.md +58 -0
- package/skills/backend-code-review/SKILL.md +386 -0
- package/skills/backend-code-review/references/report-format.md +333 -0
- package/skills/backend-code-review/scripts/list_routes.py +269 -0
- package/skills/backend-code-review/scripts/sweep.py +550 -0
- package/skills/backend-code-review/scripts/verify_citations.py +201 -0
- package/skills/be-brief/SKILL.md +18 -0
- package/skills/clarke-list-excel/SKILL.md +51 -0
- package/skills/clarke-list-excel/references/output-schema.md +125 -0
- package/skills/clarke-list-excel/scripts/clarke_common.py +251 -0
- package/skills/clarke-list-excel/scripts/clarke_extract.py +487 -0
- package/skills/clarke-list-excel/scripts/load_clarke.py +322 -0
- package/skills/clarke-list-excel/scripts/run_all.py +63 -0
- package/skills/datalab-api/SKILL.md +163 -0
- package/skills/datalab-api/references/parameters-and-payload.md +121 -0
- package/skills/datalab-api/references/table-selection.md +35 -0
- package/skills/datalab-api/scripts/datalab_tables.py +365 -0
- package/skills/find-test-seam/SKILL.md +41 -0
- package/skills/frontend-code-review/SKILL.md +247 -0
- package/skills/frontend-code-review-2/SKILL.md +192 -0
- package/skills/frontend-code-review-2/scripts/fetch_pr_comments.py +65 -0
- package/skills/frontend-code-review-2/scripts/render_report.py +139 -0
- package/skills/murtaza-breif/SKILL.md +143 -0
- package/skills/murtaza-breif/scripts/save_brief.py +128 -0
- package/skills/pdf-to-json/SKILL.md +42 -0
- package/skills/pdf-to-json/references/output-schema.md +168 -0
- package/skills/pdf-to-json/scripts/extract_figures.py +319 -0
- package/skills/pdf-to-json/scripts/load_mongo.py +287 -0
- package/skills/pdf-to-json/scripts/pdf_extract.py +1313 -0
- package/skills/record-api-traffic/SKILL.md +434 -0
- package/skills/record-api-traffic/references/reading-recordings.md +224 -0
- package/skills/record-api-traffic/scripts/check-schema.mjs +184 -0
- package/skills/record-api-traffic/scripts/dump-quotation.mjs +67 -0
- package/skills/record-api-traffic/scripts/dump-source-excel.mjs +75 -0
- package/skills/record-api-traffic/scripts/lib/repo.mjs +109 -0
- package/skills/record-api-traffic/scripts/preflight.py +528 -0
- package/skills/record-api-traffic/scripts/record-api-traffic.py +720 -0
- package/skills/refac-wrt-business-goal/SKILL.md +305 -0
- package/skills/refac-wrt-business-goal/references/critic.md +170 -0
- package/skills/system-resource-triage/SKILL.md +180 -0
- package/skills/system-resource-triage/scripts/reap.sh +116 -0
- package/skills/system-resource-triage/scripts/triage.sh +111 -0
- package/skills/using-git-worktrees/SKILL.md +167 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: record-api-traffic
|
|
3
|
+
description: Record the HTTP traffic of a Node/Next app on four legs — browser-to-frontend, browser-to-backend, and each server's own outbound calls (the frontend's server-side fetches, the backend's to Microsoft Graph and Datalab) — by running this skill's own recorder against a repo path, after a preflight that finds every blocker first. Everything lives in the skill; the repo is only read. Use this skill whenever the user wants to capture, record, trace, log or inspect those calls — "record the API traffic", "record the frontend traffic", "start the recorder", "capture what the frontend sends", "what pages did the browser load", "what does the backend call out to", "I want a log of the API calls" — and also whenever they hit trouble around it: "port 7100 is in use", "the recording is empty", "the backend won't start", "the dev server won't move off 4100", "mitmproxy won't bind", "what's holding 4100". Also use it when they ask what a finished recording contains or how to read the .jsonl and .log output, or when they want the pipeline's real input and output — the source Excel file or the stored quotation document — dumped for a given quotationId.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Recording API traffic
|
|
7
|
+
|
|
8
|
+
## What this is
|
|
9
|
+
|
|
10
|
+
Everything this skill needs lives in the skill. `scripts/record-api-traffic.py` starts six
|
|
11
|
+
processes — the two servers, each moved one port up, with a mitmproxy in front of it and a
|
|
12
|
+
second mitmproxy recording its own outbound calls — and prints a link. The user opens it, uses
|
|
13
|
+
the app, presses Ctrl-C, and is left with a single folder for that run holding four pairs of
|
|
14
|
+
files: for each leg, an aligned table of every call and a JSONL file with full request and
|
|
15
|
+
response bodies.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
browser ──→ :4100 recorder ──→ :4101 frontend ──→ :4102 recorder ──→ the backend, external
|
|
19
|
+
└─→ :7100 recorder ──→ :7101 backend ──→ :7102 recorder ──→ Graph, Datalab
|
|
20
|
+
|
|
21
|
+
:4100 frontend-inbound.* what arrives at the frontend (browser)
|
|
22
|
+
:4102 frontend-outbound.* what the frontend calls out to (backend, external)
|
|
23
|
+
:7100 backend-inbound.* what arrives at the backend (browser, frontend)
|
|
24
|
+
:7102 backend-outbound.* what the backend calls out to (Graph, Datalab)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both servers are treated identically: the recorder takes the port that is already forwarded to
|
|
28
|
+
the laptop, the server moves one up behind it, and a forward proxy catches what that server's
|
|
29
|
+
own Node process calls out to. Nothing on the VS Code side changes and no config file in the
|
|
30
|
+
repo is edited.
|
|
31
|
+
|
|
32
|
+
**Ask for one thing: the repo path.** Every script here takes it as its single input, and once
|
|
33
|
+
you have it you reuse it for all of them. Nothing else is asked of the user.
|
|
34
|
+
|
|
35
|
+
**The repo is read, never written.** The backend's working directory is a temp dir under `/tmp`,
|
|
36
|
+
recordings and dumps land in `skills/record-api-traffic-workspace/recordings/`, and no config
|
|
37
|
+
file is edited.
|
|
38
|
+
The one exception is honest and unavoidable: `npm run dev` writes `Frontend/.next/`, exactly as
|
|
39
|
+
it would if the user started the dev server themselves. It is gitignored. If you ever find
|
|
40
|
+
yourself about to create, edit or delete anything else under the repo, stop — that is a bug in
|
|
41
|
+
what you are doing, not a step.
|
|
42
|
+
|
|
43
|
+
The scripts work. The hard part is everything around them: ports are frequently occupied, the
|
|
44
|
+
frontend can be silently pointed away from the recorder, and stopping a stray process is a
|
|
45
|
+
decision that needs the user's judgment rather than yours. That is what this skill is for.
|
|
46
|
+
|
|
47
|
+
Run the preflight, resolve what it finds *with* the user, launch, then prove the recording is
|
|
48
|
+
actually capturing before calling it done.
|
|
49
|
+
|
|
50
|
+
## Why the recorders sit on 7100 and 4100
|
|
51
|
+
|
|
52
|
+
Worth understanding, because it explains several checks. The browser runs on the user's laptop
|
|
53
|
+
and reaches the VM through VS Code port forwarding. `:4100` and `:7100` are already forwarded.
|
|
54
|
+
So each recorder takes over the port the browser already calls and the server moves one up
|
|
55
|
+
behind it. Nothing changes on the laptop, no new port needs forwarding, and no config file in
|
|
56
|
+
the repo is edited. `BACKEND_PUBLIC_URL` and `AZURE_REDIRECT_URI` already point at `:7100`, so
|
|
57
|
+
the Outlook OAuth callback keeps working and gets recorded too.
|
|
58
|
+
|
|
59
|
+
The two servers resist being moved in different ways, and each needs its own trick.
|
|
60
|
+
|
|
61
|
+
**The backend** ignores shell env vars: `app.js` calls `dotenv.config({ override: true })`, so
|
|
62
|
+
`.env` wins. The recorder instead runs `node src/server.js` from a temp directory holding a copy
|
|
63
|
+
of `.env` with `PORT=7101`, since dotenv resolves its file from the current working directory.
|
|
64
|
+
That copy is deleted on teardown, and it is why nothing has to be written inside the repo to
|
|
65
|
+
move the port.
|
|
66
|
+
|
|
67
|
+
**The frontend** ignores `PORT` for a different reason: the dev script pins the port on the
|
|
68
|
+
command line (`next dev -H 0.0.0.0 -p 4100`), and commander only consults `.env("PORT")` when
|
|
69
|
+
the flag is absent. So the recorder appends a second one — `npm run dev -- -p 4101` — which both
|
|
70
|
+
argument parsers Next has shipped resolve to the last occurrence. `PORT` is exported alongside
|
|
71
|
+
for a dev script that leaves the port unpinned. Preflight reports which of the two applies, and
|
|
72
|
+
warns when the dev script is not a Next one at all.
|
|
73
|
+
|
|
74
|
+
## Why there are two forward proxies
|
|
75
|
+
|
|
76
|
+
A reverse proxy only sees what the browser sends. Plenty of what these apps do never crosses one:
|
|
77
|
+
the Excel attachment the backend pulls from Microsoft Graph server-side, the Datalab extraction
|
|
78
|
+
calls, and anything the frontend fetches from a server component or route handler rather than
|
|
79
|
+
from the browser. Those are outbound HTTPS from a Node process, so each server gets a
|
|
80
|
+
`mitmdump --mode regular` of its own and is pointed at it with `HTTP_PROXY`/`HTTPS_PROXY` plus
|
|
81
|
+
`NODE_USE_ENV_PROXY=1` — Node's global `fetch` ignores proxy env vars without that flag.
|
|
82
|
+
`NODE_EXTRA_CA_CERTS` makes it trust mitmproxy's CA.
|
|
83
|
+
|
|
84
|
+
The two differ in one respect, and it is deliberate. The backend excludes all of loopback from
|
|
85
|
+
its proxy (`NO_PROXY=127.0.0.1,localhost`), because it has no loopback calls worth recording.
|
|
86
|
+
The frontend excludes only its **own** two ports, so a server component calling the backend on
|
|
87
|
+
`:7100` still goes through `:4102` — that call is the main thing the frontend outbound leg
|
|
88
|
+
exists to catch. It is then recorded twice, once leaving the frontend and once arriving at the
|
|
89
|
+
backend, which is the point: you can see both ends of the same call.
|
|
90
|
+
|
|
91
|
+
Not covered on either: the AWS SDK (its own HTTP handler ignores the env) and MongoDB (a binary
|
|
92
|
+
protocol, not HTTP). `--no-outbound` skips both forward proxies.
|
|
93
|
+
|
|
94
|
+
## Step 1 — Preflight
|
|
95
|
+
|
|
96
|
+
Always run this before launching. It is read-only: it never kills a process, writes a file, or
|
|
97
|
+
edits config.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python3 skills/record-api-traffic/scripts/preflight.py <repo>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--json` for machine-readable output. Exit code 0 means clear, 1 means blocked, 2 means the path
|
|
104
|
+
is not an app repo.
|
|
105
|
+
|
|
106
|
+
It reports every check at once rather than dying on the first failure, which matters
|
|
107
|
+
because blockers cluster — an abandoned session typically leaves a proxy on 7100 *and* a dev
|
|
108
|
+
server on 4100, and discovering those one launch-attempt at a time is slow and irritating.
|
|
109
|
+
|
|
110
|
+
## Step 2 — Clear the blockers
|
|
111
|
+
|
|
112
|
+
Present what preflight found in plain terms, then work through it. Two rules shape everything
|
|
113
|
+
here.
|
|
114
|
+
|
|
115
|
+
**Never stop a process you have not identified.** Preflight classifies each port holder as
|
|
116
|
+
`[ours]` (a leftover from this recorder), `[app]` (the project's own dev server), or
|
|
117
|
+
`[foreign]` (something else entirely), and prints the working directory and command line for
|
|
118
|
+
foreign ones. That distinction is the whole point. A `mitmdump` on 7100 could be this tool's
|
|
119
|
+
own orphan, or it could be a different project's recording session that someone is actively
|
|
120
|
+
using — killing it destroys their work with no way to recover it.
|
|
121
|
+
|
|
122
|
+
**Killing is the user's call, not yours.** For `[ours]` and `[app]` you can offer directly and
|
|
123
|
+
act once they agree. For `[foreign]`, show them the command line and cwd and ask what it is
|
|
124
|
+
before proposing anything. If they are unsure, leave it alone and say so — an unstarted
|
|
125
|
+
recording costs a minute, a destroyed session costs an afternoon.
|
|
126
|
+
|
|
127
|
+
### The blockers you will actually see
|
|
128
|
+
|
|
129
|
+
**Port held by a foreign process.** The common real case: a `mitmdump` from another repo's
|
|
130
|
+
recording setup, sometimes half-dead (proxy still listening, its backend long gone). Show the
|
|
131
|
+
cwd and argv, ask, and only then offer `kill <pid>`. Prefer plain `kill` over `kill -9` so the
|
|
132
|
+
process can shut down cleanly and flush whatever it was writing.
|
|
133
|
+
|
|
134
|
+
**Port held by the app's own dev server.** Someone ran `npm run dev` by hand. Safe to offer to
|
|
135
|
+
stop — but say what it is, because they may have work mid-flight in that browser tab.
|
|
136
|
+
|
|
137
|
+
Six ports are checked now, not four: `4100`/`4101`/`4102` and `7100`/`7101`/`7102`. Only the
|
|
138
|
+
two `x100` ports are ever forwarded or called from outside, so a holder on one of the other four
|
|
139
|
+
is almost always debris. Preflight now also reads each holder's environment for the marker the
|
|
140
|
+
recorder exports into every child, which is what lets it call a leftover dev server on `:4101`
|
|
141
|
+
`[ours]` rather than `[app]` — the frontend has to run with the repo as its working directory,
|
|
142
|
+
so cwd alone cannot tell the two apart.
|
|
143
|
+
|
|
144
|
+
**The dev script cannot be moved off 4100.** Preflight reads `Frontend/package.json` and says
|
|
145
|
+
how the port will be moved. A `next` script is fine. Anything else falls back to `PORT`, and if
|
|
146
|
+
that script pins its own port instead, it will fight the recorder for `:4100` and one of them
|
|
147
|
+
loses. Read what preflight prints rather than assuming; `--no-frontend` is the way out if the
|
|
148
|
+
script cannot be moved.
|
|
149
|
+
|
|
150
|
+
**Frontend env points somewhere else.** This is the one to care about most. If
|
|
151
|
+
`NEXT_PUBLIC_API_URL` does not contain `:7100`, the browser talks straight to the backend and
|
|
152
|
+
the API recorder sees nothing. Everything appears to work — processes start, the link opens, the
|
|
153
|
+
app functions — and `backend-inbound.log` comes out empty. A crash is honest; this failure mode
|
|
154
|
+
lies. Fix `Frontend/.env.local` before launching. That file is the user's to edit, not yours.
|
|
155
|
+
|
|
156
|
+
Note what this does *not* affect: `frontend-inbound.log` fills up regardless, because the
|
|
157
|
+
browser reaches the frontend through `:4100` either way. A run with pages in one file and
|
|
158
|
+
nothing in the other is this bug, not a quiet recorder.
|
|
159
|
+
|
|
160
|
+
**Dump scripts out of sync with the app.** See the next section — the one blocker that is new
|
|
161
|
+
and easy to under-react to.
|
|
162
|
+
|
|
163
|
+
**MongoDB unreachable.** The backend never passes its health check and the script gives up after
|
|
164
|
+
90 seconds with no obvious cause. Preflight resolves this ahead of time. Note that Atlas
|
|
165
|
+
hostnames are SRV-only, so a plain A/AAAA lookup fails even when the cluster is perfectly
|
|
166
|
+
healthy — check `_mongodb._tcp.<host>` instead. Preflight already does this correctly; don't
|
|
167
|
+
"fix" it with `ping` or `getent hosts` and conclude the network is down.
|
|
168
|
+
|
|
169
|
+
**Missing mitmproxy CA.** `~/.mitmproxy/mitmproxy-ca-cert.pem` is written on mitmproxy's first
|
|
170
|
+
ever run. Without it the backend cannot trust the outbound proxy and every outbound call fails.
|
|
171
|
+
Generate it by starting `mitmdump` once, or launch with `--no-outbound`.
|
|
172
|
+
|
|
173
|
+
**Orphans or a leftover scratch directory.** Debris from a previous run that crashed hard. The
|
|
174
|
+
orphaned processes are this tool's own leftovers, so they are safe to offer to stop. A stale
|
|
175
|
+
`/tmp/record-api-traffic-*` is only a warning. If preflight mentions `Backend/.mitm-run`, that is
|
|
176
|
+
from an older version of this skill that wrote inside the repo; report it and let the user delete
|
|
177
|
+
it — do not delete it yourself.
|
|
178
|
+
|
|
179
|
+
One caveat, now that the frontend runs under the recorder: starting a dev server spawns
|
|
180
|
+
short-lived helpers that inherit the recorder's marker, and one can outlive teardown by a while
|
|
181
|
+
without holding any port. So an orphan report whose ports are all free, shortly after a session
|
|
182
|
+
ended, is worth re-running before acting on — it usually clears on its own. A report that
|
|
183
|
+
persists, or that comes with a busy port, is real.
|
|
184
|
+
|
|
185
|
+
## Step 3 — Check the dump scripts still fit the app
|
|
186
|
+
|
|
187
|
+
`scripts/dump-quotation.mjs` and `scripts/dump-source-excel.mjs` read the app's Mongoose model
|
|
188
|
+
directly. They used to live in the app repo, where a schema change and the code reading it moved
|
|
189
|
+
in the same commit. They no longer do, and nothing keeps them honest but this check:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
node skills/record-api-traffic/scripts/check-schema.mjs --repo <repo>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Preflight runs it for you and folds the result into its report; run it directly when you want the
|
|
196
|
+
per-field detail. It loads the real model and asserts every field the dumps touch — that
|
|
197
|
+
`sourceWorkbookBuffer` exists **and is still a Buffer**, that `db.js` still default-exports
|
|
198
|
+
`connectDB`, that `email.service.getAttachmentBuffer` still takes three arguments.
|
|
199
|
+
|
|
200
|
+
Take a `FAIL` here seriously, because drift is silent. A renamed field does not crash: the
|
|
201
|
+
document still loads, `quotation.sourceWorkbookBuffer` is merely `undefined`, and
|
|
202
|
+
`dump-source-excel` falls through to the Graph refetch or writes a zero-byte file. You get an
|
|
203
|
+
artifact that looks like an artifact and is worthless. Fix the script in the skill before
|
|
204
|
+
recording, rather than after discovering the dump is empty.
|
|
205
|
+
|
|
206
|
+
`warn` on a display-only field (`status`, `quotationItems`) means the summary line degrades but
|
|
207
|
+
the dumped bytes are still right — worth mentioning, not worth blocking on. Exit code 2 means
|
|
208
|
+
the check could not run at all, almost always because `Backend/node_modules` is missing; that is
|
|
209
|
+
the dependency blocker, not a schema problem.
|
|
210
|
+
|
|
211
|
+
## Step 4 — Launch
|
|
212
|
+
|
|
213
|
+
The recorder is a long-lived foreground process that the user stops with Ctrl-C. Hand the
|
|
214
|
+
command over rather than running it yourself in the background, where they cannot reach it and
|
|
215
|
+
its output is buried:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
python3 skills/record-api-traffic/scripts/record-api-traffic.py <repo>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Suggest they type `! python3 skills/record-api-traffic/scripts/record-api-traffic.py <repo>` in
|
|
222
|
+
the prompt so the output lands in the conversation.
|
|
223
|
+
|
|
224
|
+
Startup takes up to a couple of minutes — the backend waits on Mongo, and Next.js compiles the
|
|
225
|
+
first page on demand. It prints progress for each stage, then a banner with the link
|
|
226
|
+
(`http://localhost:4100`), the run directory and one path per active leg.
|
|
227
|
+
|
|
228
|
+
If it dies during startup it prints the failing child's last 40 lines. Read those rather than
|
|
229
|
+
guessing; the cause is nearly always in them.
|
|
230
|
+
|
|
231
|
+
Three flags, none of them the normal case:
|
|
232
|
+
|
|
233
|
+
- `--no-frontend` leaves the dev server on `:4100` unrecorded, exactly as it ran before the
|
|
234
|
+
frontend legs existed. Reach for it when the dev script cannot be moved off `:4100`, or when
|
|
235
|
+
fronting Next with a proxy is itself the suspect.
|
|
236
|
+
- `--no-outbound` drops **both** forward proxies, not just the backend's. It is the answer when
|
|
237
|
+
`~/.mitmproxy/mitmproxy-ca-cert.pem` is missing or Node is older than 24, since either
|
|
238
|
+
condition breaks both equally.
|
|
239
|
+
- `--frontend-all` keeps the static-asset traffic the frontend leg drops by default. Worth it
|
|
240
|
+
when the question is about asset loading or chunk sizes, and a bad idea otherwise — see the
|
|
241
|
+
quirk on noise below.
|
|
242
|
+
|
|
243
|
+
## Step 5 — Prove it is recording
|
|
244
|
+
|
|
245
|
+
Do not report success because processes started. Confirm a call was actually captured, on both
|
|
246
|
+
browser-facing legs — they fail independently, so one row proves nothing about the other:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
run=$(ls -d skills/record-api-traffic-workspace/recordings/*/run-* | tail -1)
|
|
250
|
+
curl -s -o /dev/null http://127.0.0.1:7100/health
|
|
251
|
+
curl -s -o /dev/null http://127.0.0.1:4100/
|
|
252
|
+
tail -3 "$run/backend-inbound.log"
|
|
253
|
+
tail -3 "$run/frontend-inbound.log"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
A row should appear for `/health` in the first and for `/` in the second. If a file has only its
|
|
257
|
+
header after the user has clicked around the app:
|
|
258
|
+
|
|
259
|
+
- **`backend-inbound.log` empty, `frontend-inbound.log` filling.** The browser is reaching the
|
|
260
|
+
frontend but the frontend is not routing API calls through `:7100`. Re-check
|
|
261
|
+
`NEXT_PUBLIC_API_URL`.
|
|
262
|
+
- **Both empty.** The browser is not reaching `:4100` at all — a stale forwarded port, or a tab
|
|
263
|
+
still pointed at an old URL.
|
|
264
|
+
- **`frontend-outbound.log` empty.** Usually correct, not broken. It only fills when
|
|
265
|
+
the frontend fetches from Node — a server component, a route handler, or the dev server's own
|
|
266
|
+
telemetry. An app whose data fetching all happens in the browser leaves this leg nearly empty
|
|
267
|
+
by design.
|
|
268
|
+
|
|
269
|
+
This step exists because the expensive failure is a session that felt fine and produced nothing.
|
|
270
|
+
Thirty seconds here saves re-running the whole thing.
|
|
271
|
+
|
|
272
|
+
## Step 6 — Read the output
|
|
273
|
+
|
|
274
|
+
Recordings live in the skill's workspace, not the repo, under a folder named for the repo they
|
|
275
|
+
came from, so two clones of the same project never mix. The workspace sits beside the skill
|
|
276
|
+
rather than inside it, because `skills/*-workspace/` is already gitignored — which is what keeps
|
|
277
|
+
the tokens in a recording out of this repo's history:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
skills/record-api-traffic-workspace/
|
|
281
|
+
iteration-N/ eval and benchmark output, not yours to touch
|
|
282
|
+
recordings/
|
|
283
|
+
<last-two-path-segments-of-the-repo>/
|
|
284
|
+
run-20260907-015953/
|
|
285
|
+
run.json repo, time, and which legs were on
|
|
286
|
+
backend-inbound.log into the backend, scannable table
|
|
287
|
+
backend-inbound.jsonl the same calls with full bodies
|
|
288
|
+
backend-outbound.log out of the backend (Graph, Datalab)
|
|
289
|
+
backend-outbound.jsonl
|
|
290
|
+
frontend-inbound.log into the frontend (browser)
|
|
291
|
+
frontend-inbound.jsonl
|
|
292
|
+
frontend-outbound.log out of the frontend (backend, external)
|
|
293
|
+
frontend-outbound.jsonl
|
|
294
|
+
artifacts/ written by the dump scripts in Step 7
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
One run, one directory; nothing to correlate by timestamp across a flat listing.
|
|
298
|
+
|
|
299
|
+
The `.log` is one row per call, time first. The `.jsonl` holds the full bodies, one JSON object
|
|
300
|
+
per line, where line number equals the row's ID. The two forward-proxy legs put `host:port` in
|
|
301
|
+
the PATH column, since they span several hosts — and, on the frontend's, several loopback ports
|
|
302
|
+
that would otherwise all read as `127.0.0.1`.
|
|
303
|
+
|
|
304
|
+
`run.json` records which legs were active. A missing file means that leg was switched off, not
|
|
305
|
+
that it captured nothing; check there before concluding a recorder failed.
|
|
306
|
+
|
|
307
|
+
The banner prints `Run dir` at startup. When working with "the recording" later, resolve the
|
|
308
|
+
newest run for that repo rather than assuming, and check `run.json` if you are unsure which repo
|
|
309
|
+
a folder belongs to.
|
|
310
|
+
|
|
311
|
+
For formats, field meanings and jq recipes, read `references/reading-recordings.md`.
|
|
312
|
+
|
|
313
|
+
## Step 7 — Stop, verify, dump
|
|
314
|
+
|
|
315
|
+
When the user signals they are finished with the app — "done", "stop recording", "that's it" —
|
|
316
|
+
that single signal means all three of the following. Carry them out in order without waiting to
|
|
317
|
+
be asked for each one. A run that stops without its dumps is an incomplete recording, and the
|
|
318
|
+
user will not know to ask for them.
|
|
319
|
+
|
|
320
|
+
### 7a — Stop
|
|
321
|
+
|
|
322
|
+
Ctrl-C stops all six processes, removes the temp scratch directory, and prints a count of calls
|
|
323
|
+
recorded and failed for each leg.
|
|
324
|
+
|
|
325
|
+
If you launched it in the background rather than handing it over, Ctrl-C cannot reach it. Send
|
|
326
|
+
`kill -INT` to the `python3` process itself — not `kill -9`, which skips teardown and loses both
|
|
327
|
+
the summary and the scratch cleanup:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
pgrep -af '[r]ecord-api-traffic.py' | grep -v 'zsh -c'
|
|
331
|
+
kill -INT <pid of the "python3 .../record-api-traffic.py" process>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Read that list and pick the right line rather than piping to `head -1`. It also contains the
|
|
335
|
+
launching shell (`/bin/zsh -c ... eval 'python3 ...'`) and the mitmdump children — eight lines of
|
|
336
|
+
them, since mitmproxy forks and each of the four recorders shows up twice. All match the pattern.
|
|
337
|
+
Signalling the shell does nothing — the recorder keeps running and the ports stay bound, which
|
|
338
|
+
looks exactly like a teardown that silently failed. Confirm with the port check in 7b rather than
|
|
339
|
+
assuming the signal landed.
|
|
340
|
+
|
|
341
|
+
### 7b — Verify the teardown
|
|
342
|
+
|
|
343
|
+
The repo should be exactly as it was, apart from the dev server's gitignored `.next/`:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
git -C <repo> status --short # nothing from the recording
|
|
347
|
+
ls -d /tmp/record-api-traffic-* 2>/dev/null # gone
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Then confirm the ports are free. Do not go looking for processes by name:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
for p in 4100 4101 4102 7100 7101 7102; do
|
|
354
|
+
ss -ltnp | grep -q ":$p " && echo "$p BUSY" || echo "$p free"
|
|
355
|
+
done
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
A `pgrep -af '[m]itmdump|[s]erver\.js|[n]ext dev'` sweep is deliberately broad and will catch
|
|
359
|
+
unrelated things — VS Code's `tsserver.js` matches `server.js`, and other repos run their own
|
|
360
|
+
mitmdump. The port check answers the actual question. If you do run pgrep, use bracketed patterns
|
|
361
|
+
like `[m]itmdump`; without them the pattern matches the shell running it, which reports a phantom
|
|
362
|
+
process and, with `pkill`, kills the shell itself. Report anything foreign rather than killing it.
|
|
363
|
+
|
|
364
|
+
### 7c — Dump the input and the output
|
|
365
|
+
|
|
366
|
+
The recording is HTTP only, so the pipeline's real input and output are not in it. Two scripts
|
|
367
|
+
fill that gap, both taking the repo and a quotationId. Get the id from the recording rather than
|
|
368
|
+
asking:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
grep -oE '/api/quotations/[a-f0-9]{24}' <run-dir>/backend-inbound.log \
|
|
372
|
+
| grep -oE '[a-f0-9]{24}' | sort | uniq -c | sort -rn
|
|
373
|
+
|
|
374
|
+
node skills/record-api-traffic/scripts/dump-source-excel.mjs --repo <repo> <quotationId>
|
|
375
|
+
node skills/record-api-traffic/scripts/dump-quotation.mjs --repo <repo> <quotationId>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Both default to `artifacts/` inside the newest run folder *for that repo*, so run them straight
|
|
379
|
+
after a session and they land beside the traffic they belong to. Pass a run dir as an optional
|
|
380
|
+
extra argument to target an older session. If several quotationIds appear in the log, dump each.
|
|
381
|
+
|
|
382
|
+
The Excel comes from `sourceWorkbookBuffer` on the document — the raw xlsx bytes are kept there
|
|
383
|
+
as the export template — and falls back to refetching from Graph when that field is null, which
|
|
384
|
+
is the case for CSV attachments and oversized documents. That fallback is rarely exercised;
|
|
385
|
+
treat a failure in it as untested code rather than a broken session.
|
|
386
|
+
|
|
387
|
+
Say plainly that these files contain live JWTs, the login password and Outlook tokens, recorded
|
|
388
|
+
raw by deliberate choice. The `skills/*-workspace/` gitignore rule keeps them out of this repo's
|
|
389
|
+
history, and nothing is written into the recorded app's repo at all. They
|
|
390
|
+
should not be pasted into issues or shared.
|
|
391
|
+
|
|
392
|
+
## Known quirks
|
|
393
|
+
|
|
394
|
+
**No record is written when the backend is unreachable.** mitmproxy aborts at the connection
|
|
395
|
+
stage before a flow exists, so its error hook never fires. If the backend dies mid-session,
|
|
396
|
+
expect silence in the log and a loud message in the terminal — the launcher notices within a
|
|
397
|
+
second and shuts everything down. Absence of rows during an outage is expected, not a bug.
|
|
398
|
+
|
|
399
|
+
**No hot reload on the backend.** It runs under plain `node`, not nodemon, because nodemon
|
|
400
|
+
watches the working directory and that is now a temp directory. Backend edits need a restart of
|
|
401
|
+
the recorder. The frontend is unaffected: `next dev` still runs normally, and its HMR websocket
|
|
402
|
+
upgrades cleanly through the reverse proxy, so editing a component reloads the browser as usual.
|
|
403
|
+
|
|
404
|
+
**The frontend leg drops static assets by default.** One page load in `next dev` pulls dozens of
|
|
405
|
+
`/_next/static/` chunks; recorded in full they would bury the handful of rows that carry meaning
|
|
406
|
+
and add tens of megabytes of JavaScript to the JSONL. So `/_next/static/`, `/_next/image`,
|
|
407
|
+
`/_next/webpack-hmr`, `/__nextjs*` and `/favicon.ico` are skipped before the row counter moves,
|
|
408
|
+
which keeps row IDs equal to JSONL line numbers. Navigations, RSC payloads, server actions and
|
|
409
|
+
any route handler the frontend serves itself are all kept. `--frontend-all` records everything.
|
|
410
|
+
|
|
411
|
+
**A frontend server-side call to the backend appears twice.** Once on
|
|
412
|
+
`frontend-outbound` as it leaves Node, once on `backend-inbound` as it arrives. Same
|
|
413
|
+
call, both ends. Match them on path and timestamp; the outbound row is the earlier of the two.
|
|
414
|
+
|
|
415
|
+
**The frontend outbound leg picks up npm and Next's own chatter.** `next dev` checks the npm
|
|
416
|
+
registry for a newer version at startup, and that request is a real outbound call from the
|
|
417
|
+
frontend's Node process, so it is recorded like any other. A row for `registry.npmjs.org` at the
|
|
418
|
+
top of the file is expected, not a leak.
|
|
419
|
+
|
|
420
|
+
**Bodies are capped at 1 MB** and binary responses (images, PDFs, spreadsheets) are recorded as
|
|
421
|
+
a size summary rather than base64. The quotation PUT path can carry 16 MB of inline images, so
|
|
422
|
+
without this the file would be unusable. This applies to every leg: the multipart upload to
|
|
423
|
+
Datalab shows as a size, not the file. Use `dump-source-excel.mjs` for the bytes.
|
|
424
|
+
|
|
425
|
+
Note what the cap does *not* cover on the frontend leg. JavaScript and HTML are text, so a page
|
|
426
|
+
or a chunk under 1 MB is stored in full — which is exactly why the noise filter above exists,
|
|
427
|
+
and why `--frontend-all` should be a considered choice rather than a default.
|
|
428
|
+
|
|
429
|
+
**The dump scripts resolve everything from `--repo` by hand.** They import the app's modules by
|
|
430
|
+
absolute path and its `mongoose` through `createRequire` rooted at `Backend/`, because a bare
|
|
431
|
+
`import mongoose` inside the skill would look in the skill's own non-existent `node_modules`.
|
|
432
|
+
They also parse `Backend/.env` themselves rather than relying on `dotenv` finding it relative to
|
|
433
|
+
the working directory. If you add a script here that touches the app, use `scripts/lib/repo.mjs`
|
|
434
|
+
rather than reinventing that.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Reading a recording
|
|
2
|
+
|
|
3
|
+
Every session writes its own folder in the skill's workspace,
|
|
4
|
+
`skills/record-api-traffic-workspace/recordings/<repo-slug>/run-<YYYYMMDD-HHMMSS>/`, containing:
|
|
5
|
+
|
|
6
|
+
- `run.json` — which repo this came from, when, and which legs were recorded
|
|
7
|
+
- `backend-inbound.log` / `.jsonl` — everything arriving at the backend. Mostly the browser,
|
|
8
|
+
but the frontend's own server-side calls land here too
|
|
9
|
+
- `backend-outbound.log` / `.jsonl` — what the backend calls out to: Microsoft Graph, Datalab
|
|
10
|
+
- `frontend-inbound.log` / `.jsonl` — everything arriving at the frontend: page navigations,
|
|
11
|
+
RSC payloads, server actions, and any route handler the frontend serves itself
|
|
12
|
+
- `frontend-outbound.log` / `.jsonl` — what the frontend calls out to, i.e. what its Node
|
|
13
|
+
process fetches rather than what the browser does: the backend, and anything external
|
|
14
|
+
- `artifacts/` — the source spreadsheet and stored quotation, written by the dump scripts
|
|
15
|
+
|
|
16
|
+
The names say which server is being watched and which way the traffic goes, so each server's
|
|
17
|
+
pair sits together in a listing. Note that "inbound" is deliberate rather than "from the
|
|
18
|
+
browser": `backend-inbound` catches whatever reaches `:7100`, whoever sent it.
|
|
19
|
+
|
|
20
|
+
Each pair is an aligned table for scanning plus one JSON object per line with the full bodies.
|
|
21
|
+
All four share a format, so every recipe below works on any of them — swap the filename.
|
|
22
|
+
|
|
23
|
+
The recipes below assume you are inside a run folder. To work on the most recent session:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd "$(ls -d skills/record-api-traffic-workspace/recordings/*/run-* | tail -1)"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The slug is the last two segments of the repo path, so two clones of the same project keep
|
|
30
|
+
separate folders. When several repos have been recorded, pick the right slug rather than the
|
|
31
|
+
newest run overall, and check `run.json` if you are unsure.
|
|
32
|
+
|
|
33
|
+
The only formatting difference between legs: the two forward-proxy files
|
|
34
|
+
(`backend-outbound`, `frontend-outbound`) put `host:port` in the `PATH` column,
|
|
35
|
+
because they span several hosts — and on the frontend's, several loopback ports that would
|
|
36
|
+
otherwise all read as `127.0.0.1`.
|
|
37
|
+
|
|
38
|
+
All are flushed after every call, so `tail -f` on a `.log` works while a session is live.
|
|
39
|
+
|
|
40
|
+
## Which file answers which question
|
|
41
|
+
|
|
42
|
+
| Question | File |
|
|
43
|
+
|---|---|
|
|
44
|
+
| What did the user's browser actually load? | `frontend-inbound` |
|
|
45
|
+
| Which API calls did the page make? | `backend-inbound` |
|
|
46
|
+
| Did a server component or route handler fetch this, or the browser? | `frontend-outbound` — if it is there, it came from Node |
|
|
47
|
+
| What did the backend call out to? | `backend-outbound` |
|
|
48
|
+
|
|
49
|
+
A server-side call from the frontend to the backend lands in **two** files: once leaving Node,
|
|
50
|
+
once arriving at the backend. That is not double-counting — it is both ends of one call, and
|
|
51
|
+
comparing them is how you see what the proxy chain did to it in between.
|
|
52
|
+
|
|
53
|
+
The frontend leg drops `/_next/static/`, `/_next/image`, `/_next/webpack-hmr`, `/__nextjs*` and
|
|
54
|
+
`/favicon.ico` unless the run used `--frontend-all`; `run.json` records which. The rows are
|
|
55
|
+
dropped before the counter moves, so IDs still equal line numbers.
|
|
56
|
+
|
|
57
|
+
## The table
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
TIME ID MS METHOD STATUS PATH REQ RESP
|
|
61
|
+
14:22:03.184 0001 1243 POST 200 /api/auth/login 86 B 412 B
|
|
62
|
+
14:22:06.771 0003 4021 GET 200 /api/email/messages?top=25 - 184 KB
|
|
63
|
+
14:22:31.004 0004 12908 POST 502 /api/quotation/extract 2.4 MB 311 B
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`ID` is the line number of the matching record in the `.jsonl`, so row `0003` is line 3. To
|
|
67
|
+
read a row's full bodies, jump straight to that line:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
sed -n '3p' backend-inbound.jsonl | jq .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Two things to know about the columns. `TIME` is when the response *completed*, not when the
|
|
74
|
+
request started — rows are appended on completion, which is what keeps ID equal to line number
|
|
75
|
+
even when requests overlap. The start time is in the JSONL as `started_at`. And paths longer
|
|
76
|
+
than 44 characters are ellipsized with `…` to hold the table's alignment; the full path and
|
|
77
|
+
parsed query string are always intact in the JSONL.
|
|
78
|
+
|
|
79
|
+
`STATUS` shows `ERR` for a flow that failed without a response.
|
|
80
|
+
|
|
81
|
+
## The JSONL record
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"id": "0002",
|
|
86
|
+
"ts": "2026-09-03T18:45:27.323364+00:00",
|
|
87
|
+
"started_at": "2026-09-03T18:45:27.321536+00:00",
|
|
88
|
+
"duration_ms": 1,
|
|
89
|
+
"method": "POST",
|
|
90
|
+
"path": "/api/auth/login",
|
|
91
|
+
"host": "127.0.0.1",
|
|
92
|
+
"url": "http://127.0.0.1:7100/api/auth/login",
|
|
93
|
+
"query": {},
|
|
94
|
+
"client_ip": "127.0.0.1",
|
|
95
|
+
"request": {
|
|
96
|
+
"headers": { "Authorization": "Bearer ...", "Content-Type": "application/json" },
|
|
97
|
+
"body": { "email": "ops@example.com", "password": "hunter2" },
|
|
98
|
+
"body_encoding": "json",
|
|
99
|
+
"body_size": 48,
|
|
100
|
+
"truncated": false
|
|
101
|
+
},
|
|
102
|
+
"response": {
|
|
103
|
+
"status": 200,
|
|
104
|
+
"headers": { "Content-Type": "application/json" },
|
|
105
|
+
"body": { "success": true, "token": "eyJ..." },
|
|
106
|
+
"body_encoding": "json",
|
|
107
|
+
"body_size": 42,
|
|
108
|
+
"truncated": false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`path` excludes the query string; `query` holds it parsed. `host` is the request's target host,
|
|
114
|
+
which is the only way to tell Graph from Datalab in an outbound recording. `response` is absent
|
|
115
|
+
entirely on a failed flow, and an `error` field appears instead.
|
|
116
|
+
|
|
117
|
+
Which external services the backend called, and how often:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
jq -r '.host' backend-outbound.jsonl | sort | uniq -c | sort -rn
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
On the frontend outbound leg `host` is nearly always `127.0.0.1`, so key off the origin instead —
|
|
124
|
+
the port is what separates the backend from everything else:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
jq -r '.url | split("/")[0:3] | join("/")' frontend-outbound.jsonl \
|
|
128
|
+
| sort | uniq -c | sort -rn
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
1 https://registry.npmjs.org
|
|
133
|
+
1 http://127.0.0.1:7100
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Which calls on a page came from the server rather than the browser — the paths in this list are
|
|
137
|
+
the ones you will also find in `backend-inbound.jsonl`, recorded a few milliseconds later at the
|
|
138
|
+
other end:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
jq -r 'select(.url | test(":7100")) | "\(.method) \(.path)"' \
|
|
142
|
+
frontend-outbound.jsonl | sort -u
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### body_encoding
|
|
146
|
+
|
|
147
|
+
Tells you how to interpret `body`, which matters because its type varies:
|
|
148
|
+
|
|
149
|
+
- `json` — parsed, so jq can reach inside it directly
|
|
150
|
+
- `text` — a string; either genuinely non-JSON, or JSON that was truncated before parsing
|
|
151
|
+
- `binary` — a summary object `{"binary": true, "content_type": "image/png", "size": 5004}`.
|
|
152
|
+
Images, PDFs and spreadsheets are never base64'd into the file; the S3 image proxy alone
|
|
153
|
+
would otherwise add megabytes per thumbnail.
|
|
154
|
+
- `null` — no body at all (`body_size` 0)
|
|
155
|
+
|
|
156
|
+
`body_size` is always the true size on the wire. When `truncated` is `true` the body was larger
|
|
157
|
+
than the 1 MB cap and `body` holds the first 1 MB as a string — so `body_size` and the actual
|
|
158
|
+
length of `body` disagree, by design.
|
|
159
|
+
|
|
160
|
+
## jq recipes
|
|
161
|
+
|
|
162
|
+
Failures, with the reason:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
jq -c 'select(.response.status >= 400 or has("error"))
|
|
166
|
+
| {id, method, path, status: .response.status, msg: .response.body.message}' \
|
|
167
|
+
backend-inbound.jsonl
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Slowest calls first. Put `duration_ms` first and let `sort -rn` key off it — piping whole JSON
|
|
171
|
+
objects into `sort` does not work, because the field separators inside the object throw the
|
|
172
|
+
column numbering off and you get something that looks sorted but is not:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
jq -r '[.duration_ms, .id, .method, .path] | @tsv' backend-inbound.jsonl \
|
|
176
|
+
| sort -rn | head -20
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Which endpoints were exercised, and how often:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
jq -r '.method + " " + .path' backend-inbound.jsonl | sort | uniq -c | sort -rn
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Everything one endpoint did:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
jq -c 'select(.path | test("/api/quotation")) | {id, method, status: .response.status}' \
|
|
189
|
+
backend-inbound.jsonl
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Request and response bodies for one call:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
jq 'select(.id == "0004") | {request: .request.body, response: .response.body}' \
|
|
196
|
+
backend-inbound.jsonl
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Payloads big enough to have been truncated:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
jq -c 'select(.request.truncated or .response.truncated) | {id, path, req: .request.body_size, resp: .response.body_size}' \
|
|
203
|
+
backend-inbound.jsonl
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
An API inventory to compare against documentation — unique endpoints with the status codes each
|
|
207
|
+
returned:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
jq -r '"\(.method) \(.path) -> \(.response.status)"' backend-inbound.jsonl \
|
|
211
|
+
| sort -u
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If `jq` is unavailable, the same work is straightforward in a short Python loop over
|
|
215
|
+
`json.loads` per line.
|
|
216
|
+
|
|
217
|
+
## Secrets
|
|
218
|
+
|
|
219
|
+
Recordings are raw by deliberate choice — nothing is masked. `request.headers.Authorization`
|
|
220
|
+
carries live bearer tokens, `/api/auth/login` request bodies carry the real password, and the
|
|
221
|
+
Outlook flow carries Microsoft access tokens. The `skills/*-workspace/` gitignore rule keeps the
|
|
222
|
+
whole workspace out of this repo's history, and nothing is written into the recorded app's repo
|
|
223
|
+
at all. Treat a
|
|
224
|
+
recording like a credential: don't paste one into an issue, a PR, or a chat with anyone.
|