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 /pingand the bootstrap-onlyGET /status.statusdiscloses no token value and is limited to connection metadata needed to identify the intended local IDE instance. When both the settings-panel session token andbetterpy.remoteControl.tokenare present, either token is accepted. - Uses the
X-BetterPy-Remote-Tokenheader, or atokenquery parameter.
Base URL:
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:
Example properties:
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:
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:
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.applicationsandendpoints: aggregate totals from that invocation.frameworks: one declaration-ordered row for every supported framework, including zero-result frameworks; each row hasframework,applications, andendpoints.counters: aggregate scanner measurements only.discoveryRequestscounts scanner requests;indexedCandidateLookupsandindexedCandidateFilesdescribe index-based candidate selection;fullPythonScanFallbacksrecords fallback selection;fullPythonMarkerScanFilesExamined,routeObjectCandidateFiles, anddiscoveryFilesdescribe candidate breadth.counters.phaseTimings: a declaration-ordered list containing every phase, including skipped phases with zero timings. Each item hasphase,calls,totalNanos, andmaxNanos. The phases areDISCOVERY_FILES,FULL_PYTHON_MARKER_SCAN,PSI_EXTRACTION_AND_TYPE_INFERENCE,ENDPOINT_AND_INCLUDE_COLLECTION,INCLUDE_EXPANSION,RESULT_NORMALIZATION, andCONNEXION_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 disabledProject 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 action → wait → state 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.