> ## 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.

# Node.js Client

> Complete reference for the @lua-ai-global/device-client Node.js package

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @lua-ai-global/device-client
  ```

  ```bash yarn theme={null}
  yarn add @lua-ai-global/device-client
  ```

  ```bash pnpm theme={null}
  pnpm add @lua-ai-global/device-client
  ```
</CodeGroup>

## DeviceClientConfig

The configuration object passed to `new DeviceClient()`.

<ParamField body="agentId" type="string" required>
  Agent ID to connect to. Found in `.lua/lua.config.yaml` or the Lua dashboard.
</ParamField>

<ParamField body="apiKey" type="string" required>
  API key for authentication. Generate with `lua auth configure`.
</ParamField>

<ParamField body="deviceName" type="string" required>
  Unique name for this device. Lowercase with hyphens (e.g., `label-printer`, `pico-sensor-01`).
</ParamField>

<ParamField body="commands" type="DeviceCommandDefinition[]">
  Array of commands this device supports. Sent to the server at connect time so the agent can use them as tools. See [Self-Describing Commands](/devices/self-describing-commands).
</ParamField>

<ParamField body="transport" type="'socketio' | 'mqtt'" default="socketio">
  Transport protocol. Use `'mqtt'` for constrained devices or environments where MQTT is preferred.
</ParamField>

<ParamField body="serverUrl" type="string" default="https://api.heylua.ai">
  Server URL for Socket.IO transport.
</ParamField>

<ParamField body="mqttUrl" type="string" default="wss://mqtt.heylua.ai/mqtt">
  MQTT broker URL. Required when `transport` is `'mqtt'`.
</ParamField>

<ParamField body="cdnUrl" type="string" default="https://cdn.heylua.ai">
  CDN URL for file uploads and downloads.
</ParamField>

<ParamField body="group" type="string">
  Optional device group name for fan-out commands (e.g., `'printers'`, `'sensors-floor-2'`).
</ParamField>

## DeviceCommandDefinition

Each entry in the `commands` array describes one command the device supports.

<ParamField body="name" type="string" required>
  Command name. Used by the agent to invoke the command (e.g., `read_temperature`).
</ParamField>

<ParamField body="description" type="string" required>
  Human-readable description. Shown to the AI agent as the tool description. Write it as if explaining to a person what the command does.
</ParamField>

<ParamField body="inputSchema" type="Record<string, any>">
  JSON Schema for command input parameters. The agent uses this to know what arguments to pass.
</ParamField>

<ParamField body="timeoutMs" type="number" default="30000">
  Command timeout in milliseconds. If the device does not respond within this time, the command fails.
</ParamField>

<ParamField body="retry" type="{ maxAttempts: number; backoffMs: number }">
  Retry configuration for failed commands. The gateway retries with exponential backoff.
</ParamField>

## Connection Lifecycle

```typescript theme={null}
import { DeviceClient } from '@lua-ai-global/device-client';

const device = new DeviceClient({ /* config */ });

// Connect -- resolves when authenticated
await device.connect();

// Check connection status
console.log(device.isConnected()); // true

// Listen for lifecycle events
device.on('connected', () => console.log('Connected'));
device.on('reconnected', () => console.log('Reconnected'));
device.on('disconnected', (reason) => console.log('Disconnected:', reason));
device.on('error', (err) => console.error('Error:', err));

// Graceful shutdown (stops auto-reconnect)
await device.disconnect();
```

<Note>
  The client automatically reconnects on network failures with jittered exponential backoff (1s to 30s). Call `disconnect()` to stop reconnection. The client also registers `SIGTERM` and `SIGINT` handlers for graceful shutdown.
</Note>

## Handling Commands

Register handlers for commands the agent can send to this device:

```typescript theme={null}
device.onCommand('read_temperature', async (payload) => {
  // payload contains whatever the agent sent (validated against inputSchema)
  const reading = await sensor.read();
  return {
    temperature: reading.celsius,
    humidity: reading.humidity,
    timestamp: new Date().toISOString(),
  };
});

device.onCommand('set_led', async (payload) => {
  const { color, brightness } = payload;
  await led.setColor(color);
  await led.setBrightness(brightness);
  return { success: true, color, brightness };
});
```

If a handler throws an error, the error message is returned to the agent as a failed command result. The agent sees the error and can decide how to respond to the user.

## Firing Triggers

Send events from the device to the agent:

```typescript theme={null}
// Fire a trigger -- resolves when the server acknowledges receipt
await device.trigger('temperature_alert', {
  temperature: 42.1,
  threshold: 40,
  sensor: 'main-floor',
});

// Optionally listen for trigger execution results
device.onTriggerResult('temperature_alert', (result) => {
  console.log('Agent handled the alert:', result);
});
```

See [Triggers](/devices/triggers) for the full guide on how to handle triggers on the agent side.

## CDN Uploads

Every `DeviceClient` instance includes a `cdn` property for uploading and downloading files:

```typescript theme={null}
import fs from 'fs';

// Upload a screenshot
const screenshot = fs.readFileSync('/tmp/screenshot.png');
const result = await device.cdn.upload(screenshot, 'screenshot.png', 'image/png');
console.log(result.url); // https://cdn.heylua.ai/{fileId}

// Get the URL for a previously uploaded file
const url = device.cdn.getUrl(result.fileId);

// Download a file
const buffer = await device.cdn.download(result.fileId);
fs.writeFileSync('/tmp/downloaded.png', buffer);
```

See [CDN Uploads](/devices/cdn-uploads) for more details.

## Transport Configuration

<Tabs>
  <Tab title="Socket.IO (default)">
    ```typescript theme={null}
    const device = new DeviceClient({
      agentId: 'your-agent-id',
      apiKey: 'your-api-key',
      deviceName: 'my-device',
      // transport defaults to 'socketio'
      // serverUrl defaults to 'https://api.heylua.ai'
      commands: [
        { name: 'ping', description: 'Health check' },
      ],
    });
    ```

    Socket.IO is best for Node.js applications running on desktops, servers, or single-board computers with plenty of memory.
  </Tab>

  <Tab title="MQTT">
    ```typescript theme={null}
    const device = new DeviceClient({
      agentId: 'your-agent-id',
      apiKey: 'your-api-key',
      deviceName: 'my-device',
      transport: 'mqtt',
      // mqttUrl defaults to 'wss://mqtt.heylua.ai/mqtt'
      commands: [
        { name: 'ping', description: 'Health check' },
      ],
    });
    ```

    MQTT is best for constrained devices, battery-powered sensors, or environments with flaky network connectivity. MQTT supports offline message queueing with persistent sessions.
  </Tab>
</Tabs>

## Complete Example

A device that simulates a smart thermostat with temperature reading, target temperature setting, and a high-temperature alert trigger:

```typescript theme={null}
import { DeviceClient } from '@lua-ai-global/device-client';

let currentTemp = 21.0;
let targetTemp = 22.0;
let heatingOn = false;

const device = new DeviceClient({
  agentId: process.env.LUA_AGENT_ID!,
  apiKey: process.env.LUA_API_KEY!,
  deviceName: 'smart-thermostat',
  commands: [
    {
      name: 'read_temperature',
      description: 'Read the current room temperature in celsius',
    },
    {
      name: 'set_target',
      description: 'Set the target temperature for the thermostat',
      inputSchema: {
        type: 'object',
        properties: {
          temperature: { type: 'number', minimum: 10, maximum: 35 },
        },
        required: ['temperature'],
      },
    },
    {
      name: 'get_status',
      description: 'Get full thermostat status including heating state',
    },
  ],
});

device.onCommand('read_temperature', async () => {
  return { temperature: currentTemp, unit: 'celsius' };
});

device.onCommand('set_target', async (payload) => {
  targetTemp = payload.temperature;
  heatingOn = currentTemp < targetTemp;
  return { targetTemperature: targetTemp, heatingOn };
});

device.onCommand('get_status', async () => {
  return {
    currentTemperature: currentTemp,
    targetTemperature: targetTemp,
    heatingOn,
    timestamp: new Date().toISOString(),
  };
});

async function main() {
  device.on('connected', () => console.log('Thermostat online'));
  device.on('disconnected', (reason) => console.log('Offline:', reason));

  await device.connect();

  // Simulate temperature changes and fire trigger on high temp
  setInterval(async () => {
    currentTemp += (Math.random() - 0.4) * 0.5;
    currentTemp = Math.round(currentTemp * 10) / 10;

    if (currentTemp > 35) {
      await device.trigger('high_temperature', {
        temperature: currentTemp,
        threshold: 35,
      });
    }
  }, 5000);
}

main().catch(console.error);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="MQTT Transport" icon="tower-broadcast" href="/devices/mqtt-client">
    Deep dive into MQTT configuration and topic structure
  </Card>

  <Card title="CDN Uploads" icon="cloud-arrow-up" href="/devices/cdn-uploads">
    Upload and download files from your device
  </Card>

  <Card title="Triggers" icon="bolt" href="/devices/triggers">
    Send events from your device to the agent
  </Card>

  <Card title="API Reference" icon="book" href="/api/luadeviceclient">
    Complete class and method documentation
  </Card>
</CardGroup>
