Skate.registerTool({
name: 'change_plan', // 2-40 characters: a-z, 0-9, _
description: 'Move the current workspace to another plan.',
parameters: { // up to 12
plan: { type: 'string', enum: ['free', 'pro', 'team'], description: 'The plan to move to' },
seats: { type: 'integer', required: false },
},
changes: true, // it writes: the person approves each call
run: async ({ plan, seats }) => {
const r = await api.changePlan(plan, seats); // your own code, with your own auth
return r.ok ? { ok: true, result: `Plan is now ${plan}` } : { ok: false, result: r.error };
},
});
Skate.registerTool({
name: 'invoice_total',
description: 'Read the total of an invoice by its number.',
parameters: { number: { type: 'string' } },
changes: false, // a read: runs without asking
run: ({ number }) => ({ ok: true, result: `${number} total is ${totals[number]}` }),
});
Skate.unregisterTool('change_plan'); // e.g. on a page where it does not apply
Skate.tools(); // what is registered nowIf your code runs before the widget loads, queue the call:
window.SkateQueue = window.SkateQueue || [];
SkateQueue.push(['registerTool', { name: 'change_plan', /* … */ }]);registerTool returns false and logs the reason in the console when a registration is not valid.
Parameters
Each parameter has a type: string, number, integer or boolean. Add enum for a fixed list of values, description to help Skate choose, and required: false for an optional one. Skate asks the person for a required value the request does not give. It never invents one.
What run returns
Return { ok, result }. ok is whether it worked; result is a short text (or any JSON) that Skate reads. Anything else you return is read as ok: true with its JSON. A run that throws, or takes longer than 20 seconds, is a failed call.
The rules Skate keeps
- Approval. A tool with
changes: trueis never run without the person pressing Approve on a card that shows your description and the exact arguments. Declining runs nothing. Guide mode (“show me how”) never runs one. - Checked arguments. Skate only calls a registered tool, with the parameters you declared and values of the right type and enum. Anything else is refused before
runis called. - Once. The same write with the same arguments runs at most once per request.
- What it proves. The result is shown to Skate as a row from your app. A read (
changes: false) can back an answer, never “Done”. A failed call (ok: false) is never treated as done: Skate says it did not work. - The site owner’s “never do” list applies to tools too.
- Nothing leaves the page but the spec.
runstays in the browser. Skate sends the name, description and parameters to plan with. The conversation log keeps which tools ran and whether they worked, never their arguments.
“Checked on the page” or “Confirmed by your app”
A successful write is your app’s own claim, not something Skate saw. So it is labelled as that, everywhere:
- After the call, Skate still reads the page. If the page shows the result (the new value, the updated row), the task is Done, checked on the page, like any other.
- If the page does not show it, the task is Done, confirmed by your app. The visitor reads “Done, confirmed by your app. I could not see it on the page.” The conversation records
verification: "app"and the end statedone_by_app. done_by_appcounts as done and as resolved in every number (Overview, Support, value, weekly report, MCP), and is always shown on its own line, never added silently to “checked on the page”.
To get “checked on the page”, make the result visible: update the page after run (or return and render the new state), and Skate will see it.