跳转到内容

脚本 API

本页按使用场景整理脚本 API。需要在编辑器中查看实时自动补全和完整签名时,请在脚本首行加入:

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

每个脚本都需要 metarun(ctx)

/** @type {CursorCrane.Meta} */
const meta = {
type: "action",
name: "Example",
permissions: [],
};
/** @param {CursorCrane.Context} ctx */
async function run(ctx) {
ctx.toast("Done");
}
meta 字段说明
type"action" 执行一次动作;"form" 打开表单。
presentationform 可选。"panel" 适合持续使用;"transient" 失焦后关闭。
name脚本在 Cursor Crane 中显示的名称。
permissions脚本需要的权限。

可用权限是 "accessibilityAPI""clipboard""keyboard""mouse""shell"。没有声明某项权限时,对应 API 不可使用。

ctx.applicationManager 用于查看运行中的应用:

成员用途
applications所有可用应用。
getApplicationByBundleIdentifier(id)按 bundle identifier 找应用。
getApplicationByPid(pid)按进程 ID 找应用。

每个 Application 都有 namebundleIdentifierpid

ctx.windowManager 用于寻找和切换窗口:

方法用途
getWindowsByPid(pid)取得某个应用的窗口。
getWindowsByBundleIdentifier(id)按应用查窗口。
getWindowById(windowId)按窗口 ID 查找。
getActiveWindow()当前正在使用的窗口。
getPreviousWindow()上一个使用过的窗口。
activateWindow(windowId)切换到指定窗口。

Window 包含 titlewindowId 和所属的 application

ctx.elementInspector.getElementByPid(pid) 取得一个应用的界面元素入口,需要 accessibilityAPI 权限。之后可以从应用的 getWindows() 找到窗口,再继续查找按钮、文本框或列表项。

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];
属性用途
title元素提供的标题。
displayTitle更适合展示给用户看的标题。
labeldescription元素的补充说明。
kind元素类型,例如 Button 或 TextField。
windowId元素所在窗口的 ID。
rect / frame元素在屏幕上的位置与尺寸。
方法用途
getChildren()取得直接子元素。
getVisibleChildren()取得当前可见的子元素。
getParent()取得父元素。
getFocusedWindow()取得应用当前聚焦的窗口。
getWindows()取得应用的窗口。
getWindow()取得元素所在窗口。
queryElements(selector, maxCount?)搜索窗口中的所有元素。会遍历 children;元素层级很大时,可能造成明显阻塞。
queryVisibleElement(selector, maxCount?)只搜索当前可见的内容。
matchesSelector(selector)验证这个已知元素是否符合完整选择器,包括父级与祖先条件。

选择器写法见元素选择器

getValue()setValue(value) 用于读取和设置元素的 AXValue 属性。要访问其他辅助功能属性,请使用 getAttributeValue(name)setAttributeValue(name, value)。值可以是文字、整数或小数。

const value = await element.getValue();
await element.setValue("Updated title");
const title = await element.getAttributeValue("AXTitle");
await element.setAttributeValue("AXValue", 50);

这四个方法都需要 accessibilityAPI 权限。属性不可用、值类型不匹配,或 macOS 拒绝操作时都会抛出错误;需要向用户说明失败原因时,请用 try / catch 处理。

以下方法需要 accessibilityAPI 权限,并且可以 await

方法用途
performPress()触发元素的主要操作。
performClick()移动到元素并点击。
performMoveCursorTo()移动到元素。
performShowMenu()打开元素的菜单。
getActions()查看元素支持的操作。
performAction(identifier)触发指定操作。

ctx.elementInspector.triggerMenuItem(menuPath) 会触发当前应用中的菜单项。传入包含完整菜单层级标题的数组:

await ctx.elementInspector.triggerMenuItem([
"Navigate",
"Move Focus to Next Editor Pane",
]);

它需要 accessibilityAPI 权限。Cursor Crane 会按菜单层级寻找目标,并且只会对最终菜单项运行 AXPress

需要 keyboard 权限。三个方法都返回 Promise:

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

修饰键可使用 "shift""control""option""command"

需要 mouse 权限:

方法用途
moveTo(x, y)平滑移动指针。
warpTo(x, y)立即移动指针。
click(button, modifiers?)点击;0 为左键、1 为右键、2 为中键。
doubleClick(button, modifiers?)双击。
rightClick(modifiers?)右键点击。

需要 shell 权限。execute(command) 会运行一条非交互式 zsh 命令,并返回其输出:

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

返回值包含 stdoutstderrexitCode。命令与脚本共用执行超时,每一路输出最多保留 1 MiB。

需要 clipboard 权限。可以读取或替换系统剪贴板中的纯文本:

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

剪贴板中没有纯文本时,readText() 返回 nullwriteText(value) 会用指定文字替换剪贴板当前的内容。

ctx.toast(message) 用于简短反馈。ctx.toast.highlight(rect, text?, timeout?) 会高亮屏幕上的一个范围,并在下方显示可选提示文字。

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

console.log()error()warn()info()debug() 会写入脚本日志,适合在开发脚本时查看过程与错误。Console 方法会保持原有行为,同时显示在 Cursor Crane 的脚本日志中。

使用 setTimeout(callback, delay?, ...args) 可以在至少等待指定毫秒数后执行一次回调。它会返回一个标识符;回调执行前,可以将该标识符传给 clearTimeout(identifier) 以取消定时器。

const timeout = setTimeout((message) => {
ctx.toast(message);
}, 500, "Still working");
// 不再需要延迟操作时取消它。
clearTimeout(timeout);

定时器只会在脚本仍处于执行状态时运行。表单关闭前会维持脚本执行,因此定时器可以更新或隐藏已打开的表单;关闭表单会取消尚未执行的定时器。

form 脚本从 run(ctx) 返回一个数组。每项都需要唯一的 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}`);
},
},
];
}

Cursor Crane 会在 run(ctx) 开始前创建并显示 form。对于 async form,在 run(ctx) 完成前会显示加载指示器。form 初始的 run(ctx) 没有超时限制,因此可以等待较慢的初始化工作再返回表单项;run(ctx) 也可以立即更新、隐藏或关闭已显示的 form。

kind常用字段适合什么
labeltextvalue?展示说明或结果。
numberInputtitlevalue?min?max?step?输入数字。
textInputtitlevalue?placeholder?输入单行文字。
selecttitlevalue?options从列表中选择。
checkboxtitle?value?勾选一个选项。
toggletitle?value?开关一个选项。
buttontitlesubmit?onClick?执行操作。

可编辑项可以提供 onChange(newValue, ctx);按钮使用 onClick(ctx)。两者都可以是 async 函数。

form 的 ctx 还提供:

成员用途
formData读取当前表单的值。
updateForm(items)用新的表单内容替换当前表单。未提供 value 的同名项目会保留用户当前输入。
hideForm()隐藏 panel,但不结束其会话。再次触发同一个脚本时会显示原来的表单并保留状态;用于 transient 时则会关闭表单。
closeForm()关闭当前表单。
onFormActivate(callback)注册一个在已隐藏的 panel 再次显示时触发的回调。回调会收到当前 form context,也可以是 async

当用户回到已隐藏的 panel 时,可以用 onFormActivate 刷新临时 state。它不会在首次显示时触发;transient 会直接关闭而非复用,因此不适用。请在 run(ctx) 中注册:

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

用户可以使用 TabShift + Tab 和方向键在表单中移动焦点。聚焦按钮后按 EnterSpace 会触发它;标记为 submit: true 的按钮可用 Command + Enter 从任意位置触发。

对于 panelEsc 和标题栏的黄色按钮会隐藏表单;红色按钮与 Command + W 会关闭表单并结束会话。再次触发一个已隐藏的 panel 脚本时,会重新显示同一个表单,而不是创建新窗口。transient 表单则会在失去焦点时关闭。

在开发脚本时,可以用 try / catch 显示清楚的提示:

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

如果调用了没有声明权限的 API、选择器写错,或目标暂时不可用,Cursor Crane 会让这次调用失败。给常见失败情况加上 toast 或表单中的说明,脚本会更容易使用。