> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Device Definition API

> Define server-side devices and device triggers with defineDevice() and defineDeviceTrigger()

## Overview

The Device Definition API gives you two helpers for declaring how your agent talks to a physical or virtual device:

* **`defineDevice(config)`** — declares a device, its commands, and (optionally) its built-in triggers.
* **`defineDeviceTrigger(config)`** — declares a standalone trigger primitive (versioned, pushed independently).

```typescript theme={null}
import { defineDevice, defineDeviceTrigger } from 'lua-cli';
import { z } from 'zod';
```

<Note>
  For the device-side **client library** (the code that runs on the device itself — Node, MQTT, MicroPython), see [Device Client](/api/luadeviceclient) and the [Devices tab](/devices/overview).
</Note>

***

## `defineDevice(config)`

Declares a device. Devices have commands (agent → device) and optionally triggers (device → agent).

```typescript theme={null}
import { defineDevice } from 'lua-cli';
import { z } from 'zod';

export const labelPrinter = defineDevice({
  name: 'label-printer',
  description: 'Warehouse label printer (Zebra GK420d)',
  commands: {
    print: {
      description: 'Print a shipping label',
      schema: z.object({
        orderId: z.string(),
        labelData: z.object({
          address: z.string(),
          tracking: z.string(),
        }),
      }),
    },
    eject: {
      description: 'Eject the current label',
      schema: z.object({}),
    },
  },
  triggers: {
    paperLow: {
      description: 'Fires when paper drops below threshold',
      payloadSchema: z.object({ level: z.number() }),
      execute: async (payload, { agent, device }) => {
        await agent.chat(`Printer ${device.name} paper low: ${payload.level}%`);
      },
    },
  },
});
```

### Configuration — `LuaDeviceConfig`

<ParamField path="name" type="string" required>
  Unique device name. Used in `lua devices` and addressing from agent code.
</ParamField>

<ParamField path="description" type="string">
  Human-readable description shown in the admin dashboard.
</ParamField>

<ParamField path="group" type="string">
  Optional group label for organizing devices (e.g. `'printers'`, `'sensors'`). `lua devices list --group <name>` filters by this.
</ParamField>

<ParamField path="commands" type="Record<string, DeviceCommandConfig>" required>
  Map of command name → command config. Each command is a callable the agent can invoke on the device.
</ParamField>

<ParamField path="triggers" type="Record<string, DeviceTriggerConfig>">
  Map of trigger name → trigger config. Built-in triggers tied to this device. For triggers shared across devices, use [`defineDeviceTrigger`](#definedevicetrigger-config) instead.
</ParamField>

### Command Shape — `DeviceCommandConfig`

<ParamField path="description" type="string" required>
  What this command does. Used by the agent's LLM to decide when to invoke.
</ParamField>

<ParamField path="schema" type="ZodSchema" required>
  Zod schema for the command's input. Validated before the command leaves the agent.
</ParamField>

<ParamField path="timeout" type="number">
  Per-command timeout in milliseconds. Defaults to the device-wide timeout (`30000`).
</ParamField>

### Trigger Shape — `DeviceTriggerConfig`

<ParamField path="description" type="string" required>
  What this trigger represents. Used in trigger discovery and admin UI.
</ParamField>

<ParamField path="payloadSchema" type="ZodSchema" required>
  Zod schema for the trigger payload. Validated when the device fires the trigger.
</ParamField>

<ParamField path="execute" type="(payload, ctx) => Promise<void>" required>
  Handler invoked when the trigger fires. Receives the validated payload and a context with `agent` (for invoking the agent) and `device` (the device that fired).
</ParamField>

***

## `defineDeviceTrigger(config)`

Declares a **standalone** device trigger as a first-class primitive. Use this when a trigger isn't bound to a single device — for example, a trigger that any device in a group can fire, or a trigger that's pushed/versioned independently from its associated device.

```typescript theme={null}
import { defineDeviceTrigger } from 'lua-cli';
import { z } from 'zod';

export const paperLowAlert = defineDeviceTrigger({
  name: 'paper-low-alert',
  description: 'Generic paper-low handler for any printer',
  payloadSchema: z.object({
    deviceName: z.string(),
    level: z.number(),
  }),
  execute: async (payload, { agent }) => {
    await agent.chat(`Paper low on ${payload.deviceName}: ${payload.level}%`);
  },
});
```

Standalone triggers are pushed via `lua push device-trigger` (or as part of `lua push all`) and managed through the standard CLI surfaces.

### Configuration — `LuaDeviceTriggerConfig`

<ParamField path="name" type="string" required>
  Unique trigger name. Allowed characters: `a-z`, `0-9`, `_`, `-`. Must start with a letter.
</ParamField>

<ParamField path="description" type="string" required>
  What this trigger represents.
</ParamField>

<ParamField path="payloadSchema" type="ZodSchema" required>
  Zod schema for the trigger payload.
</ParamField>

<ParamField path="execute" type="(payload, ctx) => Promise<void>" required>
  Handler invoked when the trigger fires.
</ParamField>

***

## Wiring Up to an Agent

```typescript theme={null}
import { LuaAgent } from 'lua-cli';
import { labelPrinter } from './devices/label-printer';
import { paperLowAlert } from './triggers/paper-low-alert';

export const agent = new LuaAgent({
  name: 'warehouse-agent',
  devices: [labelPrinter],
  deviceTriggers: [paperLowAlert],
  // ...
});
```

## Invoking Commands from Tools

Inside a skill tool, address a device by name and call its command:

```typescript theme={null}
class PrintLabelTool extends LuaTool {
  name = 'printLabel';
  description = 'Print a shipping label';
  inputSchema = z.object({ orderId: z.string() });

  async execute({ orderId }, ctx) {
    const order = await Data.get('orders', orderId);
    await ctx.devices['label-printer'].commands.print({
      orderId,
      labelData: { address: order.shipping, tracking: order.tracking },
    });
    return { printed: true };
  }
}
```

## Local Testing

Use `lua devices test` and `lua devices test-trigger` to exercise commands and triggers without involving real hardware:

```bash theme={null}
lua devices test --device-name label-printer
lua devices test-trigger --device-name label-printer --payload '{"level":15}'
```

## Related

* [Devices Overview](/devices/overview)
* [Device Client](/api/luadeviceclient) — the device-side library
* [Devices Command](/cli/devices-command)
* [Self-Describing Commands](/devices/self-describing-commands)
* [Device Triggers](/devices/triggers)
