Repository navigation
Releases: loomhand/shuttle
Release list
shuttle v0.1.19
- macOS: foreground click, type and key with a window bring that window to the front first and refuse when it cannot, instead of going to the frontmost app.
shuttle v0.1.18
- Bundled: cua-driver v0.38.0-loomhand.2 (was v0.38.0-loomhand.1). macOS: background typing into a native search field now triggers the app's search. A background accessibility write into a single-line native field outside web content, whose window is the app's key window, is followed by a net-zero key edit (space, backspace), so search-as-you-type fields such as WeChat's search box run their search. When that edit changes the field's text, as an accepted inline completion does, the result carries a warning.
shuttle v0.1.17
- Embedded mode. An app that ships shuttle inside its own bundle and starts
shuttle serveitself setsSHUTTLE_EMBEDDED=1forserveandmcp, so the macOS permissions belong to that app (install.md).servethen starts the bundled cua-driver withCUA_DRIVER_EMBEDDED=1, which raises no permission prompts of its own.mcponly connects: when no daemon answers it fails at once withno shuttle daemon answers at <endpoint>, and never starts one. Before,mcpfrom a copy of shuttle inside another app's bundle found that app as its own bundle and ranopen -a <that app> --args serve. - Stale driver socket. Before each start of the bundled cua-driver, the first one and every restart, the daemon removes a socket file at the driver's endpoint that refuses a connect, as one a killed driver leaves; a socket that answers or any other error leaves the file alone. A cua-driver in embedded mode refuses to start over an existing socket file. Windows pipes are unchanged.
- A click, scroll or drag that names a window takes pixels of that window's shot. With
window,x,y(andto_x,to_yfor a drag) are pixels of the imagescreenshot {window}(desk shotwithwindow) returns, in the foreground and in the background, on any display and at any scale, so the point an agent picks in that image is the point it clicks. A cropped shot returnsorigin, its top left in the whole window image, and its pixel (px,py) is (origin[0] + px,origin[1] + py). A title that matches two listed windows alike, both equal to it or both containing it, is refused for such points, since a shot by that title may show the other window, and a window shot waits for a click, scroll or drag in flight, so the driver maps the click by the read it was made for. Before, they were desk coordinates, the main display's pixels, which an agent had to work out from a window origin thatdesk windowsdoes not give, and which on a Retina main display are not the pixels of a window on a 1x display. Withoutwindowthey stay desk coordinates;atkeeps desk coordinates withwindowtoo. A script that passeswindowwith desk coordinates now clicks elsewhere: dropwindowor convert the point. - macOS: a click after a window shot lands where it is aimed.
screenshot {window}reads the window at full size, and cua-driver maps the next pixel click on that window by that read, while shuttle still scaled the click by the smaller image of its own earlier read. On a window wider than 1280 image pixels (on a Retina display, wider than 640 points) a background click, or a foreground click in the window, after a shot of it landed short, at about 0.6 of its distance from the window's top left on a 2113-point TextEdit window. Shuttle now keeps the scale of the shot's read for the next click. atwithout a window reads the window at the point.desk atwith neitherwindownorpidhit-tests the topmost app window the driver lists at the point, and the allowlist judges that window's app; with no app window there it fails withno app window at the point. Before, it hit-tested in the front window, so with an allowlist a point on another window failed with the front app's name (Warp is not in the app allowlistfor a point on a window of an allowed app on a second display).
shuttle v0.1.16
-
Bundled: cua-driver v0.38.0-loomhand.1 (was v0.37.0-loomhand.5), the fork's patches rebased onto upstream cua-driver 0.38.0 and later fixes: Windows
list_windowsleaves out the driver's own overlay windows, keys for a popup a Linux MPX click opened go through the master keyboard of the session that clicked, and the macOS background gate and window-change detector enumerate windows without the Space snapshot, now upstream (#5566, #5652, #5712); upstream also tracks the agent cursor's placement explicitly, so negative display coordinates no longer read as an unplaced cursor, keeps a Windows foreground drag off the window that covers its target, and keeps GNOME element actions inside the requested window.verify_hit, the kitty AXValue route, the off-Space window lookup, the macOS overlay tile and the X11 core-focus bounce carry over unchanged. -
macOS: a snapshot or find by title picks the on-screen window over its hidden twin. Among windows of the same title, none of them active,
snapshot,findand the other verbs that pick a window by the accessibility tree take the newest one on a display before a minimized or hidden one, sosnapshotof WeChat's "微信" reads its main window and not the hidden 280×380 one beside it. A window shows when its accessibility frame is the bounds of an on-screen window of its process, read from the window list each window listing already reads and from its position and size in the existing batched read. An app's only window of a title, such as a minimized one, can still be picked; two windows with the same frame both count as shown. -
shotof a window on any display.desk shotand thescreenshottool takewindowand return that window alone as cua-driver captures it, so a window on a second display, which the main-display capture does not hold, can be shot;x,y,w,hthen crop the window's image in its own pixels. The window is picked asfocuspicks it, within the app allowlist. A rectangle off the main display still fails, and the error now says to name the window. A device shot refuseswindow. -
macOS: a title picks the on-screen window over its hidden twin. Background input,
focusand the element click by title or pid no longer pick an app's hidden window that is off screen on the current Space when a window of the same app and title is on screen: WeChat keeps a hidden 280×380 "微信" beside its main window, and a click by title went to the hidden one and failed with "this window takes no pointer input without the foreground". An app's only window with a title, such as a minimized one, can still be picked, and twins across Spaces are still refused. -
gui_plannames a front app that stopped answering. On Linux, when no window is in front because the app of the X active window lists no window in AT-SPI (LibreOffice while it saves or reloads, an app whose accessibility peer hangs), a step that names no window stops withfront app "<app>" (pid N) is not answering accessibilityin place ofno focused window, and an escalation without a front window shows that line as its outline. The new desk verbunansweredgives that window. It is read only when a plan stops.
shuttle v0.1.15
gui_planwith no window in front. When no window is in front, as on GNOME while_NET_ACTIVE_WINDOWnames none and no listed window is active, awaitstep takes the one listed window that carries its title and brings it to the front, and a step that names no window acts in the plan's last window when one listed window has its title, bringing it to the front first. Before,waitfailed withexpected window "…" did not appearwhile its own window list held that title, and the next step failed withno focused window. Two listed windows with the awaited title still fail the step, with no window in front, an AT-SPI alert is not taken for the awaited window, and a plan with no window of its own yet still stops atno focused window. Background plans are unchanged.
shuttle v0.1.14
- Faster
gui_planwait for a window already in front. Awaitstep whose window is already the front one no longer reads that window's tree first. A dialog that has just opened, such as Save As, is still being built then, and that first read cost 67–70 ms (about 5 ms once built), soctrl+shift+sthenwait: "Save As"took 75–113 ms and now takes about 20 ms on an Ubuntu 24.04 XFCE desktop. A front window whose role isalertstill stops the plan, as before, also when its tree reads empty. Since 0.1.13 the front window on Linux follows the X active window, which made the dialog front by the time the step started; 0.1.12 had skipped the read only because its stale front still named the editor. - computer_act on an element without a press action (macOS). A left click or double click on an element whose app refuses its press action with
-25206(a window, sheet, toolbar, list or native text field) is sent once more as averify_hitclick: cua-driver brings the element's window to the front and clicks the element's centre only when the accessibility hit test there finds that element or one inside it. When the driver refuses, the result carries the press error and the refusal, and nothing is clicked. Before, shuttle clicked the element's centre on the desktop whenever its window was on top there, so it clicked whatever covered that centre inside the window (a toolbar's centre over a heading below it), and it refused when another window covered the element. A press that succeeds costs nothing more. desk.atnames a window on top. On Linux and macOSathit-tests inside the window it is asked about, so it named an element of that window even where another window covered the point (30 of 30 covered points on an Ubuntu 24.04 XFCE desktop, 56 of 56 on macOS). It now also returnscoveredwith that window'stitleandpid: on Linux from the window manager's stacking order (_NET_CLIENT_LIST_STACKING, top down, past the panel and desktop windows; a popup menu or tooltip, which the window manager does not manage, is not checked), the top window counting as the target's own when it is its process's only window or that process's one window with the target's title, so two same-titled windows of one app count as covering, on macOS from the system-wide accessibility hit test. Agui_plangrounded click refuses such a point withgrounding pointed at …, under the window "<title>"before any model check. Windows is unchanged: its hit test already returns the top window's element.
shuttle v0.1.13
- Bundled: cua-driver v0.37.0-loomhand.5 (was v0.37.0-loomhand.4): an element
clickcan ask forverify_hit, which hit-tests the element's centre after fronting its window and a 150 ms (Windows) / 40 ms (macOS) hover settle and clicks only when the element is there, refusing withhit_mismatchotherwise; on macOS background text to kitty goes in as an accessibility value write, which kitty takes without being the key window, and a blank kitty read-back reports unverified instead of zero delivered; a macOS window on another Space is found through its app's focused or main window, and an off-Space window of an app with its own renderer (WeChat, Electron, Chromium, Qt) carries a note that its pixels can be stale. - Off-Space note. The
unverifiednote on background input to a macOS window on another Space now says that background input cannot read that window back; after typed text, not to type the text again, and to ask the user to confirm the text before submitting it with Return or Send; after a key, click, scroll or drag, not to send the input again, and to ask the user to confirm its effect. Before, "read it back on its Space before sending the input again" read to an agent as a stop before the Return too. - Renamed to shuttle, under a loomhand namespace. The command is
shuttleand the macOS appShuttle.app(bundle idio.github.loomhand.shuttle); release archives areshuttle-vX.Y.Z-<target>. Everything else lives underloomhand, so another tool named shuttle does not collide: the app in~/Applications/Loomhand, the config in~/.config/loomhand/shuttle(%APPDATA%\loomhand\shuttle), the daemon socket in~/Library/Caches/loomhand/shuttleor~/.cache/loomhand/shuttle(Windows:\\.\pipe\loomhand-shuttle-desk), the Linux install in~/.local/lib/loomhand/shuttle, the Windows install in%LOCALAPPDATA%\Programs\loomhand\shuttle, and the process-wide class namesLoomhandShuttleControlsandLoomhandShuttleConfirmWatch. There is no fallback to the old names. To upgrade: quit the old daemon (pkill -f 'cocoon-shuttle serve'), install, move~/.config/cocoon-shuttleto~/.config/loomhand/shuttle(Windows:%APPDATA%\cocoon-shuttleto%APPDATA%\loomhand\shuttle), point your agent's MCP entry atshuttle mcp, grant Accessibility and Screen Recording to~/Applications/Loomhand/Shuttle.app, and remove~/Applications/Cocoon Shuttle.app,~/.local/bin/cocoon-shuttleand the old grants. Other tools also install ashuttlecommand (cargo-shuttle, Homebrewshuttle-cli), so checkwhich shuttle; the daemon starts its app by full path, never by name, since another macOS app is named Shuttle. - Linux front window.
windowsmarks as active the one window that_NET_ACTIVE_WINDOWnames, by its pid and title, when AT-SPI still calls another window of the session active, so after a backgroundtype_textinto a back GTK windowfrontnames the window in front and not the back one; GTK keeps a window's AT-SPI active state after the X focus moved on. Two windows of that pid with that title leave no window active, sofrontnames none rather than maybe the wrong one; an X window whose pid has no AT-SPI window, or whose title is longer than 4 KiB, keeps AT-SPI's state. X window titles are read whole up to 4 KiB, where they were cut at 256 bytes. The check reads the active window, its pid and its title on the MIT-SHM connection, three X round trips perwindowscall for a title of up to 256 bytes and one more per further 256 bytes; without MIT-SHM the AT-SPI state stands. - computer_act clicks on text fields on macOS and Windows. A left click on a text field element (
AXTextField,AXTextArea,AXComboBox,Edit,ComboBox) asks the bundled cua-driver for averify_hitclick, which v0.37.0-loomhand.5 delivers: the driver brings the field's window to the front and posts a click at the field's centre only when the accessibility hit test there finds that same element or one inside it, so typing next goes into the field. Before, the click ran the field's press action, which does not focus it on macOS, or a background click on Windows, and the text went to whatever window was in front. When the driver refuses (another element on top, the field gone or replaced, no foreground), the field gets its press action, and if it has none the refusal is returned rather than a click at a point; a click that failed after it was sent is not repeated. A cua-driver withoutverify_hit, such as an older one at its default socket that a build without a bundled driver uses, presses the field as before. - Grounded clicks need an element and a clear yes. When a
gui_planclick finds no element and grounds the target on the screen, it clicks only where the accessibility tree has an element under the point that is not a root (awindow,frame,application,dialogordocument, which macOSAXWebAreaand UIA Document map to) and lies inside the plan window or a window it owns, judged by the element's ancestors when they name a window (a Windows UWP app shows its content from another process than its frame's) and by its process when they name none; otherwise it refuses and asks no verification model, withgrounding pointed at no accessible element,…, a window or document root,…, which belongs to another process,…, whose process is unknownor, when the element under the point could not be read,… a point whose element could not be read: <reason>.desk.atnow returns thepidof the element it finds. Sogui_planno longer clicks by vision alone in an app that exposes no accessibility tree, such as desktop Chrome on a cocoonbox desktop: usecomputer_actthere. The first check of the 640x400 crop now takes the click alone only at p ≥ 0.9 (was 0.5); below that the zoom check (0.65) and the screen check with a ring (0.95) decide. A control that was not on a Chrome page got a grounded click in about 1 run of 7 before, at crop answers of 0.43–0.58 for a point with no element under it. The step'straceentry now carriesgroundwith the check path,p,p_zoom,p_fulland the element under the point (hit), whether the step clicked or refused.
shuttle v0.1.12
- Quieter serve log off Linux. On macOS and Windows the computer verbs no longer try the Linux MIT-SHM grabber on each call, so the serve log no longer repeats
desk: MIT-SHM capture unavailable, using cua-driverfor every computer call. - Bundled: cua-driver v0.37.0-loomhand.4 (was v0.37.0-loomhand.2): on macOS the agent cursor overlay repaints only the region around its cursors instead of the whole screen each animation frame, so a background
press_key,type_textorscrolltakes about 0.5–0.6 s in place of 1.3–2.3 s while the cursor animates on a GPU-less VM; on Linux under xfwm4, a backgroundtype_textinto a window behind another no longer leaves the front window looking inactive: the focus guard puts back the window manager's active window by moving the core focus through None and back, where it used to reportnot_restoredafter its full restore budget (about 1.1 s → 0.64 s per call on cross-app pairs).
shuttle v0.1.11
- Auto input.
[desk] input = "auto"sends desk input in the background and takes the foreground only for an action that background input refused before anything was sent: shuttle brings that action's window to the front, runs it once in the foreground, and its result says"input": "foreground". A Windows drag runs in the foreground from the start. Input the driver could not confirm comes back withunverified, as in background, and is never sent twice.gui_plantypes a step again in the foreground only when its read-back shows that nothing landed and the front held, and a step background refuses before sending anything (a macOS menu-bar entry, grounding, a pixel state read, a visionexpect) fronts the plan window and runs, with the rest of the plan, in the foreground. On Linux and with an[android]section,autoacts asforeground(safety.md). - macOS menu-bar icon. The indicator is an 18 pt template icon in place of the
Shuttletext: outlined when idle, filled while acting, struck through when stopped. Its tooltip and the first line of its menu name the version and the input mode. The icons are vector PDFs built into the binary, and the item redraws only when its state changes. A menu-bar manager such as Ice puts a new item in its hidden section until you move it (safety.md). - macOS windows on another Space. With
[desk] input = "background",windowson macOS also lists the windows on another Space, a desktop or a full-screen app's, markedspace: "other"and never active, so background input can name them; before, a window on another Space was missing from the list even by pid. Background input by title no longer picks an app's hidden window that belongs to no Space over its window on a Space, and refuses a title that two windows of one app share across Spaces, naming both. When the driver cannot confirm input to a window on another Space,unverifiedadds that its capture may be stale. In background modesnapshot,find,atandcloseon such a window fail with "is on another Space" in place of "no window". The listing asks WindowServer for the windows of the Spaces no display shows, about 1 ms perwindowscall in background mode; the private calls it uses are looked up at run time, so a macOS without them only loses the list.
shuttle v0.1.10
- Background gui_plan. With
[desk] input = "background",gui_plandrives the window its app or wait step names, and the dialogs that window owns, without fronting it or moving your pointer, on macOS and Windows; it refuses only on Linux. It reads that window by its id, and refuses input when another window of the app has the same title, a step that needs the screen, a macOS menu-bar entry, a close of a dialog it cannot tie to the window, input the driver could not confirm that its own read or the next wait, verify or expect step does not show, and input after its window is gone; an app that brings itself to the front fails the step. Known limits: the typing check counts the text in any element of the window, and a macOS dialog with no owner is taken by its app and its arrival after input.desk windowsnames the window that owns a macOS sheet or a Windows owned window asowner(safety.md). - Computer verbs on macOS and Windows.
desk computer_state,computer_act,computer_end,computer_scaleandlookanswer on macOS and Windows as on Linux, refused under an app allowlist. Screenshots and the settle come from cua-driver's captures; screenshot pixels map to the screen by the driver's screen and screenshot widths when it reports noframe_scale; keys and typing go to the frontmost window; input at a screen point now goes in foreground delivery, so it drives the real pointer and may bring the app it clicks to the front, and on a Retina Mac it goes in capture pixels; window bounds may be fractional; and an element whose macOS app refuses its press action gets one click at its centre when the accessibility hit test there lands in its own window.lookwith aregioncuts it from a full-size driver capture wherever MIT-SHM is not available. - macOS launch grace. A macOS app in its first 10 s on screen is no longer skipped from
windowsfor 10 s when its accessibility reads keep timing out, so a launch that keeps its app busy no longer hides its window past agui_planapp step's 8 s wait (the 0.1.9 known issue). An app that was on screen before keeps the 2 s / 10 s rule. - macOS typing pace. macOS foreground typing of basic-plane text no longer waits 30 ms per character in the driver: 20 characters take about 385 ms in place of 866. Astral characters, such as most emoji, and background typing keep the driver default.
- Windows pipe hang-up. On Windows a client connection dropped with a write still pending cancels its I/O, so the other end sees the hang-up at once: a service splice whose first pump ended and a call an MCP cancel dropped mid-write left the server waiting before, since the handle stayed open until a write it never read completed.
- Bundled: cua-driver v0.37.0-loomhand.2 (was v0.37.0-loomhand.1): on macOS
get_desktop_statewith no overlay to leave out captures through ScreenCaptureKit instead of ascreencaptureprocess, about 170 ms in place of 325 ms at a 1024 cap on a GPU-less VM; on Linux an ended session keeps its master pointer while its menus are open by upstream's own code (trycua/cua#5254), and a text may use keycode 248 as a spare, so 18 of a desktop box's 19 free keycodes. - Known issue: on a Retina Mac guest whose apps answer accessibility slowly, a
gui_planstep can still fail withno accessibility answer within 3000 ms, or find a just-launched window late; this is the app's own latency, not the skip.