@saptools/cf-inspector 0.4.11 โ†’ 0.5.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/README.md CHANGED
@@ -25,11 +25,13 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
25
25
  - ๐Ÿชœ **Stack capture** โ€” `--stack-depth N --stack-captures 'this, args'` walks call frames and evaluates expressions per frame
26
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
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`
29
- - ๐Ÿง  **Agent-friendly** โ€” JSON-by-default I/O, deterministic shape, bounded value previews for large debugger payloads
28
+ - ๐Ÿ“ก **Non-pausing logpoints** โ€” `cf-inspector log --at file:line --expr 'JSON.stringify({โ€ฆ})'` streams JSON Lines as the line executes without pausing the inspectee, with optional `--condition`, `--hit-count`, and `--max-events`
29
+ - ๐Ÿ›ก๏ธ **Read-only capture guard** โ€” snapshot, watch, and exception captures use V8's side-effect analysis by default; `--allow-mutation` is an explicit escape hatch
30
+ - ๐Ÿงต **Worker-aware sessions** โ€” `list-targets` discovers raw inspector targets and nested NodeWorker sessions; use `--target` or `--worker` to select an isolate
31
+ - ๐Ÿง  **Agent-friendly** โ€” JSON-by-default I/O, deterministic shapes, and explicit `truncated`/`originalLength`/`omittedCount` metadata for bounded values
30
32
  - ๐Ÿงญ **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`)
31
33
  - ๐Ÿ” **Composes with `cf-debugger`** โ€” pass `--app/--region/--org/--space` and the tunnel is opened automatically; pass `--port` to attach to anything CDP-speaking
32
- - ๐Ÿชถ **Tiny dependency footprint** โ€” `commander` + `ws` only, no heavy CDP framework
34
+ - ๐Ÿชถ **Tiny dependency footprint** โ€” `@saptools/cf-debugger` + `commander` + `ws`, with no heavy CDP framework
33
35
  - ๐Ÿงฉ **Typed API** โ€” every CLI command has a programmatic equivalent with full TypeScript definitions
34
36
 
35
37
  ---
@@ -40,11 +42,13 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
40
42
  npm install -g @saptools/cf-inspector
41
43
  # or
42
44
  pnpm add @saptools/cf-inspector
45
+
46
+ cf-inspector --version
43
47
  ```
44
48
 
45
49
  > [!NOTE]
46
50
  > Requires **Node.js โ‰ฅ 20**.
47
- > For Cloud Foundry targets, also install [`@saptools/cf-debugger`](https://www.npmjs.com/package/@saptools/cf-debugger) (added automatically as a peer-style runtime dep).
51
+ > Cloud Foundry support uses [`@saptools/cf-debugger`](https://www.npmjs.com/package/@saptools/cf-debugger), which is installed automatically as a normal runtime dependency.
48
52
 
49
53
  ---
50
54
 
@@ -64,6 +68,11 @@ cf-inspector snapshot \
64
68
 
65
69
  This command internally calls `@saptools/cf-debugger` to open the SSH tunnel, runs the snapshot through it, and tears the tunnel down on exit.
66
70
 
71
+ Cloud Foundry targeting is deliberately deterministic: `--app` requires
72
+ `--region`, `--org`, and `--space`. The CLI never inherits missing selectors
73
+ from ambient `cf target` state. Use `--api-endpoint` only when the selected
74
+ region needs an explicit endpoint override.
75
+
67
76
  ---
68
77
 
69
78
  ## ๐Ÿงฐ CLI
@@ -90,15 +99,20 @@ cf-inspector snapshot --port 9229 \
90
99
  | Flag | Description |
91
100
  | --- | --- |
92
101
  | `--port <number>` | Local port the inspector or tunnel listens on. **Required** unless `--app/--region/--org/--space` are all set |
102
+ | `--region/--org/--space/--app` | Explicit Cloud Foundry target. All four are required when `--port` is omitted; ambient `cf target` is never consulted |
103
+ | `--api-endpoint <url>` | Override the API endpoint resolved from `--region` |
104
+ | `--target <index>` | Raw `/json/list` target index (default: `0`) |
105
+ | `--worker <index>` | Nested NodeWorker index reported under the selected raw target by `list-targets` |
93
106
  | `--bp <file:line>` | **Required.** Source location to break at. Pass multiple times to race several locations โ€” the first one to hit wins |
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 |
107
+ | `--condition <expr>` | Native breakpoint condition. It is compile-checked before arming; mutation-shaped conditions require `--allow-mutation` because CDP provides no side-effect guard for native conditions |
95
108
  | `--hit-count <n>` | Skip the first N โˆ’ 1 hits and only pause on the Nth (combines with `--condition` via logical AND) |
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
- | `--setup-eval <expr>` | Repeatable, order-preserving global expression evaluated inside the inspected process before breakpoint setup. It can mutate runtime state, so use it only in controlled debug sessions |
109
+ | `--capture <expr,โ€ฆ>` | Top-level comma-separated expressions evaluated in the paused frame under V8's side-effect guard. Nested commas inside objects, arrays, calls, or strings are preserved. Objects are materialized to JSON strings when serializable |
110
+ | `--setup-eval <expr>` | Repeatable, order-preserving global expression evaluated before breakpoint setup. It is mutation-capable and only receives an advisory warning |
98
111
  | `--stack-depth <n>` | Walk this many call frames per hit (default: `1`, top frame only). When `> 1`, the result includes a `stack` array |
99
- | `--stack-captures <expr,โ€ฆ>` | Expressions to evaluate on each call frame in the captured stack |
112
+ | `--stack-captures <expr,โ€ฆ>` | Expressions evaluated on each captured call frame under the same side-effect guard as `--capture` |
113
+ | `--allow-mutation` | Disable V8's capture side-effect guard and explicitly allow mutation-shaped native conditions. Heuristic matches are annotated with `mutationRisk: true` |
100
114
  | `--timeout <seconds>` | How long to wait for the breakpoint to hit (default: `30`) |
101
- | `--max-value-length <chars>` | Maximum characters per captured value before truncation (default: `4096`) |
115
+ | `--max-value-length <chars>` | Maximum characters per captured value before truncation (one-shot default: `131072`). Explicit values are honored exactly |
102
116
  | `--remote-root <value>` | Optional path-mapping anchor: literal path or `regex:<pattern>` / `/pattern/flags` |
103
117
  | `--include-scopes` | Include expanded paused-frame scopes under `topFrame.scopes`. Omitted by default to keep targeted captures concise |
104
118
  | `--no-json` | Print a human-readable summary instead of JSON |
@@ -118,6 +132,56 @@ is only present with `--include-scopes` because scope objects can be large and
118
132
  drown out targeted captures. Values are raw debugger values, so be careful when
119
133
  sharing logs.
120
134
 
135
+ Capture expressions are read-only by default. `cf-inspector` sends
136
+ `throwOnSideEffect: true` to V8 for both `--capture` and `--stack-captures`.
137
+ Assignments, mutating methods such as `push`, and calls V8 cannot prove pure
138
+ are returned as a blocked capture with `blocked: true`, `mutationRisk: true`,
139
+ and a `MUTATION_NOT_ALLOWED` error. Pass `--allow-mutation` only when changing
140
+ the live inspectee is intentional. The opt-in disables the V8 guard and adds
141
+ `mutationRisk: true` when the advisory syntax scan recognizes a likely
142
+ mutation; arbitrary function calls can still mutate without being recognized.
143
+
144
+ `--condition` is different: V8 executes it internally as a native breakpoint
145
+ condition, where CDP has no `throwOnSideEffect` option. Mutation-shaped native
146
+ conditions are rejected unless `--allow-mutation` is present. `--setup-eval`
147
+ and the standalone `eval` command remain mutation-capable by design and emit
148
+ advisory warnings for recognizable mutation syntax.
149
+
150
+ A blocked capture remains a normal additive `CapturedExpression`, so one unsafe
151
+ expression does not corrupt the rest of the snapshot:
152
+
153
+ ```json
154
+ {"expression":"items.push(1)","error":"MUTATION_NOT_ALLOWED: V8 blocked the capture expression ...","mutationRisk":true,"blocked":true}
155
+ ```
156
+
157
+ With `--allow-mutation`, recognized mutation syntax runs and the result carries
158
+ `"mutationRisk": true`; ordinary reads do not gain the field.
159
+
160
+ #### Truncation contract
161
+
162
+ JSON truncation is always out-of-band. A text value longer than the effective
163
+ limit is cut to exactly that many JavaScript characters and gains
164
+ `"truncated": true` plus its full `"originalLength"`; no ellipsis is appended
165
+ to the JSON value. These fields are absent when no cut occurs. Human output may
166
+ add a visual ellipsis.
167
+
168
+ Expanded objects and scopes also report bounded structural capture. The
169
+ `VariableSnapshot`, `ScopeSnapshot`, or `FrameSnapshot` whose properties,
170
+ variables, or scopes were cut gains `truncated: true` and the exact direct
171
+ `omittedCount`. A serialized object capture propagates the aggregate known
172
+ omission count to its `CapturedExpression`. `ExceptionSnapshot` additionally
173
+ uses `valueOriginalLength` and `descriptionOriginalLength` so consumers can
174
+ identify which field was cut; its compatibility `originalLength` is the larger
175
+ reported field length.
176
+
177
+ One-shot `snapshot` and `exception` commands default to `131072` characters.
178
+ Repeated `watch` and `log` events default to `4096`. All four accept
179
+ `--max-value-length`, and an explicit limit is applied exactly.
180
+
181
+ ```json
182
+ {"expression":"largeText","value":"exactly-N-characters","type":"string","truncated":true,"originalLength":250000}
183
+ ```
184
+
121
185
  `pausedDurationMs` measures the client-observed time from receiving the matching
122
186
  pause event until `Debugger.resume` completes. With `--keep-paused`, it is `null`
123
187
  because resume is intentionally skipped.
@@ -137,7 +201,9 @@ readiness keeps the 180-second default.
137
201
 
138
202
  ### ๐Ÿ“ก `cf-inspector log`
139
203
 
140
- Set a non-pausing logpoint and stream the evaluated expression each time the line executes. Safe for production traffic โ€” the inspectee **never pauses**.
204
+ Set a non-pausing logpoint and stream the evaluated expression each time the
205
+ line executes. The inspectee does not pause, but the expression and condition
206
+ still execute against live state and can mutate it.
141
207
 
142
208
  ```bash
143
209
  # Stream user IDs hitting handler.ts:42 for 30 seconds
@@ -166,15 +232,22 @@ When the user expression throws, the event is emitted with `error` instead of `v
166
232
  | Flag | Description |
167
233
  | --- | --- |
168
234
  | `--port <number>` | Local port the inspector or tunnel listens on. **Required** unless `--app/--region/--org/--space` are all set |
235
+ | `--target <index>` / `--worker <index>` | Select a raw inspector target or nested NodeWorker session from `list-targets` |
169
236
  | `--at <file:line>` | **Required.** Source location to log at |
170
- | `--expr <expression>` | **Required.** JS expression to evaluate at each hit (wrapped in try/catch on the inspectee side) |
237
+ | `--expr <expression>` | **Required.** JavaScript expression evaluated at each hit, wrapped in try/catch on the inspectee side. It is mutation-capable; recognizable risks produce a warning |
171
238
  | `--duration <seconds>` | Stop streaming after N seconds (default: run until SIGINT) |
172
239
  | `--max-events <n>` | Stop streaming after emitting N log events. The trailer reports `stopped: "max-events"` |
173
240
  | `--hit-count <n>` | Start emitting once the line has been hit N or more times |
174
- | `--condition <expr>` | Only log when this JS expression evaluates truthy on the inspectee. Composes with `--hit-count` via logical AND |
241
+ | `--condition <expr>` | Mutation-capable native condition evaluated on the inspectee. Recognizable risks produce a warning. Composes with `--hit-count` via logical AND |
242
+ | `--max-value-length <chars>` | Maximum characters per log value (streaming default: `4096`). Truncated events include `truncated` and `originalLength` |
175
243
  | `--remote-root <value>` | Optional path-mapping anchor (same DSL as `snapshot`) |
176
244
  | `--no-json` | Print human-readable lines instead of JSON Lines |
177
245
 
246
+ Native logpoint expressions and conditions have no V8 side-effect gate. The
247
+ CLI warns when its best-effort syntax scan recognizes assignments or common
248
+ mutating calls, but that scan cannot prove an arbitrary function is pure. Treat
249
+ logpoint expressions as executable live code.
250
+
178
251
  ### ๐Ÿ” `cf-inspector watch`
179
252
 
180
253
  Stream a snapshot per breakpoint hit. The inspectee is paused briefly while
@@ -201,19 +274,21 @@ Each event is a `WatchEvent`:
201
274
 
202
275
  | Flag | Description |
203
276
  | --- | --- |
204
- | `--port <number>` | Local port the inspector or tunnel listens on |
277
+ | `--port <number>` | Local port the inspector or tunnel listens on. Otherwise pass all explicit Cloud Foundry selectors |
278
+ | `--target <index>` / `--worker <index>` | Select a raw inspector target or nested NodeWorker session from `list-targets` |
205
279
  | `--bp <file:line>` | **Required.** Source location to capture on (repeatable) |
206
- | `--capture <expr,โ€ฆ>` | Top-level comma-separated expressions to evaluate per hit |
207
- | `--setup-eval <expr>` | Repeatable, order-preserving global expression evaluated inside the inspected process before breakpoint setup. It can mutate runtime state, so use it only in controlled debug sessions |
208
- | `--condition <expr>` | Only emit hits where this expression evaluates truthy |
280
+ | `--capture <expr,โ€ฆ>` | Top-level comma-separated expressions evaluated per hit under V8's side-effect guard |
281
+ | `--setup-eval <expr>` | Repeatable mutation-capable global expression evaluated before breakpoint setup; recognizable risks produce a warning |
282
+ | `--condition <expr>` | Native condition; mutation-shaped conditions require `--allow-mutation` |
209
283
  | `--hit-count <n>` | Start emitting once the line has been hit N or more times |
210
284
  | `--remote-root <value>` | Path-mapping anchor (same DSL as `snapshot`) |
211
285
  | `--duration <seconds>` | Stop streaming after N seconds (default: until SIGINT) |
212
286
  | `--max-events <n>` | Stop streaming after emitting N events |
213
287
  | `--timeout <seconds>` | How long to wait for the next hit before giving up (default: `30`) |
214
- | `--max-value-length <chars>` | Maximum characters per captured value before truncation |
288
+ | `--max-value-length <chars>` | Maximum characters per captured value (streaming default: `4096`) |
215
289
  | `--stack-depth <n>` | Walk this many call frames per hit (default: `1`) |
216
- | `--stack-captures <expr,โ€ฆ>` | Expressions to evaluate on each call frame |
290
+ | `--stack-captures <expr,โ€ฆ>` | Expressions evaluated on each call frame under the capture side-effect guard |
291
+ | `--allow-mutation` | Disable the capture side-effect guard and explicitly allow mutation-shaped native conditions |
217
292
  | `--include-scopes` | Include expanded paused-frame scopes per hit |
218
293
  | `--no-json` | Print human-readable lines instead of JSON Lines |
219
294
 
@@ -248,20 +323,29 @@ Result is a `SnapshotResult` with an extra `exception` field:
248
323
 
249
324
  | Flag | Description |
250
325
  | --- | --- |
326
+ | `--port` or explicit `--region/--org/--space/--app` | Select the local inspector or deterministic Cloud Foundry target |
327
+ | `--target <index>` / `--worker <index>` | Select a raw inspector target or nested NodeWorker session from `list-targets` |
251
328
  | `--type <state>` | Pause on which exceptions: `uncaught` (default), `caught`, or `all` |
252
- | `--capture <expr,โ€ฆ>` | Top-level expressions to evaluate in the paused frame |
329
+ | `--capture <expr,โ€ฆ>` | Top-level expressions evaluated in the paused frame under V8's side-effect guard |
253
330
  | `--stack-depth <n>` | Walk this many call frames (default: `1`) |
254
- | `--stack-captures <expr,โ€ฆ>` | Expressions to evaluate on each frame |
331
+ | `--stack-captures <expr,โ€ฆ>` | Expressions evaluated on each frame under V8's side-effect guard |
332
+ | `--allow-mutation` | Disable the capture side-effect guard; heuristic matches gain `mutationRisk: true` |
255
333
  | `--include-scopes` | Include paused-frame scopes |
256
334
  | `--remote-root <value>` | Path-mapping anchor (only used if you also wire snapshot helpers) |
257
335
  | `--timeout <seconds>` | How long to wait for an exception (default: `30`) |
258
- | `--max-value-length <chars>` | Maximum characters per captured value before truncation |
336
+ | `--max-value-length <chars>` | Maximum characters per captured value (one-shot default: `131072`) |
259
337
  | `--keep-paused` | Skip `Debugger.resume` after capture |
260
338
  | `--no-json` | Print a human-readable summary instead of JSON |
261
339
 
262
340
  ### ๐Ÿงฎ `cf-inspector eval`
263
341
 
264
- 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.
342
+ Evaluate one expression with `Runtime.evaluate` in the selected isolate's
343
+ global scope and print the result. `eval` is intentionally mutation-capable and
344
+ has no side-effect gate; recognizable mutation syntax emits an advisory warning
345
+ to `stderr`. For read-only paused-frame values, use `snapshot --capture` or call
346
+ the programmatic `evaluateOnFrame(..., { throwOnSideEffect: true })` API. Plain
347
+ `evaluateOnFrame(...)` remains unrestricted by default for backward
348
+ compatibility.
265
349
 
266
350
  ```bash
267
351
  cf-inspector eval --port 9229 --expr 'process.uptime()'
@@ -277,13 +361,56 @@ cf-inspector list-scripts --port 9229 --filter 'dist/.+\.js'
277
361
 
278
362
  ### ๐ŸŽฏ `cf-inspector list-targets`
279
363
 
280
- Print `/json/list` inspector targets with stable indexes. Use the index with `--target <index>` when a long-running worker thread appears as a separate target.
364
+ Print raw `/json/list` inspector targets with stable `index` values, likely
365
+ worker labels, and the total target/worker counts on `stderr`. For each raw
366
+ target, the command also probes Node's `NodeWorker` CDP domain and lists live
367
+ nested workers with their own indexes.
281
368
 
282
369
  ```bash
283
370
  cf-inspector list-targets --port 9229
371
+ cf-inspector snapshot --port 9229 --worker 0 --bp dist/worker.js:42
372
+ # If a runtime publishes a worker as another raw /json/list target instead:
284
373
  cf-inspector snapshot --port 9229 --target 1 --bp dist/worker.js:42
285
374
  ```
286
375
 
376
+ JSON output nests workers beneath their raw target:
377
+
378
+ ```json
379
+ [
380
+ {
381
+ "index": 0,
382
+ "description": "node.js instance",
383
+ "id": "target-id",
384
+ "type": "node",
385
+ "title": "app.mjs",
386
+ "url": "file:///app/app.mjs",
387
+ "webSocketDebuggerUrl": "ws://127.0.0.1:9229/target-id",
388
+ "likelyWorker": false,
389
+ "workerDiscoverySupported": true,
390
+ "workers": [
391
+ {"index": 0, "workerId": "1", "type": "worker", "title": "jobs", "url": "file:///app/worker.mjs"}
392
+ ]
393
+ }
394
+ ]
395
+ ```
396
+
397
+ `--target` selects a complete raw inspector endpoint. `--worker` selects a
398
+ nested NodeWorker session under the chosen raw target (raw target `0` unless
399
+ `--target` is also passed). Modern Node.js 20โ€“25 verification found workers on
400
+ the `NodeWorker` path, including workers already alive before post-hoc
401
+ `SIGUSR1` inspector activation; the raw-target selector remains supported for
402
+ runtimes that publish that shape.
403
+
404
+ When multiple raw targets or nested workers exist and no selector is passed,
405
+ commands attach to raw target `0` and print a selection notice. A bound
406
+ breakpoint that sees no hit prints a worker-isolate hint. If only one raw target
407
+ and no workers are visible, `list-targets` explains that the worker may have
408
+ exited, the runtime may not expose NodeWorker discovery, or a separate worker
409
+ port may be unreachable through the single Cloud Foundry tunnel. Rerun the
410
+ command while the worker is alive before selecting an index.
411
+
412
+ If `list-targets`, `attach`, or another command reports `ECONNREFUSED`, the local inspector or tunnel on that port is usually stale/closed. Restart the local Node inspector or tunnel and retry; for Cloud Foundry targets, pass the complete `--region/--org/--space/--app` selector so `cf-inspector` can open a fresh tunnel.
413
+
287
414
  ### ๐Ÿ”— `cf-inspector attach`
288
415
 
289
416
  Connect, fetch the runtime version, print it, disconnect. Useful as a smoke-test that the tunnel is healthy.
@@ -292,19 +419,23 @@ Connect, fetch the runtime version, print it, disconnect. Useful as a smoke-test
292
419
  cf-inspector attach --port 9229
293
420
  ```
294
421
 
422
+ `attach` checks the port-level `/json/version` endpoint, so raw-target and
423
+ worker selectors do not apply to this smoke test.
424
+
295
425
  ---
296
426
 
297
427
  ## ๐Ÿ”ญ How it works
298
428
 
299
429
  ```
300
430
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” 1. GET http://127.0.0.1:<port>/json/list
301
- โ”‚ cf-inspector โ”‚ 2. Open ws:// debugger URL
302
- โ”‚ snapshot --bp X:Y โ”‚ โ”€โ–บ3. Debugger.enable + Runtime.enable
303
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 4. Debugger.setBreakpointByUrl({ urlRegex, lineNumber: Y - 1 })
304
- โ”‚ 5. Wait for `Debugger.paused`
305
- โ–ผ 6. Debugger.evaluateOnCallFrame(...) for each --capture expression
306
- JSON snapshot 7. Runtime.getProperties(scopeChain[i].object.objectId) when --include-scopes is set
307
- 8. Debugger.resume (unless --keep-paused)
431
+ โ”‚ cf-inspector โ”‚ 2. Open the selected raw WebSocket target
432
+ โ”‚ snapshot --bp X:Y โ”‚ โ”€โ–บ3. Optionally attach to a selected NodeWorker sub-session
433
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 4. Debugger.enable + Runtime.enable
434
+ โ”‚ 5. Debugger.setBreakpointByUrl({ urlRegex, lineNumber: Y - 1 })
435
+ โ–ผ 6. Wait for `Debugger.paused`
436
+ JSON snapshot 7. Debugger.evaluateOnCallFrame({ throwOnSideEffect: true, ... })
437
+ 8. Runtime.getProperties(...) when object/scopes are expanded
438
+ 9. Debugger.resume (unless --keep-paused)
308
439
  ```
309
440
 
310
441
  Path mapping uses CDP's first-class `urlRegex`:
@@ -322,7 +453,14 @@ Path mapping uses CDP's first-class `urlRegex`:
322
453
 
323
454
  ## โš™๏ธ Composing with `cf-debugger`
324
455
 
325
- If `--port` is omitted but `--region/--org/--space/--app` are given, the CLI internally calls `startDebugger(...)` from `@saptools/cf-debugger`, attaches over the SSH tunnel, and disposes the tunnel on exit. You get the same one-shot UX whether the target is local or in CF.
456
+ If `--port` is omitted, all of `--region/--org/--space/--app` are required. The
457
+ CLI does not read ambient `cf target` state. It calls `startDebugger(...)` from
458
+ `@saptools/cf-debugger`, attaches over the SSH tunnel, and disposes the tunnel
459
+ on exit. You get the same one-shot UX whether the target is local or in CF.
460
+
461
+ The tunnel forwards one inspector port. Nested NodeWorker sessions carried by
462
+ that inspector connection are selectable with `--worker`; a worker exposing
463
+ only an unrelated separate port is outside that tunnel's reach.
326
464
 
327
465
  ```bash
328
466
  cf-inspector snapshot \