Core API
Namespacing
Organize commands with dot notation and deep nesting
Namespacing#
Organize related commands using dot notation. Nest command groups to create clean, discoverable APIs.
const surf = await createSurf({ name: 'My Store', commands: { // Top-level command search: { description: 'Search products', params: { query: { type: 'string', required: true } }, run: async ({ query }) => db.search(query), },ย // Nested namespace using groups cart: { add: { description: 'Add item to cart', params: { sku: { type: 'string', required: true } }, auth: 'required', run: async ({ sku }, ctx) => cart.add(ctx.sessionId, sku), }, remove: { description: 'Remove item from cart', params: { sku: { type: 'string', required: true } }, auth: 'required', run: async ({ sku }, ctx) => cart.remove(ctx.sessionId, sku), }, view: { description: 'View cart contents', auth: 'required', run: async (_, ctx) => cart.get(ctx.sessionId), }, },ย // Deep nesting user: { profile: { get: { description: 'Get user profile', auth: 'required', run: async (_, ctx) => users.getProfile(ctx.claims?.userId), }, }, }, },})This creates commands accessible as search, cart.add, cart.remove, cart.view, and user.profile.get.
Deep Nesting
Surf supports arbitrary nesting depth โ for namespaces, object parameters, and return types. This lets you model complex, real-world data structures while keeping everything typed and validated.
Nested Namespaces
Command groups can nest multiple levels deep. Each level creates a dot-separated namespace in the manifest.
const surf = await createSurf({ name: 'My Store', commands: { cart: { items: { add: { description: 'Add an item to the cart', params: { sku: { type: 'string', required: true } }, auth: 'required', run: async ({ sku }, ctx) => cart.addItem(ctx.sessionId!, sku), }, remove: { description: 'Remove an item from the cart', params: { sku: { type: 'string', required: true } }, auth: 'required', run: async ({ sku }, ctx) => cart.removeItem(ctx.sessionId!, sku), }, list: { description: 'List all items in the cart', auth: 'required', run: async (_, ctx) => cart.listItems(ctx.sessionId!), }, }, checkout: { description: 'Checkout the current cart', auth: 'required', run: async (_, ctx) => cart.checkout(ctx.sessionId!), }, }, user: { profile: { get: { description: 'Get user profile', auth: 'required', run: async (_, ctx) => users.getProfile(ctx.claims?.userId), }, update: { description: 'Update user profile', auth: 'required', params: { name: { type: 'string' }, bio: { type: 'string' }, }, run: async (params, ctx) => users.updateProfile(ctx.claims?.userId, params), }, }, }, },})This produces commands: cart.items.add, cart.items.remove, cart.items.list, cart.checkout, user.profile.get, and user.profile.update.
Nested Object Parameters
Use type: 'object' with properties to define nested parameter structures. Nesting can go as deep as needed.
{ params: { address: { type: 'object', description: 'Shipping address', properties: { street: { type: 'string', required: true }, city: { type: 'string', required: true }, zip: { type: 'string' }, country: { type: 'string', default: 'US' }, coordinates: { type: 'object', properties: { lat: { type: 'number', required: true }, lng: { type: 'number', required: true }, }, }, }, }, },}Nested Array Parameters
Arrays use items to define element schemas โ including nested objects and arrays of arrays.
{ params: { // Array of strings tags: { type: 'array', items: { type: 'string' }, },ย // Array of objects lineItems: { type: 'array', items: { type: 'object', properties: { sku: { type: 'string', required: true }, quantity: { type: 'number', default: 1 }, options: { type: 'array', items: { type: 'string' }, }, }, }, }, },}Reusable Types with $ref
Define complex types once in types and reference them with $ref anywhere โ in params, return schemas, or array items. This avoids duplication and keeps your manifest clean.
const surf = await createSurf({ name: 'My Store', types: { Address: { type: 'object', description: 'A postal address', properties: { street: { type: 'string', required: true }, city: { type: 'string', required: true }, zip: { type: 'string' }, country: { type: 'string' }, }, }, LineItem: { type: 'object', description: 'A single item in an order', properties: { sku: { type: 'string', required: true }, quantity: { type: 'number', default: 1 }, price: { type: 'number', required: true }, }, }, }, commands: { 'order.create': { description: 'Place a new order', auth: 'required', params: { shippingAddress: { $ref: 'Address' }, billingAddress: { $ref: 'Address' }, items: { type: 'array', items: { $ref: 'LineItem' } }, notes: { type: 'string' }, }, returns: { type: 'object', properties: { orderId: { type: 'string' }, total: { type: 'number' }, items: { type: 'array', items: { $ref: 'LineItem' } }, }, }, run: async (params, ctx) => { return orders.create(ctx.claims!.userId, params) }, }, },})๐ก Tip: Types referenced by
$refare included in the manifest under thetypeskey, making them visible to agents for schema understanding.