Skip to main content

Building Process

1

Plan Your Skill

Define what your skill will do and what tools it needs
2

Create Tools

Implement tools as TypeScript classes
3

Define Skill

Add tools to a LuaSkill instance in index.ts
4

Test Locally

Test with lua chat (sandbox mode) and optionally lua test
5

Deploy

Push and deploy to production

Complete Example: Task Management Skill

Step 1: Plan

Goal: Build a skill to manage tasks Tools needed:
  • create_task - Add new tasks
  • list_tasks - View all tasks
  • complete_task - Mark tasks as done
  • search_tasks - Find tasks

Step 2: Create Tools

Create src/tools/TaskTools.ts:
import { LuaTool, Data } from "lua-cli";
import { z } from "zod";

export class CreateTaskTool implements LuaTool {
  name = "create_task";
  description = "Create a new task";
  
  inputSchema = z.object({
    title: z.string(),
    description: z.string().optional(),
    priority: z.enum(['low', 'medium', 'high']).default('medium')
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const task = await Data.create('tasks', {
      ...input,
      status: 'pending',
      createdAt: new Date().toISOString()
    }, `${input.title} ${input.description || ''}`);
    
    return {
      taskId: task.id,
      message: `Task "${input.title}" created`
    };
  }
}

export class ListTasksTool implements LuaTool {
  name = "list_tasks";
  description = "List all tasks";
  inputSchema = z.object({});

  async execute(input: any) {
    const result = await Data.get('tasks', {}, 1, 50);
    
    return {
      tasks: result.data.map(entry => ({
        id: entry.id,
        ...entry.data
      })),
      count: result.pagination.totalCount
    };
  }
}

export class CompleteTaskTool implements LuaTool {
  name = "complete_task";
  description = "Mark a task as completed";
  
  inputSchema = z.object({
    taskId: z.string()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const task = await Data.getEntry('tasks', input.taskId);
    
    await Data.update('tasks', input.taskId, {
      ...task.data,
      status: 'completed',
      completedAt: new Date().toISOString()
    });
    
    return {
      success: true,
      message: `Task completed!`
    };
  }
}

Step 3: Configure Agent with Skill

Update src/index.ts to use the v3.0.0 LuaAgent pattern:
import { LuaAgent, LuaSkill } from "lua-cli";
import {
  CreateTaskTool,
  ListTasksTool,
  CompleteTaskTool
} from "./tools/TaskTools";

// Create skill
const taskSkill = new LuaSkill({
  name: "task-management-skill",
  description: "Manage tasks and to-do lists",
  context: `
    This skill helps users manage their tasks.
    
    - Use create_task when users want to add a new task
    - Use list_tasks to show all tasks
    - Use complete_task when users finish a task
    
    Always confirm task details before creating.
    When listing tasks, organize by priority.
  `,
  tools: [
    new CreateTaskTool(),
    new ListTasksTool(),
    new CompleteTaskTool()
  ]
});

// Configure agent (v3.0.0)
export const agent = new LuaAgent({
  name: "task-assistant",
  
  persona: `You are a helpful task management assistant.
  
Your role:
- Help users create and organize tasks
- Keep track of todos and deadlines
- Help users stay productive

Communication style:
- Friendly and encouraging
- Clear and concise
- Confirm before creating tasks`,

  
  skills: [taskSkill]
});
New in v3.0.0: Use LuaAgent to configure your agent’s persona, welcome message, and skills together. This is now the recommended pattern.

Step 4: Test

# Test conversationally (primary)
lua chat

# Optional: Test individual tools
lua test
Select sandbox mode to test with your local skills.

Step 5: Deploy

lua push
lua deploy

Best Practices

Build one tool at a time:
  1. Create basic version
  2. Test thoroughly
  3. Add more features
  4. Test again
// ✅ Good
name = "create_task"
name = "search_products"
name = "send_email"

// ❌ Bad
name = "task1"
name = "do_stuff"
name = "tool"
// ✅ Good
description = "Create a new task with title, description, and priority"

// ❌ Bad
description = "Creates tasks"
Use Zod for comprehensive validation:
inputSchema = z.object({
  email: z.string().email(),
  age: z.number().min(0).max(120),
  priority: z.enum(['low', 'medium', 'high'])
});
async execute(input: any) {
  try {
    const result = await api.call(input);
    return result;
  } catch (error) {
    throw new Error(
      `Failed to process request: ${error.message}`
    );
  }
}
// ✅ Good - Structured
return {
  success: true,
  taskId: task.id,
  message: "Task created"
};

// ❌ Bad - Unstructured
return "Task created with ID 123";

Common Patterns

CRUD Pattern

// Create
export class CreateItemTool {
  async execute(input) {
    return await Data.create('items', input);
  }
}

// Read
export class GetItemTool {
  async execute(input) {
    return await Data.getEntry('items', input.id);
  }
}

// Update
export class UpdateItemTool {
  async execute(input) {
    return await Data.update('items', input.id, input.data);
  }
}

// Delete
export class DeleteItemTool {
  async execute(input) {
    return await Data.delete('items', input.id);
  }
}

Search Pattern

export class SearchTool {
  async execute(input: { query: string }) {
    // Vector search
    const results = await Data.search(
      'items',
      input.query,
      10,
      0.7
    );
    
    return {
      items: results.data,
      count: results.count
    };
  }
}

External API Pattern

export class ExternalApiTool {
  async execute(input: any) {
    // Get API key from environment
    const apiKey = env('EXTERNAL_API_KEY');
    
    if (!apiKey) {
      throw new Error('API key not configured');
    }
    
    // Call external API
    const response = await fetch(apiUrl, {
      headers: {
        'Authorization': `Bearer ${apiKey}`
      }
    });
    
    return await response.json();
  }
}

Next Steps