Held Modifiers Across an Action Group

hotkey presses a set of keys and releases them immediately — fine for a one-shot chord, but there was no way to hold ctrl (or shift) down across several independent actions (range-select with shift-clicks, ctrl-clicks to multi-select) and be sure the modifiers are released even if one of those actions raises.

plan_with_modifiers() wraps an op-step list with press / release steps and is pure / unit-testable; hold_modifiers() is a context manager that presses on enter and releases (in reverse) on exit — including on exception — dispatching through an injectable sink. Imports no PySide6.

Headless API

from je_auto_control import hold_modifiers, plan_with_modifiers
from je_auto_control import click_mouse

# shift-held range select: every click happens with shift down
with hold_modifiers(["shift"]):
    click_mouse("mouse_left", 100, 100)
    click_mouse("mouse_left", 100, 300)
# shift is released here — even if a click raised

plan_with_modifiers([{"op": "click"}], ["ctrl", "shift"])
# press ctrl, press shift, click, release shift, release ctrl

Modifiers are pressed in order on entry and released in reverse order on exit, in a finally block, so a stuck modifier can never leak. plan_with_modifiers is the pure plan for any op-step list.

Executor commands

AC_with_modifiers runs a nested JSON action list while modifiers (e.g. ["ctrl"] or "ctrl+shift") are held, releasing them even if an action fails. It is exposed as the MCP tool ac_with_modifiers and as a Script Builder command under Keyboard.