Skip to content

Developer remote-control API

BetterPy exposes a small HTTP remote-control API for automated debugging in dev builds.

The endpoint is intentionally narrow:

  • Available only when the active release channel is dev.
  • Accepts loopback requests only.
  • Requires a configured token for every endpoint except GET /ping and the bootstrap-only GET /status. status discloses no token value and is limited to connection metadata needed to identify the intended local IDE instance. When both the settings-panel session token and betterpy.remoteControl.token are present, either token is accepted.
  • Uses the X-BetterPy-Remote-Token header, or a token query parameter.

Base URL:

http://127.0.0.1:<IDE built-in server port>/api/betterpy-remote

The IDE built-in server commonly starts at 63342, but that port is not an instance identity. BetterPy writes a non-secret connection profile named betterpy-remote-control.json in the selected IDE configuration directory after a project opens. It contains the effective base URL, endpoint path, instance ID, release channel, and whether a token is configured.

Prefer the profile to guessed ports when targeting a deployed IDE:

python3 scripts/ide_remote_control.py \
  --connection-file "$HOME/Library/Application Support/JetBrains/PyCharm2026.2/betterpy-remote-control.json" \
  doctor

The Copy Connection Environment BetterPy action copies an equivalent shell-safe setup, including the current session token. The profile never contains a token.

Quick start

From the repository root, configure the token and let doctor explain what is ready or missing:

export BETTERPY_REMOTE_TOKEN=dev-secret
export BETTERPY_REMOTE_BASE=http://127.0.0.1:63342/api/betterpy-remote

python3 scripts/ide_remote_control.py doctor --try-ports 63342,63343
python3 scripts/ide_remote_control.py wait-ready
python3 scripts/ide_remote_control.py action CodeCompletion --wait
python3 scripts/ide_remote_control.py scenario-run --stream --body '{"steps":[]}'

doctor first uses tokenless /status or /ping to distinguish a connection failure from a reachable endpoint rejected by its channel, feature, loopback, or token gate. It reports endpoint reachability, token state, channel, feature flag state, project availability, modal blocking, and smart-mode/indexing readiness.

Installed PyCharm EAP runbook

Use this runbook when validating a dev BetterPy build inside an installed PyCharm EAP such as 2026.2 EAP.

1. Deploy the dev plugin into the installed IDE

Run the repository deploy script and select the installed PyCharm EAP from the interactive list:

JAVA_HOME=/Users/jo/Library/Java/JavaVirtualMachines/jbr-21.0.10/Contents/Home \
  ./scripts/deploy.sh

The script builds the plugin and installs it into the settings directory used by the selected app. Close the target IDE before deploying so the plugin is loaded cleanly on the next launch.

2. Configure token and port

Generate a session token from the BetterPy settings panel, or add the remote-control token and, when needed, the built-in server port to the target IDE idea.properties. For PyCharm 2026.2, the file is typically:

~/Library/Application Support/JetBrains/PyCharm2026.2/idea.properties

Example properties:

betterpy.remoteControl.token=dev-secret
ide.built.in.server.port=63343

Restart the IDE after changing these properties. A generated session token is shown and copied first in settings, but the JVM-property token remains valid until the property is removed and the IDE is restarted. A configured token is required for every endpoint except GET /ping.

3. Launch and point the client at the IDE

Open the installed app and export matching client values:

open -a "PyCharm 2026.2 EAP"

export BETTERPY_REMOTE_TOKEN=dev-secret
export BETTERPY_REMOTE_BASE=http://127.0.0.1:63343/api/betterpy-remote
python3 scripts/ide_remote_control.py doctor --try-ports 63342,63343

The expected doctor result has ready=true, channel=dev, and featureEnabled=true. If ping succeeds but protected endpoints fail, check that BETTERPY_REMOTE_TOKEN matches either the session token shown in settings or the betterpy.remoteControl.token property. Restart the IDE after property changes.

4. Open a project and handle trust/modal state

Open the project with the IDE UI or launcher. Then inspect modal state through the remote-control API rather than using OS-level automation:

python3 scripts/ide_remote_control.py modal
python3 scripts/ide_remote_control.py wait-modal --closed --timeout-ms 30000

If a trust dialog or another modal is open, inspect its component tree and click the intended button by stable text/name/accessibility metadata:

python3 scripts/ide_remote_control.py modal
python3 scripts/ide_remote_control.py modal-action click --text "Trust Project"
python3 scripts/ide_remote_control.py wait-modal --closed --timeout-ms 30000

Use exact modal output from your IDE when choosing --text, --name, or --accessible-name; do not click unrelated dialogs blindly.

5. Wait for a usable IDE

After opening and trusting the project, wait for the usual readiness gates with one command:

python3 scripts/ide_remote_control.py wait-ready --timeout-ms 60000 --poll-ms 250

wait-ready retries transient bootstrap connection failures until its deadline. For automation that must avoid controlling a different running IDE, read instanceId from /status and require it explicitly:

python3 scripts/ide_remote_control.py wait-ready \
  --expected-instance-id "<instance-id>" \
  --timeout-ms 60000 --poll-ms 250

If layout stability is not relevant for a headless check, skip that gate:

python3 scripts/ide_remote_control.py wait-ready --skip-layout-stable

6. Collect diagnostics and feature logs

Open the file under test, wait for smart mode, collect diagnostics, and trigger an action with feature-scoped logging in one action command:

python3 scripts/ide_remote_control.py open tests/test_markers.py --line 12 --column 16
python3 scripts/ide_remote_control.py wait-smart-mode --timeout-ms 60000
python3 scripts/ide_remote_control.py diagnostics --path tests/test_markers.py
python3 scripts/ide_remote_control.py action CodeCompletion \
  --wait \
  --log-feature-id pytest-mark-completion \
  --log-level trace \
  --label marker-completion-smoke

The action result includes the command status and any captured logFiles, so a second manual wait-command call is not needed.

Web framework discovery diagnostics

web-discovery-diagnostics accepts no path, framework, or reset options. It performs one direct all-framework scanner pass for the open project; it is not a cache-hit latency measurement and does not read editor highlighting diagnostics. After doctor supplies the live base URL and wait-ready has completed, invoke it with the configured token:

BETTERPY_REMOTE_TOKEN="$DISCOVERY_TOKEN" \
python3 scripts/ide_remote_control.py --base "$DISCOVERY_BASE_URL" \
  web-discovery-diagnostics

The successful payload is flattened beneath ok: true and contains:

  • elapsedNanos: elapsed time for the one direct scanner invocation.
  • applications and endpoints: aggregate totals from that invocation.
  • frameworks: one declaration-ordered row for every supported framework, including zero-result frameworks; each row has framework, applications, and endpoints.
  • counters: aggregate scanner measurements only. discoveryRequests counts scanner requests; indexedCandidateLookups and indexedCandidateFiles describe index-based candidate selection; fullPythonScanFallbacks records fallback selection; fullPythonMarkerScanFilesExamined, routeObjectCandidateFiles, and discoveryFiles describe candidate breadth.
  • counters.phaseTimings: a declaration-ordered list containing every phase, including skipped phases with zero timings. Each item has phase, calls, totalNanos, and maxNanos. The phases are DISCOVERY_FILES, FULL_PYTHON_MARKER_SCAN, PSI_EXTRACTION_AND_TYPE_INFERENCE, ENDPOINT_AND_INCLUDE_COLLECTION, INCLUDE_EXPANSION, RESULT_NORMALIZATION, and CONNEXION_OPENAPI_SCAN.

The endpoint returns the normal error envelope, { "ok": false, "error": "..." }, before scanning when either prerequisite fails. The exact errors are:

  • API route navigation is disabled
  • Project is indexing; wait for smart mode before running web discovery diagnostics

Scenario streaming

POST /scenario/run/stream executes the same RemoteScenarioRunRequest accepted by POST /scenario/run, but returns progressive newline-delimited JSON. The response content type is application/x-ndjson; each line is one RemoteStreamEvent object and clients should process lines as they arrive.

The existing buffered scenario endpoint remains unchanged. Use streaming when a live IDE smoke test needs progress feedback, a terminal aggregate result, and feature log file references without manual actionwaitstate chaining.

Event schema

Every event has this envelope:

{
  "type": "scenario.step.finished",
  "sequence": 3,
  "timestampMillis": 1761693720000,
  "scenarioId": null,
  "stepIndex": 0,
  "commandId": "cmd-123",
  "label": "marker completion",
  "correlationId": "smoke-1",
  "payload": {}
}
Event type When it appears Payload
scenario.started Before the first step is dispatched. Empty in the first implementation wave.
scenario.step.started Immediately before a step handler runs. Empty; use stepIndex, label, and correlationId from the envelope.
scenario.step.finished After a step returns a successful RemoteScenarioStepResult. The step result object.
scenario.step.failed After a step returns a failed result, including assertion or wait mismatches. The failed step result object.
logging.files After an action step returns feature-scoped log files. A list of RemoteFeatureLogFile objects.
scenario.finished After normal completion or stopOnError termination. The final RemoteScenarioRunResponse.
error For parse/access/execution failures handled after the stream route begins. RemoteStreamError with a message.

Feature log content is intentionally not streamed in this wave. Action steps can still request logs with logFeatureIds and logLevel; streamed logging.files events expose durable log-file references with featureId, category, scopeId, path, and loggerNames. Feature ids resolve to logging categories from feature metadata, and the logging service attaches both plain category logger names and #category logger names.

CLI stream follow

Use scenario-run --stream to print compact NDJSON and exit non-zero when an error event appears or the final scenario.finished payload has passed=false:

python3 scripts/ide_remote_control.py scenario-run --stream --body '{
  "label": "marker completion stream",
  "correlationId": "pytest-marker-stream-smoke",
  "steps": [
    {
      "type": "open",
      "label": "open test file",
      "open": {"path": "build/live-ide-pytest-marker-project/tests/test_markers.py"}
    },
    {
      "type": "action",
      "label": "complete marker",
      "action": {
        "actionId": "CodeCompletion",
        "logFeatureIds": ["pytest-mark-completion"],
        "logLevel": "trace"
      }
    }
  ]
}'

Example output:

{"sequence":1,"timestampMillis":1761693720000,"type":"scenario.started"}
{"label":"open test file","sequence":2,"stepIndex":0,"timestampMillis":1761693720001,"type":"scenario.step.started"}
{"label":"open test file","payload":{"index":0,"ok":true,"type":"open"},"sequence":3,"stepIndex":0,"timestampMillis":1761693720002,"type":"scenario.step.finished"}
{"commandId":"cmd-123","label":"complete marker","payload":[{"featureId":"pytest-mark-completion","category":"com.github.chbndrhnns.betterpy.features.pytestMarkCompletion.PytestMarkCompletionContributor","scopeId":"scope-1","path":"/Users/jo/Library/Logs/JetBrains/PyCharm2026.2/betterpy-remote/...log","loggerNames":["com.github.chbndrhnns.betterpy.features.pytestMarkCompletion.PytestMarkCompletionContributor","#com.github.chbndrhnns.betterpy.features.pytestMarkCompletion.PytestMarkCompletionContributor"]}],"sequence":6,"stepIndex":1,"timestampMillis":1761693720005,"type":"logging.files"}
{"payload":{"passed":true,"steps":[]},"sequence":7,"timestampMillis":1761693720006,"type":"scenario.finished"}

curl can follow the stream too:

curl -N \
  -H "X-BetterPy-Remote-Token: $BETTERPY_REMOTE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/x-ndjson" \
  -d '{"steps":[]}' \
  "$BETTERPY_REMOTE_BASE/scenario/run/stream"

Pytest marker live smoke recipe

This recipe validates the pytest marker completion and strict-marker diagnostics workflow against a real IDE project. It is designed for a disposable uv-based project outside spec/.

1. Create a small uv project

mkdir -p build/live-ide-pytest-marker-project/tests
cd build/live-ide-pytest-marker-project
uv init --bare
cat > pyproject.toml <<'EOF'
[project]
name = "betterpy-live-pytest-marker-smoke"
version = "0.1.0"
requires-python = ">=3.11"

[tool.pytest.ini_options]
strict_markers = true
markers = [
  "slow: marks slow tests",
  "smoke: marks smoke tests",
]
EOF
uv add --dev pytest
cat > tests/test_markers.py <<'EOF'
import pytest


@pytest.mark.slow
def test_registered_marker():
    assert True


@pytest.mark.unknown_live_marker
def test_unknown_marker():
    assert True
EOF
cd ../../..

2. Open, trust, and wait for readiness

Open the project in the installed EAP, then handle the trust dialog only through remote-control modal endpoints:

open -a "PyCharm 2026.2 EAP" --args "$PWD/build/live-ide-pytest-marker-project"

python3 scripts/ide_remote_control.py doctor --try-ports 63342,63343
python3 scripts/ide_remote_control.py modal
python3 scripts/ide_remote_control.py modal-action click --text "Trust Project"
python3 scripts/ide_remote_control.py wait-modal --closed --timeout-ms 30000
python3 scripts/ide_remote_control.py wait-ready --timeout-ms 60000 --poll-ms 250

If no trust dialog is open, modal-action is not needed; wait-ready should be the next command.

3. Check strict-marker diagnostics

Open the file and collect diagnostics after smart mode:

python3 scripts/ide_remote_control.py open \
  build/live-ide-pytest-marker-project/tests/test_markers.py \
  --line 9 --column 14
python3 scripts/ide_remote_control.py wait-smart-mode --timeout-ms 60000
python3 scripts/ide_remote_control.py diagnostics \
  --path build/live-ide-pytest-marker-project/tests/test_markers.py

Expected result: diagnostics include a PytestStrictMarkersInspection warning for unknown_live_marker, while registered markers such as slow and smoke do not produce BetterPy strict-marker warnings.

4. Trigger marker completion with feature logs

Add a completion target such as @pytest.mark.smo in a disposable test file, place the caret after smo, and run completion with feature-scoped logging:

python3 scripts/ide_remote_control.py action CodeCompletion \
  --wait \
  --wait-status finished \
  --wait-timeout-ms 10000 \
  --log-feature-id pytest-mark-completion \
  --log-level trace \
  --label pytest-marker-live-smoke

Expected result: the command finishes, the marker completes to smoke, and the JSON output includes logFiles for the pytest-mark-completion feature. Inspect those files if completion candidates are unexpected.

To follow the same flow as a structured stream, run a scenario that requests the same feature logs from the action step:

python3 scripts/ide_remote_control.py scenario-run --stream --body '{
  "label": "pytest marker live smoke",
  "steps": [
    {
      "type": "open",
      "label": "open marker file",
      "open": {
        "path": "build/live-ide-pytest-marker-project/tests/test_markers.py",
        "position": {"line": 9, "column": 14}
      }
    },
    {
      "type": "action",
      "label": "marker completion",
      "action": {
        "actionId": "CodeCompletion",
        "logFeatureIds": ["pytest-mark-completion"],
        "logLevel": "trace"
      }
    }
  ]
}'

Expected result: the stream ends with scenario.finished and includes a logging.files event for pytest-mark-completion. Inspect the referenced paths to review PytestMarkCompletionContributor diagnostics; raw log records are not embedded in the stream.

Future streaming direction

The first streaming wave keeps log delivery to durable file references. If that is not enough, a later additive slice can stream selected log records inline or add a dedicated log-tail endpoint keyed by scopeId or commandId.

flowchart LR
  Scenario[Scenario stream] --> Refs[Log file refs]
  Refs --> Files[Scoped log files]
  CLI[CLI] --> Files
  Future[Future log tail] --> CLI

Keep future log streaming opt-in and scoped to requested logFeatureIds so live validation remains bounded and does not expose unrelated IDE logs.

Troubleshooting

Symptom Likely cause Next step
Connection refused IDE is not running, the built-in server is on another port, or BETTERPY_REMOTE_BASE points to the wrong IDE. Run doctor --try-ports 63342,63343, or pass the target IDE's --connection-file; start the IDE or set ide.built.in.server.port.
401 or token error Client token is missing or does not match any configured settings/JVM token. Export BETTERPY_REMOTE_TOKEN, use Copy Connection Environment, or update idea.properties and restart the IDE.
ping/status works but state fails Bootstrap endpoints are intentionally tokenless; protected endpoints require a token. Provide the token for all other commands.
channel is not dev The target IDE loaded a non-dev plugin build. Deploy a dev-channel build into the installed IDE.
featureEnabled=false The remote-control feature is disabled or filtered out. Verify the dev feature set and plugin deployment.
Commands stay queued A modal is open, the IDE is indexing, or the command was not followed to completion. Run doctor, handle modal state, then use action --wait or wait-command.
Modal blocked Trust or another IDE dialog is waiting for a user decision. Inspect with modal, act with modal-action, then wait-modal --closed.
Dumb mode/indexing Project indexes are not ready. Run wait-ready --timeout-ms 60000 or wait-smart-mode --timeout-ms 60000.
Diagnostics are empty File is not open/highlighted yet, the inspection is disabled, or project trust/indexing is incomplete. Open the file, wait for readiness, then call diagnostics again.
409 with UNSAVED_DOCUMENT A remote file/write would overwrite unsaved open-editor changes. Save or discard the local editor changes, then retry; BetterPy does not silently replace them.

Local client

Use the helper script from the repository root:

export BETTERPY_REMOTE_TOKEN=dev-secret
python scripts/ide_remote_control.py ping
python scripts/ide_remote_control.py schema
python scripts/ide_remote_control.py open src/main.py --offset 42
python scripts/ide_remote_control.py action EditorCopy
python scripts/ide_remote_control.py action EditorCopy --wait
python scripts/ide_remote_control.py action EditorCopy --log-feature-id parameter-object-refactoring
python scripts/ide_remote_control.py gutters --path src/main.py --line 12
python scripts/ide_remote_control.py gutter-action --path src/main.py --line 12 --gutter-index 0
python scripts/ide_remote_control.py gutter-action --path src/main.py --line 12 --action popup --popup-action-text "Jump to Endpoint Window"
python scripts/ide_remote_control.py inlays --path src/main.py --line 12
python scripts/ide_remote_control.py inlay-action --path src/main.py --provider-id betterpy.class.implementations --action-id activate
python scripts/ide_remote_control.py logging parameter-object-refactoring --scope-id repro-1
python scripts/ide_remote_control.py logging parameter-object-refactoring --scope-id repro-1 --disable
python scripts/ide_remote_control.py diagnostics --path src/main.py --line 12 --column 5 --caret-only
python scripts/ide_remote_control.py web-discovery-diagnostics
python scripts/ide_remote_control.py window-bounds --width 1280 --height 720
python scripts/ide_remote_control.py window-maximize
python scripts/ide_remote_control.py geometry --scope all
python scripts/ide_remote_control.py geometry-windows
python scripts/ide_remote_control.py geometry-windows --include-balloons
python scripts/ide_remote_control.py occupied-start --scope all --session-id demo --auto-sample-actions --sample-interval-ms 250
python scripts/ide_remote_control.py occupied-sample demo
python scripts/ide_remote_control.py occupied-stop demo --margin 16 --ffmpeg-crop --even-dimensions
python scripts/ide_remote_control.py layout-preset --width 1280 --height 720 --hide-tool-windows --focus-editor
python scripts/ide_remote_control.py screenshot-crop --scope all --margin 16 --ffmpeg-crop
python scripts/ide_remote_control.py feature-disable contextual-feature-tips
python scripts/ide_remote_control.py tool-windows
python scripts/ide_remote_control.py tool-window-show "Pytest Explorer"
python scripts/ide_remote_control.py tool-window-move "Pytest Explorer" --anchor right
python scripts/ide_remote_control.py tool-window-resize "Pytest Explorer" --width 420
python scripts/ide_remote_control.py wait-command <command-id> --status finished
python scripts/ide_remote_control.py wait-ready --timeout-ms 60000
python scripts/ide_remote_control.py wait-ready --expected-instance-id <instance-id> --timeout-ms 60000
python scripts/ide_remote_control.py scenario-run --stream --body '{"steps":[]}'
python scripts/ide_remote_control.py doctor --try-ports 63342,63343
python scripts/ide_remote_control.py modal
python scripts/ide_remote_control.py modal-action setText --name nameField --value newName
python scripts/ide_remote_control.py wait-modal --open --title-contains Rename

Override the base URL with BETTERPY_REMOTE_BASE, --base, or --connection-file.

Endpoints

Call GET /schema for the runtime endpoint list.

Method Path Purpose
GET /ping Check endpoint reachability and dev-token configuration.
GET /status Return non-secret, loopback-only bootstrap metadata for the local IDE instance.
GET /schema Return the supported route list.
POST /open Open a file and optionally position the caret or selection.
POST /file/write Write a project file; refuses to overwrite unsaved open-editor changes.
POST /action Trigger an IDE action by action id.
POST /scenario/run Execute a multi-step scenario and return one aggregate JSON result.
POST /scenario/run/stream Execute a scenario and stream RemoteStreamEvent objects as application/x-ndjson.
GET /commands/{commandId} Read command status.
POST /wait/command Poll a command until the requested or terminal status.
POST /logging Enable or disable feature-scoped debug logging and return log file paths.
POST /usages Find usages at an explicit file position or the current caret.
POST /diagnostics Return open-editor highlighting diagnostics and active inspection results for a file or caret.
POST /diagnostics/web-discovery Run one direct all-framework web-discovery scan and return aggregate timings and cardinalities.
POST /gutters Return materialized gutter icons for a line.
POST /gutter/action Activate a gutter click action or popup context-menu item.
POST /inlays Return semantic inline inlay and code-vision entries for a file, line, or selected editor.
POST /inlay/action Activate a semantic inlay or code-vision action without relying on raw screen coordinates.
GET /state Return current editor state, last usages, last command, and modal state.
GET /geometry Return current editor and IDE frame screen bounds.
POST /geometry Return current bounds for editor, ide, or all scope.
GET /geometry/windows Return visible IDE-owned windows; balloon notifications are excluded by default.
POST /geometry/windows Return visible windows with configurable includeBalloons.
POST /geometry/occupied/start Start an occupied-space session and record the initial bounds.
POST /geometry/occupied/sample Add the current bounds to an occupied-space session.
POST /geometry/occupied/stop Stop a session and return the maximum occupied bounds, with optional margin.
POST /geometry/occupied/reset Clear one or all occupied-space sessions.
POST /geometry/screenshot-crop Prototype pixel-based crop detection inside current geometry bounds.
GET /window Return IDE window bounds and frame state.
POST /window/bounds Set IDE window position and/or size.
POST /window/maximize Maximize the IDE window.
POST /window/restore Restore the IDE window to normal frame state.
POST /layout/preset Apply a deterministic recording layout preset for window and tool-window state.
POST /features/{featureId}/enabled Enable or disable a BetterPy feature id; use contextual-feature-tips for contextual feature nudges.
GET /tool-windows List tool windows and their layout state.
POST /tool-windows/{id}/show Show a tool window by id.
POST /tool-windows/{id}/hide Hide a tool window by id.
POST /tool-windows/{id}/activate Activate a tool window by id.
POST /tool-windows/{id}/move Move a tool window to left, right, top, or bottom.
POST /tool-windows/{id}/resize Best-effort resize of a tool window using public component sizing APIs.
GET /modal Return the current modal dialog component tree.
POST /modal/action Click, set text, toggle, select, press a key, or close a modal.
POST /wait/modal Poll until modal state matches title/open/class filters.
POST /wait/smart-mode Poll until the project leaves dumb mode.
POST /wait/layout-stable Poll until the requested IDE layout remains stable.
POST /wait-ready Run the modal, smart-mode, layout, and state readiness sequence.

Response semantics

Buffered JSON endpoints return an error envelope with ok: false, an error message, and, where applicable, a stable code. The HTTP status distinguishes the recovery action:

Status Meaning
400 Bad Request Invalid arguments or request payload.
401 Unauthorized Missing or invalid remote-control token.
403 Forbidden Channel, feature-enabled, or loopback gate rejected the request.
404 Not Found Unsupported remote-control endpoint.
409 Conflict The request would overwrite an unsaved open-editor document (UNSAVED_DOCUMENT).

Recording crop workflow

Coordinates returned by geometry endpoints are physical screen coordinates, intended for post-processing screen recordings. For a full-IDE crop:

python scripts/ide_remote_control.py window-bounds --width 1280 --height 720
python scripts/ide_remote_control.py occupied-start --scope all --session-id demo --auto-sample-actions --sample-interval-ms 250
# run the scripted interaction here
python scripts/ide_remote_control.py occupied-stop demo --margin 16 --ffmpeg-crop --even-dimensions

Use --scope editor when the final crop should include only the selected editor content area, --scope ide for the IDE frame, and --scope all for the union of visible IDE-owned windows such as popups, dialogs, and floating windows. Balloon notifications are excluded by default because they often appear in the bottom-right corner and inflate recording size; pass --include-balloons when they are part of the scenario.

The returned bounds is the union of all sampled bounds in the session; margin expands that final rectangle after sampling. --auto-sample-actions samples after remote-control operations such as open, actions, gutter actions, inlay actions, modal actions, tool-window changes, and layout presets. --sample-interval-ms adds timer-based sampling while the session is active, which helps catch short-lived popups.

For deterministic recording setup, layout-preset can set window bounds, hide tool windows, focus the editor, and configure individual tool windows:

python scripts/ide_remote_control.py layout-preset \
  --width 1280 --height 720 \
  --hide-tool-windows \
  --focus-editor \
  --tool-window '{"id":"Pytest Explorer","visible":true,"anchor":"right","width":420}'

screenshot-crop is an opt-in prototype that screenshots the selected geometry bounds and computes a non-background crop using the top-left pixel as the background reference.

Modal actions can target controls by path, name, accessibleName, exact text, textContains, type, and className. Prefer name or accessibleName when available because positional paths change when dialog layouts change.

Feature Logging

POST /logging accepts:

{
  "featureIds": ["parameter-object-refactoring"],
  "enabled": true,
  "level": "trace",
  "scopeId": "repro-1"
}

level can be debug, trace, or all. scopeId is optional; when omitted, the IDE generates one. This is runtime-only logging capture: feature ids are resolved through feature metadata, and the returned feature/file category values come from each feature's loggingCategories. The response includes logFiles with exact paths under the IDE log directory.

POST /action also accepts logFeatureIds and logLevel. In that mode, logging is scoped to the command id, enabled while the action runs, disabled afterwards, and the same logFiles are returned in both the action response and command snapshot.