The ChattreeHostApi is the bridge between a WASM plugin and Chattree's internal services.
Your plugin communicates with it through a single imported function: chattree.host_call.
[!NOTE] Slash-Command Input (
lang=<code>)
When a user invokes a plugin slash command from chat, Chattree appends the active app language to plain-text arguments (e.g.,/mycmdpasses"lang=es"and/mycmd testpasses"test lang=es"). Strip trailinglang=<code>tokens before matching exact subcommands.
Your WASM module must declare this import to use any Host API:
(import "chattree" "host_call"
(func $host_call (param i32 i32 i32 i32) (result i32))
)
Parameters:
| Param | Type | Meaning |
|---|---|---|
reqPtr | i32 | Memory offset where the request JSON string starts |
reqLen | i32 | Byte length of the request JSON string |
resPtr | i32 | Memory offset where Chattree will write the response JSON |
resMaxLen | i32 | Maximum bytes Chattree can write to the response buffer |
Return value:
| Value | Meaning |
|---|---|
> 0 | Synchronous response written at resPtr, length = return value |
0 | Async operation started — WASM must export a resume function |
-1 | Error parsing request or memory out of bounds |
{
"api": "storage",
"operation": "set",
"payload": { "key": "my_key", "value": "my_value" }
}
| Field | Type | Required | Description |
|---|---|---|---|
api | string | ✅ | Namespace identifier (see table below) |
operation | string | ✅ | Operation name within the namespace |
payload | any | ✅ | Operation-specific parameters |
{
"success": true,
"data": { ... },
"error": null
}
| Field | Type | Description |
|---|---|---|
success | boolean | true if the operation succeeded |
data | object | Operation result (present when success == true) |
error | string | Error message (present when success == false) |
| Namespace | Description | Sync? | Capability Required |
|---|---|---|---|
core | Plugin metadata and logging | ✅ Sync | None |
storage | Private key-value store | ✅ Sync | storage.private (auto) |
network | HTTP/HTTPS requests | ❌ Async | network.http (+ optional network.local for LAN/loopback) |
files | File pick / read / write | ❌ Async | files.pick/files.read/files.write |
notifications | Local alerts, Notification Hub & Listener | ❌ Async | notifications.send, notifications.read, notifications.query, notifications.reply, notifications.action |
location | GPS position | ❌ Async | location.current |
camera | Photo capture | ❌ Async | camera.capture |
contacts | Address book | ❌ Async | contacts.read/.create/.update |
calendar | Device calendar | ❌ Async | calendar.read/.create/.update/.delete |
email | Native IMAP/SMTP email & inbox | ❌ Async | email.read/email.send |
clipboard | System clipboard read/write | ❌ Async | clipboard.read/clipboard.write |
share | Share sheet | ❌ Async | share.text/share.file |
open | Launch URLs and apps | ❌ Async | open.url/open.app |
chattree.notes | Chattree Notes CRUD | ❌ Async | chattree.notes.* |
chattree.reminders | Chattree Reminders CRUD | ❌ Async | chattree.reminders.* |
chattree.context | Current message & bounded chat history | ❌ Async | chattree.context.read / chat.history.read |
ai | Single-turn LLM completion, embeddings & RAG | ❌ Async | ai.context / ai.embeddings |
websocket | Real-time bidirectional WebSockets | ❌ Async | network.websocket (+ optional network.local) |
ui | Declarative rich UI cards, badges, progress & buttons | ❌ Async (ui.validate is Sync) | ui.components / ui.render |
scheduler | Wall-clock Cron & periodic background tasks | ❌ Async | scheduler.task / scheduler.cron |
database | Private isolated relational SQLite & transactions | ❌ Async | database.sql / storage.database |
Synchronous operations complete within the same WASM execution call:
core.get_manifest, core.logstorage.* operationsAsynchronous operations require a second execution round-trip:
host_call(…) → receives 0 (async pending).resume(resPtr, resLen) function.If your plugin only uses sync operations (core + storage), you do NOT need to export a
resumefunction.
| Operation | Sync | Input | Output |
|---|---|---|---|
core.get_manifest | ✅ | {} | { id, version, name, description, author, runtimeApiVersion } |
core.log | ✅ | { level: "info"|"warning"|"error", message: string } | { logged: true, level } |
core.log messages appear in Flutter debug logs prefixed with [Plugin:<id>].
| Operation | Sync | Input | Output |
|---|---|---|---|
storage.set | ✅ | { key, value } | { stored: true, key, value } |
storage.get | ✅ | { key } | { value: string|null, exists: boolean } |
storage.delete | ✅ | { key } | { deleted: boolean, key } |
storage.has | ✅ | { key } | { exists: boolean, key } |
storage.keys | ✅ | {} | { keys: string[], count: number } |
Quotas enforced by PluginStorageStore:
262,144 bytes)2,097,152 bytes)| Operation | Sync | Input | Output |
|---|---|---|---|
network.http_request | ❌ | { url, method?, headers?, body?, bodyEncoding?, responseType?, timeoutMs? } | { statusCode, headers, body, encoding?, isBase64? } |
| Operation | Sync | Input | Output |
|---|---|---|---|
files.pick | ❌ | { allowedExtensions?, title? } | { cancelled, handle, name, size, extension, mimeType } |
files.read | ❌ | { handle, asText? } | { handle, name, size, encoding, content } |
files.write | ❌ | { name, content, encoding? } | { name, size, written, handle } |
A plugin can generate a file with files.write and return its opaque handle as part of the WASM tool result. Chattree then renders a file card in the chat with Save and Share actions.
files.write in the plugin or tool capabilities and request/grant it as required by the app.files.write with a safe filename and content. Use encoding: "utf8" for text formats such as Markdown, or encoding: "base64" for binary data.data.handle and data.name.{
"type": "direct",
"displayText": "Generated report is ready.",
"fileHandle": "f_<handle returned by files.write>",
"fileName": "report.md"
}
The Host API response from files.write has the form {"success":true,"data":{"name":"report.md","size":123,"written":true,"handle":"f_…"}}. In the final tool result, Chattree verifies that the handle exists and belongs to the calling plugin; the handle registry is authoritative for the actual filename and file path.
The chat file card shows the filename/type, the displayText, and Save and Share buttons. Save opens the native save flow; Share opens the platform share sheet. These are user-triggered actions. They do not require the plugin to call share.file, because the app performs them from the card. The file itself still requires the files.write capability to create.
For a .md file, files.write stores Markdown text and the card identifies it as a Markdown/text file. The card’s displayText is currently shown as plain text rather than rendered Markdown; the generated file can be saved or shared and then opened in a Markdown viewer/editor.
If you return an unknown, unavailable, or another plugin’s handle, Chattree drops the file attachment. Do not invent or persist handles between sessions; create the file and return its handle in the same execution flow.
The notifications namespace covers local notification dispatch and the full universal Notification Hub device integration (querying active alerts, listening to real-time incoming notifications, direct replies via RemoteInput, programmatically dismissing notifications, and invoking native actions via cryptographically secure ActionHandle tokens).
[!IMPORTANT] Package Scope Isolation (
allowedNotificationPackages)
Every notification operation (getActive,reply,dismiss,invokeAction, and push event delivery) requires the target application's Android package name to be explicitly declared in the plugin manifest (plugin.jsonunderallowedNotificationPackages). Calls targeting undeclared packages are rejected with403 Forbidden(packageScopeViolation: true).
| Operation | Sync | Capability Required | Input | Output | Description |
|---|---|---|---|---|---|
notifications.send | ❌ | notifications.send | { title: string, body?: string } | { notificationId, sent, remainingQuota } | Sends a local system alert (rate limited to 15/hr). |
notifications.cancel | ❌ | notifications.send | { notificationId: number } | { notificationId, cancelled: boolean } | Cancels a local notification previously posted by this plugin. |
notifications.getStatus | ❌ | notifications.query or notifications.read | {} | { status: "active" | "disconnected" | "permission_required" | "reconnecting" | "unsupported", isListenerEnabled: boolean, isSupported: boolean } | Returns current Android Notification Listener service status. |
notifications.getActive | ❌ | notifications.query | { packageName?: string, category?: string, canReplyOnly?: boolean, excludeOngoing?: boolean, excludeFromMe?: boolean, includeRecent?: boolean, limit?: number } | DeviceNotificationSummary[] | Queries active status bar alerts (and optionally recent RAM history), filtered strictly by allowedNotificationPackages. |
notifications.query | ❌ | notifications.query | { packageName?: string, category?: string, canReplyOnly?: boolean, excludeOngoing?: boolean, excludeFromMe?: boolean, includeRecent?: boolean, limit?: number } | DeviceNotificationSummary[] | Alias of notifications.getActive. |
notifications.getRecent | ❌ | notifications.query | { limit?: number, packageName?: string, category?: string, canReplyOnly?: boolean, excludeOngoing?: boolean, excludeFromMe?: boolean } | DeviceNotificationSummary[] | Queries the ephemeral in-memory circular buffer (up to 100 recent non-ongoing notifications), filtered by allowedNotificationPackages. |
notifications.reply | ❌ | notifications.reply | { logicalId: string, message: string } | { replied: boolean, logicalId: string } | Sends inline quick reply via Android native RemoteInput (max 4,000 characters). Package scope is verified from logicalId. |
notifications.dismiss | ❌ | notifications.action | { logicalId: string } | { dismissed: boolean, logicalId: string } | Swipes away / cancels an active notification from the Android status bar. |
notifications.invokeAction | ❌ | notifications.action | { actionHandle: string, logicalId?: string, packageName?: string, actionIndex?: number } | { invoked: boolean, actionHandle: string } | Triggers a native action button via its single-use, cryptographically secure ActionHandle (or logicalId + actionIndex). |
notifications.subscribe | ❌ | notifications.read | {} | { subscribed: boolean, status: string, isListenerEnabled: boolean, isSupported: boolean } | Ensures the platform event listener is initialized. |
notifications.openSettings | ❌ | notifications.query or notifications.read | {} | { opened: boolean, isSupported: boolean } | Launches Android OS Notification Listener Access settings screen. |
notifications.read)When notifications.read is granted, the plugin receives ephemeral in-memory events on HostEventBus. Events are filtered at the dispatcher level so that the plugin only receives notifications from applications declared in its allowedNotificationPackages whitelist:
device.notification.received: Published when a new notification arrives on the device.device.notification.updated: Published when an existing active notification thread updates (e.g. new message in conversation).
{
"logicalId": "com.whatsapp_1042_null",
"packageName": "com.whatsapp",
"appName": "WhatsApp",
"channelId": "group_chat_notifications_1",
"postTime": 1711234567890,
"title": "Juan Pérez",
"body": "Hola, ¿cómo estás?",
"subText": "WhatsApp",
"infoText": "",
"category": "msg",
"isGroupSummary": false,
"isOngoing": false,
"isClearable": true,
"isFromMe": false,
"canReply": true,
"conversationTitle": "Familia",
"isGroup": true,
"senderName": "Juan Pérez",
"textLines": ["Juan: Hola, ¿cómo estás?"],
"messageHistory": [
{
"text": "Hola, ¿cómo estás?",
"timestamp": 1711234567890,
"sender": "Juan Pérez",
"isSelf": false
}
],
"actions": [
{
"actionHandle": "act_4f8b9123cde4a7812bcdeff012345678",
"title": "Responder",
"isReply": true,
"semanticAction": "reply",
"index": 0
},
{
"actionHandle": "act_8e2a149b561c437a89ff0123456789ab",
"title": "Marcar como leído",
"isReply": false,
"semanticAction": "mark_as_read",
"index": 1
}
]
}
device.notification.removed: Published when a notification is dismissed by the user or cancelled by the source app.
{
"logicalId": "com.whatsapp_1042_null",
"packageName": "com.whatsapp",
"postTime": 1711234567890
}
device.notification.status: Published when the listener service status transitions (e.g. active, disconnected).notifications.invokeAction) & ActionHandle SecurityAction buttons attached to Android notifications (such as Mark as read, Archive, Snooze, or Approve) are exposed to plugins via opaque, cryptographically random ActionHandle tokens:
{
"api": "notifications",
"operation": "invokeAction",
"payload": {
"actionHandle": "act_4f8b9123cde4a7812bcdeff012345678",
"packageName": "com.whatsapp",
"logicalId": "com.whatsapp_1042_null"
}
}
Security Guarantees:
actionHandle tokens are generated using UUID v4 (128-bit entropy) prefixed with act_. They cannot be synthesized or guessed.ACTION_ALREADY_USED or ACTION_EXPIRED.invokeAction validates that the caller plugin is authorized for packageName, and native Kotlin strictly verifies boundAction.packageName == packageName. Cross-application action hijacking is impossible.MODE_BACKGROUND_ACTIVITY_START_ALLOWED restrictions.Device notification payloads (titles, bodies, sender names, and message text) are treated as strictly ephemeral in-memory signals:
events or deliveries tables in EventJournal.database.sql / storage.database and persist structured data into their own private, isolated SQLite database file (plugin_<id>.db).| Operation | Sync | Input | Output |
|---|---|---|---|
location.current | ❌ | { accuracy? } | { latitude, longitude, accuracy, timestamp, provider } |
location.permission_status | ❌ | {} | { serviceEnabled, permission, isGranted, isDeniedForever } |
| Operation | Sync | Input | Output |
|---|---|---|---|
camera.capture | ❌ | { preferredCamera?, quality? } | { cancelled, handle, name, size, mimeType } |
| Operation | Sync | Input | Output |
|---|---|---|---|
contacts.get | ❌ | { query?, limit? } | { contacts: [...] } |
contacts.search | ❌ | { query, limit? } | { contacts: [...] } |
contacts.create | ❌ | { displayName, phones?, emails? } | { id, displayName, created } |
contacts.update | ❌ | { id, displayName?, phones?, emails? } | { id, updated } |
| Operation | Sync | Input | Output |
|---|---|---|---|
calendar.calendars | ❌ | {} | { calendars: [...] } |
calendar.events | ❌ | { calendarId, startDate, endDate } | { events: [...] } |
calendar.create | ❌ | { calendarId, title, startDate, endDate, description? } | { eventId, created } |
calendar.update | ❌ | { calendarId, eventId, ...fields } | { updated } |
calendar.delete | ❌ | { calendarId, eventId } | { deleted } |
The email namespace provides native IMAP and SMTP connectivity with TLS/SSL encryption for reading inboxes, inspecting message content, and sending emails.
email.read is strictly required for inbox, list, events, and read; email.send is required for send and create; either email.read or email.send is required for config, setup, disconnect, and logout.FlutterSecureStorage (EncryptedSharedPreferences / Keychain) and are never stored in plaintext inside PluginStorageStore.| Operation | Sync | Input | Output |
|---|---|---|---|
email.config / email.setup | ❌ | { email, password, imapHost?, imapPort? } | { configured: true, email, provider, message } |
email.inbox / email.list / email.events | ❌ | {} | { status: "success", account, unread_count, total_count, emails: [...] } |
email.read | ❌ | { id } | { id, from, to, subject, date, body } |
email.send / email.create | ❌ | { to, subject, body } | { sent: true, to, subject, response } |
email.disconnect / email.logout | ❌ | {} | { disconnected: true, message } |
| Operation | Sync | Input | Output |
|---|---|---|---|
clipboard.read / clipboard.get | ❌ | {} | { text: string, hasContent: boolean, length: number } |
clipboard.write / clipboard.set | ❌ | { text: string } | { copied: true, length: number } |
clipboard.has | ❌ | {} | { hasContent: boolean } |
| Operation | Sync | Input | Output |
|---|---|---|---|
share.text | ❌ | { text, subject? } | { shared: true, type: "text" } |
share.file | ❌ | { handle, text? } | { shared: true, type: "file", handle, fileName } |
| Operation | Sync | Input | Output |
|---|---|---|---|
open.url | ❌ | { url } | { opened: boolean, url } |
open.app | ❌ | { uri } | { opened: boolean, target } |
| Operation | Sync | Input | Output |
|---|---|---|---|
chattree.notes.search | ❌ | { query, limit? } | { notes: [...], count } |
chattree.notes.get | ❌ | { id } | { note: { id, title, content, createdAt, updatedAt } } |
chattree.notes.create | ❌ | { title, content? } | { note: { id, title, content, createdAt, updatedAt } } |
chattree.notes.update | ❌ | { id, title?, content? } | { note: { ... } } |
chattree.notes.delete | ❌ | { id } | { deleted: true, id } |
| Operation | Sync | Input | Output |
|---|---|---|---|
chattree.reminders.list | ❌ | { limit? } | { reminders: [...], count } |
chattree.reminders.get | ❌ | { id } | { reminder: { ... } } |
chattree.reminders.create | ❌ | { title, scheduledAt? } | { reminder: { ... } } |
chattree.reminders.update | ❌ | { id, title?, scheduledAt? } | { reminder: { ... } } |
chattree.reminders.delete | ❌ | { id } | { deleted: true, id } |
chattree.reminders.complete | ❌ | { id } | { completed: true, id } |
The chattree.context namespace allows a plugin to inspect the conversational invocation context (chattree.context.read) or read a bounded window of recent messages (chat.history.read).
| Operation | Sync | Input | Output |
|---|---|---|---|
chattree.context.read | ❌ | {} | { currentMessage, explicitContext, authorized, restricted, timestamp } |
chattree.context.history / chattree.context.messages | ❌ | { limit?: number } | { messages: [{ role, content, timestamp }], count, maxLimit: 20, restricted, timestamp } |
The ai namespace provides bounded access to single-turn LLM completions (ai.context) and on-device vector embeddings / semantic search (ai.embeddings).
| Operation | Sync | Input | Output |
|---|---|---|---|
ai.complete / ai.generate / ai.prompt / ai.context | ❌ | { prompt, systemPrompt?, maxTokens?, temperature? } | { text, engine: "remote"|"local", providerId?, modelId } |
ai.embed / ai.embeddings | ❌ | { text } | { embedding: number[], dimensions, modelId } |
ai.search / ai.rag_search | ❌ | { query, topK?, minSimilarity? } | { results: [{ noteId, noteTitle, chunkText, similarity, chunkIndex }], count } |
ai.index_document / ai.index | ❌ | { id, title, content } | { indexed: true, documentId, originalId, chunksCount } |
ai.remove_document / ai.remove | ❌ | { id } | { removed: true, documentId } |
{
"api": "chattree.context",
"operation": "read",
"payload": {}
}
success: true){
"success": true,
"data": {
"currentMessage": "Can you summarize /myplugin notes.md for our weekly sync?",
"explicitContext": "notes.md",
"authorized": true,
"restricted": false,
"timestamp": 1729278000000
}
}
currentMessage (string): The complete, untruncated text of the user prompt in which the plugin command was invoked. This allows tools to understand the conversational intent, natural language modifiers, and surrounding prompt context without granting access to the full historical chat tree.explicitContext (string): The specific string argument passed directly after the slash command. For example, if the prompt is Please run /calendar schedule team standup tomorrow, explicitContext will contain strictly "schedule team standup tomorrow", while currentMessage contains the entire sentence.authorized (boolean): Indicates whether the active user has granted the chattree.context.read capability to the plugin. If the user revokes or declines this permission, authorized is false, and currentMessage and explicitContext return empty strings ("").restricted (boolean): Evaluates to true if the conversation is running under restricted privacy mode (e.g., incognito chats, ephemeral trees, or system prompts containing confidential keys). In restricted mode, external plugins cannot inspect surrounding text even if authorized, preventing context leakage.timestamp (number): Unix epoch timestamp in milliseconds when the message was dispatched by the user.websocket NamespaceThe websocket namespace (WebSocketApiHandler) manages full-duplex WebSocket connections.
websocket.connectEstablishes a WebSocket connection with pre-reserved socket quota to prevent race conditions.
url (string, required): Target ws:// or wss:// endpoint.headers (object, optional): Custom headers for handshake.protocols (string[], optional): Subprotocols requested.pingIntervalSeconds (number, optional, default 30): Ping keep-alive interval.data:
connectionId (string): Unique identifier for this connection.status (string): "connected" or "connecting".url (string): Endpoint connected to.websocket.sendSends a text or binary message over the active socket.
connectionId (string, required): Identifier returned by connect.message (string, required): Text payload or Base64-encoded string for binary.isBinary (boolean, optional, default false): Set to true when sending binary data.data:
sent (boolean): true on successful dispatch.bytes (number): Number of bytes transferred.websocket.receive / websocket.pollDrains queued incoming messages from the in-memory socket buffer (up to 500 frames).
connectionId (string, required): Identifier returned by connect.maxMessages (number, optional, default 50): Maximum messages to retrieve in one call.data:
messages (array): Array of objects: [{ "data": string, "isBinary": boolean, "timestamp": string }].count (number): Number of messages drained.websocket.statusQueries current connection state and memory buffer statistics.
connectionId (string, required): Identifier returned by connect.data:
connectionId (string): The connection queried.state (string): "connected", "connecting", "closing", or "closed".bufferedAmount (number): Bytes queued in the native socket.queuedMessages (number): Number of unpolled messages in the memory queue.websocket.closeCloses an active socket cleanly with optional status code and reason string.
connectionId (string, required): Identifier returned by connect.code (number, optional, default 1000): WebSocket close code.reason (string, optional): Close reason string.data:
closed (boolean): true.connectionId (string): The connection closed.ui NamespaceThe ui namespace (UiApiHandler) enables plugins to emit and validate structured interactive UI cards and alerts.
ui.render / ui.card (Async)Sanitizes and renders an interactive UI card in the chat.
title (string, optional): Card headline (also accepts card properties at root or nested).subtitle (string, optional): Secondary caption.hideText (boolean, optional): When true, hides the plain-text intro bubble above the rendered card.elements (array, required): Array of UI element objects (max 60 elements).text: { "type": "text", "content": "Hello", "style": "body", "markdown": true }badge: { "type": "badge", "text": "OK", "variant": "success", "icon": "note" } (variants include info, success, warning, error, neutral, white, mono, monochrome, primary; icons include note/notes, alarm/reminder, etc.)progress: { "type": "progress", "value": 75, "label": "75%" }key_value: { "type": "key_value", "items": [{ "label": "Status", "value": "Active" }] } (max 40 items)list: { "type": "list", "items": [{ "title": "Note Title", "subtitle": "Preview snippet..." }] } (max 40 items; title max 100 chars, subtitle max 160 chars)divider: { "type": "divider" }button_row: { "type": "button_row", "buttons": [{ "label": "Run", "action": "command", "payload": "/mycommand", "variant": "primary" }] } (max 8 buttons; variants include primary, secondary, danger, white, mono, monochrome)data:
rendered (boolean): true.pluginId (string): The plugin ID.ui (object): Sanitized UI card payload.ui.alert / ui.toast (Async)Displays a lightweight alert or toast message.
message (string, required): Alert text.title (string, optional): Alert title.level (string, optional): Severity level (info, success, warning, error; default info).data:
type: "alert", title, message, level.ui.validate (Sync)Synchronously validates a UI card payload against schema and size limits without rendering.
data: { "valid": true }.scheduler NamespaceThe scheduler namespace (PluginTaskScheduler / SchedulerApiHandler) registers recurring Cron, interval-based, and one-shot background tasks.
scheduler.scheduleSchedules a recurring or one-shot task with anti-battery drain enforcement.
targetAction (string, required): Action or command name to dispatch when triggered (max 100 chars, [a-zA-Z0-9_.:-]+).cronExpression (string, optional): 5-field Cron expression (e.g., 0 * * * *, */15 * * * *).intervalMs (number, optional): Recurrent interval in milliseconds (minimum 60000 ms / 60 seconds).scheduledAt (string, optional): ISO-8601 timestamp for a one-shot task (e.g., 2026-11-15T15:30:00Z).payload (object, optional): Structured JSON payload dispatched with the event (max 128 KB).data:
scheduled (boolean): true.taskId (string): Generated unique task ID.targetAction (string): Registered target action.isPeriodic (boolean): Whether the task recurs.intervalMs (number, optional): Effective interval in ms.createdAt (string): ISO-8601 creation timestamp.scheduler.cancelCancels and removes an active scheduled task.
taskId (string, required): Task ID to cancel.data:
cancelled (boolean): true.taskId (string): The cancelled task ID.scheduler.listLists all active scheduled tasks for this plugin.
{}data:
tasks (array): Array of registered task descriptors.count (number): Total tasks registered.maxAllowed (number): Maximum allowed active tasks per plugin (25).scheduler.statusRetrieves the current state of a single scheduled task.
taskId (string, required): Task ID to inspect.data: Full task descriptor object.database NamespaceThe database namespace (PluginDatabaseManager / DatabaseApiHandler) provides an isolated relational SQLite database (plugin_<pluginId>.db) with 200 MB quota and atomic transactions.
database.queryExecutes a SQL SELECT query returning rows and columns.
sql (string, required): SQL query string (e.g. SELECT * FROM items WHERE status = ?).params (array, optional): Positional query parameters.data:
rows (array): Array of result row objects (up to 2,000 rows).rowCount (number): Number of returned rows.columns (string[]): Column names.database.executeExecutes a DDL or DML statement (CREATE TABLE, INSERT, UPDATE, DELETE).
sql (string, required): SQL statement string.params (array, optional): Parameter bindings.data:
changes (number): Rows modified or inserted.lastInsertRowId (number, optional): Auto-increment primary key ID.database.transactionExecutes an atomic batch transaction with automatic rollback if any statement fails.
statements (array, required): Array of { "sql": string, "params"?: any[] }.data:
success (boolean): true.results (array): Execution result summary per statement.database.tablesInspects all user tables created within the plugin's private database.
{}data:
tables (string[]): List of table names.When your exported function finishes, it must:
i32.Plain text (simplest):
Hello, World!
Chattree wraps this in ToolResult.direct(text).
Explicit JSON contract:
{ "type": "direct", "displayText": "Hello, World!" }
Error output:
{ "type": "error", "errorMessage": "Something went wrong." }
Permission denied output (for plugins that detect missing permissions):
{
"type": "permissionDenied",
"missingCapabilities": ["network.http"],
"operation": "network.http_request"
}
Declarative UI Card output:
{
"type": "card",
"displayText": "Task Summary",
"ui": {
"title": "System Monitor",
"elements": [
{ "type": "badge", "text": "ONLINE", "variant": "success" },
{ "type": "progress", "value": 45, "label": "CPU Load 45%" }
]
}
}
Generic success/error (alternative form):
{ "success": true, "message": "Done!" }
{ "success": false, "error": "Failed to connect." }