Core API
Commands
Define typed commands for AI agents
Commands#
Commands are the core building block of a Surf API. Each command has a name, description, optional parameters, and a handler function.
const surf = await createSurf({ name: 'My Store', commands: { search: { description: 'Search products by keyword', params: { query: { type: 'string', required: true, description: 'Search term' }, limit: { type: 'number', default: 10 }, }, returns: { type: 'object' }, tags: ['catalog', 'public'], hints: { idempotent: true, sideEffects: false, estimatedMs: 50, }, run: async ({ query, limit }) => { return db.products.search(query, { limit }) }, }, },})๐ก Tip: The
hintsobject helps AI agents make smart decisions โ for example, knowing a command is idempotent means it's safe to retry on failure.
Execution Hints#
The hints.execution field tells agents where a command runs. This is critical for window.surf โ it determines whether a command executes locally in the browser or goes to the server.
| Value | window.surf | HTTP / CLI | Description |
|-------|---------------|------------|-------------|
| "any" (default) | โ
Local handler or server | โ
Server handler | Works everywhere |
| "browser" | โ
Local handler only | โ Returns error | UI-only, modifies live browser state |
| "server" | โ
Proxied to server | โ
Server handler | Needs auth, DB, business logic |
execution: "browser" โ Declaration-only commands
Commands with execution: "browser" don't need a run() handler on the server. They're declaration-only in the server config โ listed in the manifest for discovery, but the actual handler lives client-side via useSurfCommands (React) or registerCommand (@surfjs/web).
const surf = await createSurf({ commands: { 'canvas.addCircle': { description: 'Add circle to canvas', params: { x: { type: 'number', required: true }, y: { type: 'number', required: true }, radius: { type: 'number', default: 50 }, fill: { type: 'string', default: '#000' }, }, hints: { execution: 'browser', sideEffects: true }, // No run() โ browser-only command // The handler is registered client-side },ย 'order.create': { description: 'Create order', params: { sku: { type: 'string', required: true }, quantity: { type: 'number', default: 1 }, }, hints: { execution: 'server', sideEffects: true }, run: async (params, ctx) => { const order = await db.orders.create(ctx.claims!.userId, params) return order }, },ย 'product.search': { description: 'Search products', params: { query: { type: 'string', required: true }, limit: { type: 'number', default: 10 }, }, hints: { execution: 'any', idempotent: true }, run: async ({ query, limit }) => { return db.products.search(query, { limit }) }, }, },})When a headless agent or CLI tries to call a browser-only command, they get a clear error:
$ surf test myapp.com canvas.addCircle --x 400โ ๏ธ canvas.addCircle requires browser execution Open the site and run: await window.surf.execute('canvas.addCircle', { x: 400 })See Architecture & Execution Models for the full breakdown of execution modes and command strategies.