Skip to content

Script API

This page organizes the script API by task. For live autocomplete and complete signatures in your editor, add this line at the top of a script:

/// <reference path="./.cursorcrane/cursorcrane.d.ts" />

Every script needs meta and run(ctx):

/** @type {CursorCrane.Meta} */
const meta = {
type: "action",
name: "Example",
permissions: [],
};
/** @param {CursorCrane.Context} ctx */
async function run(ctx) {
ctx.toast("Done");
}
meta fieldPurpose
type"action" performs a one-off action; "form" opens a form.
presentationOptional for forms. "panel" suits continued work; "transient" closes on focus loss.
nameThe script name shown in Cursor Crane.
permissionsThe permissions required by the script.

Available permissions are "accessibilityAPI", "clipboard", "keyboard", "mouse", and "shell". An API cannot be used unless the script declares its required permission.

Use ctx.applicationManager to inspect running apps:

MemberPurpose
applicationsAll available apps.
getApplicationByBundleIdentifier(id)Finds an app by bundle identifier.
getApplicationByPid(pid)Finds an app by process ID.

Each Application has name, bundleIdentifier, and pid.

Use ctx.windowManager to find and switch windows:

MethodPurpose
getWindowsByPid(pid)Gets an app’s windows.
getWindowsByBundleIdentifier(id)Gets windows by app.
getWindowById(windowId)Finds a window by ID.
getActiveWindow()Gets the window currently in use.
getPreviousWindow()Gets the previously used window.
activateWindow(windowId)Switches to a window.

Window has title, windowId, and its application.

ctx.elementInspector.getElementByPid(pid) gets an app’s interface-element entry point and requires accessibilityAPI. From there, use getWindows() to find the window, then find its buttons, text fields, and list rows.

const application = ctx.elementInspector.getElementByPid(window.application.pid);
const root = application?.getWindows().find(
item => item.windowId === window.windowId
);
const saveButton = root?.queryElements('Button[title="Save"]')[0];
PropertyPurpose
titleThe title provided by the element.
displayTitleA title that is more suitable to show to a person.
label, descriptionAdditional information about the element.
kindThe element type, such as Button or TextField.
windowIdThe ID of the window containing the element.
rect / frameThe element’s on-screen position and size.
MethodPurpose
getChildren()Gets direct children.
getVisibleChildren()Gets children that are currently visible.
getParent()Gets the parent element.
getFocusedWindow()Gets the app’s focused window.
getWindows()Gets the app’s windows.
getWindow()Gets the element’s containing window.
queryElements(selector, maxCount?)Searches all elements in a window. It traverses children and can block noticeably on a large hierarchy.
queryVisibleElement(selector, maxCount?)Searches only the currently visible content.
getStringValue()Gets the current value as text, such as the content of a text field.
getIntValue()Gets the current value as an integer.
getDoubleValue()Gets the current value as a floating-point number.

See Element Selectors for selector syntax.

These methods require accessibilityAPI and can be awaited:

MethodPurpose
performPress()Triggers the element’s main action.
setStringValue(value)Sets a new text value on an element.
setIntValue(value)Sets a new integer value on an element.
setDoubleValue(value)Sets a new floating-point value on an element.
performClick()Moves to an element and clicks it.
performMoveCursorTo()Moves to an element.
performShowMenu()Opens an element’s menu.
getActions()Lists the actions supported by an element.
performAction(identifier)Triggers a particular action.

Choose the getter or setter that matches the value type used by the element.

Requires keyboard. All three methods return a Promise:

await ctx.keyboard.pressKey("a", ["command"]);
await ctx.keyboard.releaseKey("a", ["command"]);
await ctx.keyboard.pressAndReleaseKey("return", []);

Use "shift", "control", "option", and "command" as modifiers.

Requires mouse:

MethodPurpose
moveTo(x, y)Moves the pointer smoothly.
warpTo(x, y)Moves the pointer immediately.
click(button, modifiers?)Clicks; 0 is left, 1 is right, and 2 is middle.
doubleClick(button, modifiers?)Double-clicks.
rightClick(modifiers?)Right-clicks.

Requires shell. execute(command) runs a non-interactive zsh command and returns its output:

const result = await ctx.shell.execute("git status --short");
if (result.exitCode === 0) {
ctx.toast(result.stdout || "Working tree is clean");
}

The result contains stdout, stderr, and exitCode. Commands share the script’s execution timeout, and output is limited to 1 MiB per stream.

Requires clipboard. Read or replace the plain text on the system clipboard:

const text = ctx.clipboard.readText();
if (text !== null) {
ctx.clipboard.writeText(text.trim());
}

readText() returns null when the clipboard does not contain plain text. writeText(value) replaces the current clipboard contents with the provided text.

Use ctx.toast(message) for brief feedback. ctx.toast.highlight(rect, text?, timeout?) highlights an area on screen and can show a short hint below it.

ctx.toast("Finished");
ctx.toast.highlight(element.rect, "Save button", 3);

console.log(), error(), warn(), info(), and debug() write to the script log. Use them to check progress and errors while you develop a script. Console methods keep their usual behavior and also appear in Cursor Crane’s script log.

Use setTimeout(callback, delay?, ...args) to run a callback once after at least the specified number of milliseconds. It returns an identifier that can be passed to clearTimeout(identifier) before the callback runs.

const timeout = setTimeout((message) => {
ctx.toast(message);
}, 500, "Still working");
// Cancel it when the delayed work is no longer needed.
clearTimeout(timeout);

Timers remain available while the script execution is active. A form keeps its execution active until the form is closed, so a timer can update or hide an open form. Closing the form cancels timers that have not run yet.

A form script returns an array from run(ctx). Every item needs a unique id:

function run(ctx) {
return [
{
id: "selector",
kind: "textInput",
title: "Selector",
value: "Button",
},
{
id: "run",
kind: "button",
title: "Query",
submit: true,
onClick: formCtx => {
formCtx.toast(`Searching for ${formCtx.formData.selector}`);
},
},
];
}

For an async form, Cursor Crane opens the form window immediately and shows a loading indicator until run(ctx) resolves. The initial form run(ctx) has no timeout, so it can wait for slow setup work before returning the items.

kindCommon fieldsBest for
labeltext, value?Showing instructions or results.
numberInputtitle, value?, min?, max?, step?Entering a number.
textInputtitle, value?, placeholder?Entering one line of text.
selecttitle, value?, optionsChoosing from a list.
checkboxtitle?, value?Checking an option.
toggletitle?, value?Turning an option on or off.
buttontitle, submit?, onClick?Performing an action.

Editable items can provide onChange(newValue, ctx); buttons use onClick(ctx). Both can be async functions.

A form ctx also provides:

MemberPurpose
formDataReads the current form values.
updateForm(items)Replaces the form. A matching item without a new value preserves the person’s current input.
hideForm()Hides a panel without ending its session. Invoking the same script again shows the existing form and preserves its state. A transient form closes instead.
closeForm()Closes the current form.
onFormActivate(callback)Registers a callback for when a hidden panel is shown again. The callback receives the current form context and can be async.

Use onFormActivate to refresh temporary state when a person returns to a hidden panel. It does not run for the first presentation, and it does not apply to transient forms because they close instead of being reused. Register it from run(ctx):

function run(ctx) {
ctx.onFormActivate(async formCtx => {
await refreshResults();
formCtx.updateForm(renderForm(formCtx));
});
return renderForm(ctx);
}

People can use Tab, Shift + Tab, and arrow keys to move through a form. When a button is focused, Enter or Space activates it. A button marked submit: true can be activated from anywhere with Command + Enter.

For a panel, Esc and the yellow title-bar button hide the form, while the red button and Command + W close it and end its session. Invoking a hidden panel script brings that same form back instead of creating another window. A transient form closes when it loses focus.

Use try / catch while developing a script to show a clear message:

try {
await ctx.mouse.moveTo(100, 100);
} catch (error) {
ctx.toast(String(error));
}

If a script calls an API without the required permission, uses an invalid selector, or cannot reach its target, that call fails. Add a toast or a form message for common failures so the script stays easy to use.