Using and Interfacing with Lootbox Tools
This skill provides practical guidance for calling and using lootbox tools in LLM scripts. Learn how to access tools, pass arguments correctly, handle results, and build workflows that combine multiple tools.
Quick Start
Prerequisites
Before using lootbox tools, understand what's available:
- ✅ Commonly available: Native tools like
kv,sqlite,fs,memory,github,linear,s3,workflows,graphql - 🔌 Requires .mcp.json: External MCP servers like
mcp_perplexity,mcp_*tools - 💡 Check YOUR tools: Run
lootbox toolsto see what's actually available in your environment (available tools vary by installation)
⚠️ Important: Examples using tools.mcp_* (like tools.mcp_perplexity) require external MCP servers configured in .mcp.json. If you only see native tools when running lootbox tools, skip MCP examples and focus on native tools.
The Basic Pattern
All lootbox tools follow this simple calling pattern:
const result = await tools.<namespace>.<function>({
param1: "value",
param2: 42,
optional: "field"
});
console.log(result);
Three components:
tools- The tools object (always available in lootbox scripts)<namespace>- Group of related functions (file name without.ts)<function>- Individual function name
Examples with Native Tools
// Get a value from key-value store
const data = await tools.kv.get({key: "user:123"});
// Query the database
const result = await tools.sqlite.query({
sql: "SELECT * FROM users WHERE id = ?",
params: [123]
});
// Result: { rows: [...], columns: ["id", "name", ...], rowCount: number }
// Fetch GitHub repository info (if github tool is available)
const repo = await tools.github.getRepo({repo: "anthropics/claude-code"});
// Result: { repo: { name, stars, ... } }
// List Linear issues (if linear tool is available)
const issues = await tools.linear.listIssues({limit: 10});
// Result: { issues: [...], total, hasMore }
Note: Run lootbox tools to see which namespaces are actually available in your environment. The examples above use commonly available tools.
Understanding Tool Namespaces
Native Tools (from .lootbox/tools/)
Tools you create or that come built-in:
await tools.kv.get({key: "mykey"}); // kv namespace
await tools.sqlite.query({sql: "SELECT..."}); // sqlite namespace - for SELECT (returns rows)
await tools.sqlite.execute({sql: "INSERT..."}); // sqlite namespace - for INSERT/UPDATE/DELETE
await tools.myapp.doSomething({...}); // myapp namespace
SQLite Tool Note:
query()- For SELECT statements (returnsrows,columns,rowCount)execute()- For INSERT/UPDATE/DELETE (returnssuccess,changes,lastInsertRowId)queryOne()- For SELECT returning single row (returnsrowornull)
Discovery:
lootbox tools
# Output: Namespaces: kv, fs, sqlite, memory, myapp, ...
MCP Tools (from external servers)
📘 Advanced Topic: This section covers integrating external MCP (Model Context Protocol) servers. Most users can skip this and focus on native tools above.
Tools from external MCP servers can be integrated into lootbox through .mcp.json configuration. These tools extend lootbox with additional capabilities like web search, LLM access, or specialized APIs.
How MCP Tools Appear:
MCP tools can appear in two ways depending on configuration:
As regular namespaces (when served through lootbox itself):
await tools.github.getRepo({repo: "owner/name"}); await tools.linear.listIssues({teamId: "..."});With
mcp_prefix (when using external servers directly):// Example - requires mcp_perplexity configured in .mcp.json await tools.mcp_perplexity.search({query: "..."});
Important: Run lootbox tools to see which MCP tools are actually configured in your environment. Many MCP examples you see online require additional setup in .mcp.json.
To add MCP servers: See the MCP Server documentation for setup instructions.
Calling Tools: The Syntax
Basic Call
const result = await tools.<namespace>.<function>({
arg1: value1,
arg2: value2
});
Required Arguments
All required fields must be provided:
// This function requires 'key'
await tools.kv.get({key: "mykey"}); // ✓ Works
await tools.kv.get({}); // ✗ Error - missing 'key'
await tools.kv.get({key: "", other: 1}); // ✓ Works (key is provided, even if empty)
Optional Arguments
Fields marked with ? can be omitted:
// kv.list has optional 'prefix' and 'limit' parameters
await tools.kv.list({}); // ✓ Works - uses defaults
await tools.kv.list({prefix: "user:", limit: 50}); // ✓ Works - with optional params
Type Correctness
Arguments must match their declared types:
// limit should be number, not string
await tools.kv.list({
limit: 50 // ✓ Number
});
await tools.kv.list({
limit: "50" // ✗ Error - should be number
});
Zero-Argument Functions
Functions with no required parameters have different calling conventions based on their type signature. You MUST use the correct pattern or TypeScript will fail:
// Tools with empty Args interface (e.g., `Kv_InfoArgs {}`) - MUST call with {}
const info = await tools.kv.info({}); // ✓ Works
const cleared = await tools.kv.clear({}); // ✓ Works
// await tools.kv.info(); // ✗ TypeScript error: Expected 1 argument
// Tools with no Args interface - MUST call without {}
const tables = await tools.sqlite.listTables(); // ✓ Works
const dbInfo = await tools.sqlite.info(); // ✓ Works
// await tools.sqlite.listTables({}); // ✗ TypeScript error: Expected 0 arguments
How to know which to use:
- Run
lootbox tools types <namespace>to see the function signature - If you see an Args interface (even empty like
Kv_InfoArgs {}), you MUST use{} - If no Args interface is shown (just
listTables(): Promise<...>), you MUST omit{} - These patterns are NOT interchangeable - using the wrong one causes TypeScript errors
⚠️ CRITICAL: Shell Escaping with lootbox exec
This is a common source of errors - read carefully!
When using lootbox exec from the command line, the ! operator gets escaped by the shell and causes parse errors:
// ✗ Fails in shell (! gets escaped to \!):
if (!result.exists) { ... }
if (result.exists !== true) { ... }
// ✓ Use instead:
if (result.exists === false) { ... }
Why: When passing code as a command-line string argument, the shell interprets ! as a history expansion operator before passing the code to TypeScript. Use explicit equality comparisons (=== false, === true) instead.
Alternative: If you need to use the ! operator:
- Write your code to a
.tsfile and execute the file directly instead of usinglootbox exec - Use
set +Hto disable history expansion in bash (may not work in all shells)
Execution Methods
There are multiple ways to execute lootbox code:
# Inline execution (quick tests, simple operations)
lootbox exec 'await tools.kv.set({key: "foo", value: "bar"})'
# From a TypeScript file (complex scripts, uses ! operator freely)
lootbox myscript.ts
# From stdin (piping data)
echo 'console.log(await tools.kv.get({key: "foo"}))' | lootbox
When to use each:
lootbox exec- Quick one-liners, testing, simple operations- File execution - Complex logic, scripts with
!operators, reusable code - Stdin piping - Automation, chaining with other commands
Handling Results
⚠️ IMPORTANT: Output with console.log, Not return
In lootbox scripts, do not use return at the top level - it will throw an error. Always use console.log() to output results:
// ✗ WRONG - will throw error
const result = await tools.kv.get({key: "answer"});
return result; // Error: return only allowed in functions
// ✓ CORRECT - use console.log
const result = await tools.kv.get({key: "answer"});
console.log(JSON.stringify(result, null, 2)); // You see this output
Why: Lootbox scripts execute as top-level async code, not inside a function. Use console.log() for all output you want to see.
Result Structure
All tool functions return structured objects:
// Result is always an object with documented fields
const result = await tools.kv.get({key: "test"});
// Always check the result structure
console.log(result);
// { value: "data", exists: true }
Accessing Result Fields
const result = await tools.kv.get({key: "user:123"});
// Access fields from result
if (result.exists) {
console.log(`Found value: ${result.value}`);
} else {
console.log("Key not found");
}
// Result fields are typed - IDE autocomplete available
result.nonExistent // ✗ Error - field doesn't exist
Result Documentation
Get type information for tools:
lootbox tools types <namespace>
# Shows all available functions and their result structures
Status Field Reference
Different lootbox tools use different status fields in their results. Here's a quick reference:
| Status Field | Used By | Meaning | Check Pattern |
|---|---|---|---|
exists |
kv.get(), fs.fileInfo() |
Data exists (true/false) | if (result.exists === false) |
success |
kv.set(), kv.deleteKey(), fs.write(), sqlite.execute() |
Operation succeeded (true/false) | if (result.success) |
existed |
kv.deleteKey() |
Key existed before deletion (returned alongside success) |
if (result.existed) |
found |
Some custom tools | Resource found (true/false) | if (result.found) |
rowCount |
sqlite.query() |
Number of rows returned | if (result.rowCount > 0) |
changes |
sqlite.execute() |
Number of rows modified | if (result.changes > 0) |
error |
Some tools | Error message if present | if (result.error) |
Note: Some operations return multiple status fields. For example, kv.deleteKey() returns both success (operation completed) and existed (key was present before deletion).
Note: This table shows commonly used status fields. Use lootbox tools types <namespace> to see the complete result structure for any tool.
Key principle: Always check result status fields before accessing data fields.
Naming convention: Status field names follow semantic patterns:
- Present tense (
exists,success) for current state - Past tense (
existed,deleted) for completed operations - Count fields (
rowCount,changes) for quantifiable results
Always verify field names with lootbox tools types <namespace> for your specific tool version.
Error Handling
Quick Overview
Native Local Tools (kv, sqlite, fs, memory):
- Return status in result object
- Don't throw exceptions
- Check
exists,success, orerrorfields
External API Tools (github, linear):
- THROW exceptions on failures
- MUST wrap in try/catch
- Handles 404s, auth errors, rate limits
Basic Patterns
// Native tools - check status fields
const result = await tools.kv.get({key: "test"});
if (result.exists === false) {
console.log("Key not found");
}
// External tools - use try/catch
try {
const repo = await tools.github.getRepo({repo: "owner/name"});
console.log(`Found: ${repo.repo.name}`);
} catch (error) {
console.log(`Error: ${error instanceof Error ? error.message : String(error)}`);
}
For detailed error handling patterns, see: references/error_handling.md
Native Tools vs MCP Tools
Native Tools (Fastest)
Tools in .lootbox/tools/ execute in the local TypeScript environment:
// Runs locally, fastest execution
const value = await tools.kv.get({key: "test"});
Use when:
- You control the tool code
- Speed is important
- Tool doesn't need external services
MCP Tools (External - Advanced)
Tools from external Model Context Protocol servers (requires .mcp.json configuration):
// Example: Using an external MCP search service
// ⚠️ This only works if you have the MCP server configured
const results = await tools.mcp_search.query({
query: "example search"
});
Use when:
- You need external APIs (search, LLMs, specialized data services)
- Functionality exists in a published MCP server
- You're willing to configure external dependencies
Setup required: See MCP documentation for configuration steps
Common Usage Patterns
Essential Patterns
Get or Create:
const userId = "user123";
const get = await tools.kv.get({key: userId});
if (get.exists === false) {
const userData = {name: "Alice", created: Date.now()};
await tools.kv.set({key: userId, value: userData});
return userData;
}
return get.value as any;
Pagination:
const allItems: any[] = [];
let offset = 0;
let hasMore = true;
while (hasMore) {
const page = await tools.kv.list({limit: 50, offset});
allItems.push(...page.entries);
offset += page.returnedCount;
hasMore = offset < page.totalCount;
}
Parallel Operations:
// ✓ Good: Parallel when independent
const [user, config] = await Promise.all([
tools.kv.get({key: `user:${userId}`}),
tools.kv.get({key: "config:app"})
]);
For comprehensive patterns and examples, see: references/usage_patterns.md
Best Practices
Core Principles
- Know Your Tools - Run
lootbox toolsto see available namespaces - Discover Functions - Use
lootbox tools types <namespace>before calling - Validate Arguments - Check arguments before calling tools
- Handle Errors - Check result status or use try/catch appropriately
- Cache Expensive Operations - Use KV store for expensive queries
- Chain Efficiently - Use parallel operations when possible
For detailed best practices, see: references/best_practices.md
Troubleshooting
Quick Diagnostics
Tool Not Found:
# Check available tools
lootbox tools
Function Not Found:
# List functions in namespace
lootbox tools types <namespace>
Argument Errors:
- Missing required field: Check tool types for required arguments
- Wrong type: Verify argument types match (number vs string)
- Extra unknown fields: Remove unrecognized parameters
API Errors (GitHub/Linear):
- 404 Not Found: Invalid resource name
- 401/403 Unauthorized: Check
GITHUB_TOKEN,LINEAR_API_KEYenvironment variables - 429 Rate Limiting: Implement caching to reduce API calls
For comprehensive troubleshooting, see: references/usage_troubleshooting.md
Scripts: Reusable Code
Discovering Existing Scripts
Before writing new code, check if a script already exists:
lootbox scripts # List all available scripts with examples
This shows scripts in .lootbox/scripts/ that you can run directly.
Creating Reusable Scripts
For complex or repeated tasks, create a script file:
# Create a new script from template
lootbox scripts init process-users
# This creates .lootbox/scripts/process-users.ts
# Run it with:
lootbox process-users.ts
The stdin() Helper
Scripts can accept piped input using the stdin() helper function:
/**
* Process user data from JSON input
* @example echo '{"users": ["alice", "bob"]}' | lootbox process-users.ts
*/
const input = stdin().json(); // Parse piped JSON data
for (const user of input.users) {
await tools.kv.set({ key: `user:${user}`, value: { name: user } });
}
console.log(JSON.stringify({ processed: input.users.length }));
stdin() methods:
| Method | Description | Example |
|---|---|---|
.json() |
Parse JSON input | stdin().json() → {users: [...]} |
.text() |
Get trimmed text | stdin().text() → "hello world" |
.lines() |
Get array of lines | stdin().lines() → ["line1", "line2"] |
.raw() |
Get raw input string | stdin().raw() → "hello\nworld\n" |
Example usage:
# Pipe JSON data
echo '{"name": "Alice"}' | lootbox process-user.ts
# Pipe a file
cat users.json | lootbox process-users.ts
# Pipe command output
gh issue list --json number,title | lootbox sync-issues.ts
When to create scripts:
- Task is repeated frequently
- Logic is complex (multiple steps, conditionals)
- Need to use
!operator (avoid shell escaping) - Want to accept piped input
- Code should be version controlled
Getting Help
To list available tools:
lootbox tools
To list available scripts:
lootbox scripts
To understand a tool's interface:
lootbox tools types <namespace>
To test a tool call:
lootbox exec 'console.log(await tools.<ns>.<fn>({...}))'
To create a reusable script:
lootbox scripts init <script-name>
For more examples:
See assets/examples/basic_tool_usage.ts for working code samples
For detailed guides:
references/usage_patterns.md- Comprehensive usage patternsreferences/best_practices.md- Best practices guidereferences/error_handling.md- Error handling strategiesreferences/usage_troubleshooting.md- Troubleshooting guide
Next Steps
- Want patterns? → See
references/usage_patterns.md - Want best practices? → See
references/best_practices.md - Having errors? → See
references/error_handling.md - Stuck? → See
references/usage_troubleshooting.md