Skip to main content

Overview

The User API provides access to the current user’s profile information through a powerful UserDataInstance class with direct property access.
import { User } from 'lua-cli';

// Get user instance
const user = await User.get();

// Access the new read-only user profile
console.log(user._luaProfile.fullName);
console.log(user._luaProfile.emailAddresses); // A simple array of strings

// Continue to use the user object for your agent's custom data
user.lastProductViewed = 'SKU-123';
await user.save();

Read-Only User Profile (_luaProfile)

A new, read-only property user._luaProfile is now available on the user object. This provides a secure and reliable way to access core user identity information.
  • _luaProfile (Read-Only): Contains essential user data like userId, fullName, mobileNumbers, and emailAddresses.
    • The _lua prefix indicates this is a special, system-provided property.
    • This data is immutable; any attempts to change it will be silently ignored.
  • user.* (Mutable): Continue to use the main user object to store and manage any custom data your agent needs, such as preferences, shopping carts, or game scores.

Core User Identity

Access read-only data like user._luaProfile.userId and user._luaProfile.fullName.

Custom Agent Data

Read and write your agent’s data directly, like user.cart or user.preferences.

Deprecated user.userId

To centralize core user information, user.userId is now deprecated. Please update your code to use user._luaProfile.userId. The old property will be removed in a future version.
// ✅ New & Recommended
const userId = user._luaProfile.userId;

// ⚠️ Deprecated
const oldUserId = user.userId;

Features

Direct Access

Access properties with user.name instead of user.data.name

Auto Sanitization

Removes sensitive fields automatically

Built-in Methods

update(), save(), send(), and clear() included

Messaging

Send text, images, and files to users

get(userId?)

Retrieve user data as a UserDataInstance.
User.get(userId?: string): Promise<UserDataInstance>
userId
string
User ID to retrieve a specific userRequired in: Webhooks, pre-defined LuaJob (no conversational context)Optional in: Tools, dynamic jobs (has conversational context)If not provided, retrieves the current user from the conversation context
Returns: UserDataInstance with proxy-based property access

When to Use userId Parameter

Understanding when userId is required vs optional:
ContextMethoduserId Required?Why
ToolsUser.get()❌ OptionalHas conversational context
WebhooksUser.get(userId)REQUIREDNo conversational context
LuaJobUser.get(userId)REQUIREDNo conversational context
Jobs APIjobInstance.user()❌ N/AAutomatic user context
Context Matters:
  • Tools: userId is optional - defaults to current user in conversation
  • Webhooks: userId is REQUIRED - webhooks execute outside conversations
  • LuaJob (pre-defined): userId is REQUIRED - jobs execute outside conversations
  • Jobs API (dynamic): Use jobInstance.user() instead - automatic context captured!
Examples:
Get the current user from conversation context:
import { User } from 'lua-cli';

// In your tool
async execute(input: any) {
  const user = await User.get();
  
  // Direct property access
  console.log(user.name);    // "John Doe"
  console.log(user.email);   // "[email protected]"
  console.log(user.phone);   // "555-0123"
  
  return { userName: user.name };
}

UserDataInstance API

Property Access (Direct)

Access any user property directly:
// Reading properties
const name = user.name;
const email = user.email;
const preferences = user.preferences;
const customField = user.customField;

// Setting properties (local only - call update() to persist)
user.name = "Jane Doe";
user.email = "[email protected]";
user.preferences = { theme: "dark" };

// Checking existence
if ('name' in user) {
  console.log('User has a name');
}

update()

Update user data on the server and locally.
user.update(data: Record<string, any>): Promise<any>
data
object
required
Object containing fields to update or add
Returns: Promise resolving to updated sanitized user data Examples:
// Update single field
await user.update({ name: "John Doe" });

// Update multiple fields
await user.update({
  name: "John Doe",
  email: "[email protected]",
  phone: "555-1234"
});

// Update nested objects
await user.update({
  preferences: {
    theme: "dark",
    notifications: true,
    language: "en"
  }
});

// Access updated data immediately
console.log(user.name);  // "John Doe"
console.log(user.preferences.theme);  // "dark"

save()

Save the current state of user data to the server. This is a convenience method that persists all changes made to the user instance.
user.save(): Promise<boolean>
Returns: Promise resolving to true if successful Examples:
const user = await User.get();

// Modify properties
user.name = "John Doe";
user.email = "[email protected]";
user.phone = "555-1234";

// Save all changes at once
await user.save();

// Much cleaner than multiple update calls!
New in Latest Version: The save() method provides a simpler workflow - modify properties then save, rather than passing data to update().

send()

Send messages to the user conversation. Supports text, images, and file attachments.
user.send(messages: Message[]): Promise<any>
messages
Message[]
required
Array of messages to send (text, image, or file)
Message Types:
// Text message
type TextMessage = {
  type: "text";
  text: string;
};

// Image message
type ImageMessage = {
  type: "image";
  image: string;      // Base64 encoded image data
  mediaType: string;   // e.g., "image/png", "image/jpeg"
};

// File message
type FileMessage = {
  type: "file";
  data: string;       // Base64 encoded file data
  mediaType: string;   // e.g., "application/pdf"
};
Examples:
const user = await User.get();

// Send a text message
await user.send([
  { type: "text", text: "Your order has been shipped!" }
]);

// Send multiple messages
await user.send([
  { type: "text", text: "Here is your receipt:" },
  { 
    type: "image", 
    image: base64ImageData, 
    mediaType: "image/png" 
  }
]);

// Send a file
await user.send([
  { type: "text", text: "Your invoice is attached:" },
  {
    type: "file",
    data: base64PdfData,
    mediaType: "application/pdf"
  }
]);
New in Latest Version: Send proactive messages to users for notifications, updates, and automated workflows.

clear()

Clear all user data for the current user.
user.clear(): Promise<boolean>
Returns: Promise resolving to true if successful Example:
try {
  const success = await user.clear();
  if (success) {
    console.log('User data cleared successfully');
  }
} catch (error) {
  console.error('Failed to clear user data:', error);
}
Destructive operation! This removes all user data. Use with caution.

Data Sanitization

UserDataInstance automatically removes sensitive fields: Removed (not accessible):
  • agentId
  • userId
Available (accessible):
  • name, email, phone
  • Custom fields you set
  • Preferences and settings
  • Any other data
// Server returns:
{
  userId: "12345",       // ❌ Removed
  agentId: "agent-123",  // ❌ Removed
  name: "John Doe",      // ✅ Accessible
  email: "[email protected]"  // ✅ Accessible
}

// Your user instance has:
{
  name: "John Doe",
  email: "[email protected]"
}

Complete Examples

Example 1: User Preferences

import { LuaTool } from 'lua-cli';
import { z } from 'zod';

export class ManagePreferencesTool implements LuaTool {
  name = "manage_preferences";
  description = "Manage user preferences";
  
  inputSchema = z.object({
    theme: z.enum(['light', 'dark']).optional(),
    notifications: z.boolean().optional(),
    language: z.string().optional()
  });

  async execute(input: any, context: ToolContext) {
    const user = context.user;
    
    // Direct property access
    const currentPrefs = user.preferences || {};
    
    // Merge with new preferences
    const newPrefs = {
      ...currentPrefs,
      ...input
    };
    
    // Update on server
    await user.update({ preferences: newPrefs });
    
    return {
      message: 'Preferences updated successfully',
      preferences: user.preferences  // Direct access to updated data
    };
  }
}

Example 2: Shopping Cart Persistence

export class CartTool implements LuaTool {
  name = "manage_cart";
  description = "Manage shopping cart";
  
  inputSchema = z.object({
    action: z.enum(['add', 'remove', 'get']),
    productId: z.string().optional(),
    quantity: z.number().optional()
  });

  async execute(input: any, context: ToolContext) {
    const user = context.user;
    
    // Get current cart (direct access)
    let cart = user.cart || [];
    
    if (input.action === 'add') {
      cart.push({
        productId: input.productId,
        quantity: input.quantity
      });
      await user.update({ cart });
    }
    
    if (input.action === 'remove') {
      cart = cart.filter(item => item.productId !== input.productId);
      await user.update({ cart });
    }
    
    return {
      cart: user.cart,  // Direct access
      itemCount: cart.length
    };
  }
}

Example 3: User Profile Management

export class ProfileTool implements LuaTool {
  name = "update_profile";
  description = "Update user profile information";
  
  inputSchema = z.object({
    firstName: z.string().optional(),
    lastName: z.string().optional(),
    phone: z.string().optional(),
    bio: z.string().optional()
  });

  async execute(input: any, context: ToolContext) {
    const user = context.user;
    
    // Update user data
    await user.update(input);
    
    // Return updated profile (direct access)
    return {
      success: true,
      profile: {
        firstName: user.firstName,
        lastName: user.lastName,
        phone: user.phone,
        bio: user.bio
      },
      message: 'Profile updated successfully'
    };
  }
}

Example 4: Personalized Greeting

export class GreetUserTool implements LuaTool {
  name = "greet_user";
  description = "Greet user with personalized message";
  
  inputSchema = z.object({});

  async execute(input: any, context: ToolContext) {
    const user = context.user;
    
    // Direct property access
    const hour = new Date().getHours();
    const timeGreeting = hour < 12 ? 'Good morning'
      : hour < 18 ? 'Good afternoon'
      : 'Good evening';
    
    return {
      message: `${timeGreeting}, ${user.name}! How can I help you today?`
    };
  }
}

Example 5: Order Notification with Messaging

export class SendOrderNotificationTool implements LuaTool {
  name = "send_order_notification";
  description = "Send order status notification to user";
  
  inputSchema = z.object({
    orderId: z.string(),
    status: z.enum(['shipped', 'delivered']),
    trackingNumber: z.string().optional(),
    receiptUrl: z.string().optional()
  });

  async execute(input: z.infer<typeof this.inputSchema>, context: ToolContext) {
    const user = context.user;
    
    // Build notification message
    const messages = [
      {
        type: "text" as const,
        text: `Hi ${user.name}! Your order #${input.orderId} has been ${input.status}.`
      }
    ];
    
    // Add tracking info if shipped
    if (input.status === 'shipped' && input.trackingNumber) {
      messages.push({
        type: "text" as const,
        text: `Tracking number: ${input.trackingNumber}`
      });
    }
    
    // Send messages to user
    await user.send(messages);
    
    // Update user's notification preferences
    user.lastNotificationSent = new Date().toISOString();
    await user.save();
    
    return {
      success: true,
      message: "Notification sent successfully"
    };
  }
}

Example 6: Send Receipt with Image

export class SendReceiptTool implements LuaTool {
  name = "send_receipt";
  description = "Send order receipt with QR code";
  
  inputSchema = z.object({
    orderId: z.string(),
    amount: z.number(),
    qrCodeBase64: z.string()
  });

  async execute(input: z.infer<typeof this.inputSchema>, context: ToolContext) {
    const user = context.user;
    
    // Send receipt with QR code
    await user.send([
      {
        type: "text",
        text: `Thank you for your order! Total: $${input.amount.toFixed(2)}`
      },
      {
        type: "text",
        text: "Here's your receipt QR code for easy access:"
      },
      {
        type: "image",
        image: input.qrCodeBase64,
        mediaType: "image/png"
      }
    ]);
    
    return {
      success: true,
      message: "Receipt sent to user"
    };
  }
}

Best Practices

// ✅ Recommended
const name = user.name;
const email = user.email;

// 🟡 Still works but verbose
const name = user.data.name;
const email = user.data.email;
Combine multiple updates into one call:
// ✅ Good - Single update call
await user.update({
  name: "John Doe",
  email: "[email protected]",
  phone: "555-1234"
});

// ❌ Bad - Multiple update calls
await user.update({ name: "John Doe" });
await user.update({ email: "[email protected]" });
await user.update({ phone: "555-1234" });
Not all fields may be present:
const phone = user.phone || 'Not provided';
const name = user.name || 'Guest';
const preferences = user.preferences || {};
// Personalized responses
return {
  message: `Welcome back, ${user.name}!`,
  savedItems: user.cart?.length || 0,
  lastVisit: user.lastSeen
};
The new save() method is perfect for multiple changes:
// ✅ Recommended - Modify then save
user.name = "John Doe";
user.email = "[email protected]";
user.preferences = { theme: "dark" };
await user.save();

// 🟡 Still works - Update method
await user.update({
  name: "John Doe",
  email: "[email protected]",
  preferences: { theme: "dark" }
});
Send messages to users for order updates, alerts, and more:
// Send order shipped notification
await user.send([
  { type: "text", text: "Your order has been shipped!" },
  { type: "text", text: "Tracking: TRACK123456" }
]);

// Send with image
await user.send([
  { type: "text", text: "Your boarding pass:" },
  { type: "image", image: qrCodeBase64, mediaType: "image/png" }
]);
Ensure correct message format:
// ✅ Correct
await user.send([
  { type: "text", text: "Hello!" }
]);

// ✅ Multiple messages
await user.send([
  { type: "text", text: "Message 1" },
  { type: "text", text: "Message 2" }
]);

// ❌ Wrong - Must be array
await user.send({ type: "text", text: "Hello!" });

TypeScript Support

interface UserDataInstance {
  // Properties (dynamic based on stored data)
  name?: string;
  email?: string;
  phone?: string;
  [key: string]: any;
  
  // Methods
  update(data: Record<string, any>): Promise<any>;
  save(): Promise<boolean>;
  send(messages: Message[]): Promise<any>;
  clear(): Promise<boolean>;
  toJSON(): Record<string, any>;
}

// Message types
type TextMessage = {
  type: "text";
  text: string;
};

type ImageMessage = {
  type: "image";
  image: string;
  mediaType: string;
};

type FileMessage = {
  type: "file";
  data: string;
  mediaType: string;
};

type Message = TextMessage | ImageMessage | FileMessage;

Usage with Types

// Define your user data shape
interface MyUserData {
  name: string;
  email: string;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

// Access with type safety
const user = context.user;
const name: string = user.name;
const theme = user.preferences?.theme || 'light';

Next Steps