@saptools/cf-inspector 0.3.19 → 0.4.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.
package/README.md CHANGED
@@ -20,8 +20,12 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
20
20
 
21
21
  - 🎯 **One-shot snapshot** — `cf-inspector snapshot --bp src/handler.ts:42` sets the breakpoint, waits for it to hit, captures requested expressions, auto-resumes, prints JSON, exits
22
22
  - ✅ **Conditional breakpoints** — `--condition 'req.userId === "abc"'` only pauses when the predicate is truthy
23
+ - 🔢 **Hit-count breakpoints** — `--hit-count 5` skips the first N − 1 hits and pauses on the Nth, on every command (snapshot, log, watch)
23
24
  - 🎭 **Multi-breakpoint** — repeat `--bp` to race several locations; first hit wins
24
- - 📡 **Non-pausing logpoints** — `cf-inspector log --at file:line --expr 'JSON.stringify({…})'` streams JSON Lines as the line executes, **without ever pausing the inspectee** (safe for production traffic)
25
+ - 🪜 **Stack capture** — `--stack-depth N --stack-captures 'this, args'` walks call frames and evaluates expressions per frame
26
+ - 🔁 **Watch streaming** — `cf-inspector watch --bp file:line --capture user.id --duration 30` re-captures on every hit and emits JSON Lines (the streaming counterpart of `snapshot`)
27
+ - 💥 **Exception breakpoints** — `cf-inspector exception --type uncaught --capture err.message` pauses on the next thrown error and materializes the exception value
28
+ - 📡 **Non-pausing logpoints** — `cf-inspector log --at file:line --expr 'JSON.stringify({…})'` streams JSON Lines as the line executes, **without ever pausing the inspectee** (safe for production traffic), with optional `--condition`, `--hit-count`, `--max-events`
25
29
  - 🧠 **Agent-friendly** — JSON-by-default I/O, deterministic shape, bounded value previews for large debugger payloads
26
30
  - 🧭 **Path mapping** — local `src/handler.ts:42` is matched against the remote URL via a `urlRegex`, with optional `--remote-root` literal or regex (same DSL as `cds-debug`)
27
31
  - 🔁 **Composes with `cf-debugger`** — pass `--app/--region/--org/--space` and the tunnel is opened automatically; pass `--port` to attach to anything CDP-speaking
@@ -88,7 +92,10 @@ cf-inspector snapshot --port 9229 \
88
92
  | `--port <number>` | Local port the inspector or tunnel listens on. **Required** unless `--app/--region/--org/--space` are all set |
89
93
  | `--bp <file:line>` | **Required.** Source location to break at. Pass multiple times to race several locations — the first one to hit wins |
90
94
  | `--condition <expr>` | Only pause when this JS expression evaluates truthy in the paused frame. Errors in the condition are silently treated as `false` by V8 |
95
+ | `--hit-count <n>` | Skip the first N − 1 hits and only pause on the Nth (combines with `--condition` via logical AND) |
91
96
  | `--capture <expr,…>` | Top-level comma-separated expressions to evaluate in the paused frame; nested commas inside objects, arrays, calls, or strings are preserved. Object results are materialized to JSON strings when serializable, with fallback to CDP descriptions for non-serializable values |
97
+ | `--stack-depth <n>` | Walk this many call frames per hit (default: `1`, top frame only). When `> 1`, the result includes a `stack` array |
98
+ | `--stack-captures <expr,…>` | Expressions to evaluate on each call frame in the captured stack |
92
99
  | `--timeout <seconds>` | How long to wait for the breakpoint to hit (default: `30`) |
93
100
  | `--max-value-length <chars>` | Maximum characters per captured value before truncation (default: `4096`) |
94
101
  | `--remote-root <value>` | Optional path-mapping anchor: literal path or `regex:<pattern>` / `/pattern/flags` |
@@ -148,9 +155,95 @@ When the user expression throws, the event is emitted with `error` instead of `v
148
155
  | `--at <file:line>` | **Required.** Source location to log at |
149
156
  | `--expr <expression>` | **Required.** JS expression to evaluate at each hit (wrapped in try/catch on the inspectee side) |
150
157
  | `--duration <seconds>` | Stop streaming after N seconds (default: run until SIGINT) |
158
+ | `--max-events <n>` | Stop streaming after emitting N log events. The trailer reports `stopped: "max-events"` |
159
+ | `--hit-count <n>` | Start emitting once the line has been hit N or more times |
160
+ | `--condition <expr>` | Only log when this JS expression evaluates truthy on the inspectee. Composes with `--hit-count` via logical AND |
151
161
  | `--remote-root <value>` | Optional path-mapping anchor (same DSL as `snapshot`) |
152
162
  | `--no-json` | Print human-readable lines instead of JSON Lines |
153
163
 
164
+ ### 🔁 `cf-inspector watch`
165
+
166
+ Stream a snapshot per breakpoint hit. The inspectee is paused briefly while
167
+ captures are evaluated, then resumed automatically; output is JSON Lines on
168
+ stdout with a trailer on stderr (same shape as `log`).
169
+
170
+ ```bash
171
+ cf-inspector watch --port 9229 \
172
+ --bp src/handler.ts:42 \
173
+ --capture 'user.id, payload' \
174
+ --condition 'user.id !== "system"' \
175
+ --duration 30 \
176
+ --max-events 50
177
+ ```
178
+
179
+ Each event is a `WatchEvent`:
180
+
181
+ ```jsonc
182
+ {"ts":"2026-04-29T...","at":"file:///app/src/handler.ts:42","hit":1,"reason":"other","hitBreakpoints":["..."],"captures":[{"expression":"user.id","value":"\"alice\""}]}
183
+ {"ts":"2026-04-29T...","at":"file:///app/src/handler.ts:42","hit":2,"reason":"other","hitBreakpoints":["..."],"captures":[{"expression":"user.id","value":"\"bob\""}]}
184
+ // stderr trailer:
185
+ {"stopped":"max-events","emitted":50}
186
+ ```
187
+
188
+ | Flag | Description |
189
+ | --- | --- |
190
+ | `--port <number>` | Local port the inspector or tunnel listens on |
191
+ | `--bp <file:line>` | **Required.** Source location to capture on (repeatable) |
192
+ | `--capture <expr,…>` | Top-level comma-separated expressions to evaluate per hit |
193
+ | `--condition <expr>` | Only emit hits where this expression evaluates truthy |
194
+ | `--hit-count <n>` | Start emitting once the line has been hit N or more times |
195
+ | `--remote-root <value>` | Path-mapping anchor (same DSL as `snapshot`) |
196
+ | `--duration <seconds>` | Stop streaming after N seconds (default: until SIGINT) |
197
+ | `--max-events <n>` | Stop streaming after emitting N events |
198
+ | `--timeout <seconds>` | How long to wait for the next hit before giving up (default: `30`) |
199
+ | `--max-value-length <chars>` | Maximum characters per captured value before truncation |
200
+ | `--stack-depth <n>` | Walk this many call frames per hit (default: `1`) |
201
+ | `--stack-captures <expr,…>` | Expressions to evaluate on each call frame |
202
+ | `--include-scopes` | Include expanded paused-frame scopes per hit |
203
+ | `--no-json` | Print human-readable lines instead of JSON Lines |
204
+
205
+ ### 💥 `cf-inspector exception`
206
+
207
+ Pause on a thrown exception, capture the exception value plus the paused
208
+ frame, then resume.
209
+
210
+ ```bash
211
+ cf-inspector exception --port 9229 \
212
+ --type uncaught \
213
+ --capture 'this' \
214
+ --stack-depth 4 \
215
+ --stack-captures 'arguments[0]' \
216
+ --timeout 30
217
+ ```
218
+
219
+ Result is a `SnapshotResult` with an extra `exception` field:
220
+
221
+ ```jsonc
222
+ {
223
+ "reason": "exception",
224
+ "hitBreakpoints": [],
225
+ "capturedAt": "2026-04-29T...",
226
+ "pausedDurationMs": 0.5,
227
+ "topFrame": {"functionName": "validate", "url": "...", "line": 42, "column": 5},
228
+ "exception": {"value": "{\"message\":\"missing field\",\"name\":\"Error\"}", "type": "object", "description": "missing field"},
229
+ "captures": [],
230
+ "stack": [...]
231
+ }
232
+ ```
233
+
234
+ | Flag | Description |
235
+ | --- | --- |
236
+ | `--type <state>` | Pause on which exceptions: `uncaught` (default), `caught`, or `all` |
237
+ | `--capture <expr,…>` | Top-level expressions to evaluate in the paused frame |
238
+ | `--stack-depth <n>` | Walk this many call frames (default: `1`) |
239
+ | `--stack-captures <expr,…>` | Expressions to evaluate on each frame |
240
+ | `--include-scopes` | Include paused-frame scopes |
241
+ | `--remote-root <value>` | Path-mapping anchor (only used if you also wire snapshot helpers) |
242
+ | `--timeout <seconds>` | How long to wait for an exception (default: `30`) |
243
+ | `--max-value-length <chars>` | Maximum characters per captured value before truncation |
244
+ | `--keep-paused` | Skip `Debugger.resume` after capture |
245
+ | `--no-json` | Print a human-readable summary instead of JSON |
246
+
154
247
  ### 🧮 `cf-inspector eval`
155
248
 
156
249
  Evaluate one expression with `Runtime.evaluate` in the global scope and print the result. For paused-frame values, use `snapshot --capture` or the programmatic `evaluateOnFrame(...)` API.
@@ -216,16 +309,20 @@ console.log({ bp, snapshot, customValue });
216
309
  | Export | Description |
217
310
  | --- | --- |
218
311
  | `connectInspector(options)` | Open a CDP WebSocket session against a port |
219
- | `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root |
312
+ | `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root and `hitCount` |
220
313
  | `removeBreakpoint(session, id)` | Remove a breakpoint by id |
221
- | `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires |
222
- | `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes or `maxValueLength` to override the default captured value limit |
314
+ | `setPauseOnExceptions(session, state)` | Configure exception pause state: `none / uncaught / caught / all` |
315
+ | `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires; supports `pauseReasons` allow-list |
316
+ | `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes, `stackDepth` + `stackCaptures` for multi-frame walks, or `maxValueLength` to override the default captured value limit |
317
+ | `captureException(session, pause, maxValueLength)` | Materialize the exception attached to a `Debugger.paused` event |
318
+ | `walkStack(session, frames, options)` | Walk a call stack and evaluate per-frame expressions |
223
319
  | `evaluateOnFrame(session, frameId, expression)` | Evaluate in a paused frame |
224
320
  | `evaluateGlobal(session, expression)` | Evaluate against the global Runtime |
225
321
  | `listScripts(session)` | Return the scripts the V8 instance knows about |
226
322
  | `resume(session)` | Resume execution |
227
- | `streamLogpoint(session, options)` | Stream a non-pausing logpoint until duration / signal / transport-close |
228
- | `buildLogpointCondition(sentinel, expression)` | Build the CDP `condition` string for a logpoint (low-level helper) |
323
+ | `streamLogpoint(session, options)` | Stream a non-pausing logpoint until duration / signal / max-events / transport-close |
324
+ | `buildLogpointCondition(sentinel, expression, options?)` | Build the CDP `condition` string for a logpoint with optional predicate / hit-count gates |
325
+ | `buildHitCountedCondition(hitCount, key, userCondition?)` | Build the hit-count gate used by `setBreakpoint({ hitCount })` |
229
326
  | `parseRemoteRoot(value)` | Parse a literal/regex remote-root setting |
230
327
  | `buildBreakpointUrlRegex(input)` | Build a CDP `urlRegex` for a file path |
231
328
  | `CfInspectorError` | Rich error class with typed `code` |
@@ -241,6 +338,8 @@ console.log({ bp, snapshot, customValue });
241
338
  | `INVALID_BREAKPOINT` | `--bp` / `--at` is not in `file:line` form, or line is not a positive integer |
242
339
  | `INVALID_REMOTE_ROOT` | `--remote-root` regex did not compile |
243
340
  | `INVALID_EXPRESSION` | `--condition` or `--expr` failed to parse on V8 (`Runtime.compileScript` reported a SyntaxError) — fast-fail before the breakpoint is set |
341
+ | `INVALID_HIT_COUNT` | `--hit-count` is not a positive integer |
342
+ | `INVALID_PAUSE_TYPE` | `cf-inspector exception --type` is not one of `uncaught / caught / all` |
244
343
  | `BREAKPOINT_DID_NOT_BIND` | Reserved: a breakpoint resolved to no scripts. Currently surfaced as a stderr warning only — see `BreakpointHandle.resolvedLocations` for programmatic detection |
245
344
  | `INSPECTOR_DISCOVERY_FAILED` | `/json/list` did not return a usable WebSocket URL |
246
345
  | `INSPECTOR_CONNECTION_FAILED` | WebSocket handshake failed, or the connection closed mid-request |
@@ -295,23 +394,6 @@ cf-inspector snapshot \
295
394
 
296
395
  ---
297
396
 
298
- ## 🛠️ Development
299
-
300
- From the monorepo root:
301
-
302
- ```bash
303
- pnpm install
304
- pnpm --filter @saptools/cf-inspector build
305
- pnpm --filter @saptools/cf-inspector typecheck
306
- pnpm --filter @saptools/cf-inspector lint
307
- pnpm --filter @saptools/cf-inspector test:unit
308
- pnpm --filter @saptools/cf-inspector test:e2e
309
- ```
310
-
311
- The e2e suite is fully self-contained: it spawns a small Node fixture under `--inspect=0`, drives the CLI against it, and asserts the JSON output. No CF / live network required.
312
-
313
- ---
314
-
315
397
  ## 🌐 Related
316
398
 
317
399
  - 🐛 [`@saptools/cf-debugger`](https://www.npmjs.com/package/@saptools/cf-debugger) — opens the SSH inspector tunnel