@kanvas/openclaw-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -0
- package/dist/cli/setup.js +75 -0
- package/dist/client/headers.js +25 -0
- package/dist/client/kanvas-client.js +137 -0
- package/dist/client/multipart.js +37 -0
- package/dist/client/types.js +1 -0
- package/dist/config/env.js +18 -0
- package/dist/config/types.js +1 -0
- package/dist/domains/crm/index.js +590 -0
- package/dist/domains/crm/types.js +1 -0
- package/dist/domains/inventory/index.js +199 -0
- package/dist/domains/orders/index.js +57 -0
- package/dist/domains/social/index.js +374 -0
- package/dist/domains/social/types.js +1 -0
- package/dist/index.js +179 -0
- package/dist/tools/crm.js +343 -0
- package/dist/tools/helpers.js +7 -0
- package/dist/tools/inventory.js +99 -0
- package/dist/tools/orders.js +29 -0
- package/dist/tools/social.js +171 -0
- package/openclaw.plugin.json +64 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { KanvasClient } from "./client/kanvas-client.js";
|
|
4
|
+
import { CrmService } from "./domains/crm/index.js";
|
|
5
|
+
import { InventoryService } from "./domains/inventory/index.js";
|
|
6
|
+
import { OrdersService } from "./domains/orders/index.js";
|
|
7
|
+
import { SocialService } from "./domains/social/index.js";
|
|
8
|
+
import { registerCrmTools } from "./tools/crm.js";
|
|
9
|
+
import { registerInventoryTools } from "./tools/inventory.js";
|
|
10
|
+
import { registerOrdersTools } from "./tools/orders.js";
|
|
11
|
+
import { registerSocialTools } from "./tools/social.js";
|
|
12
|
+
import { toolResult } from "./tools/helpers.js";
|
|
13
|
+
const DEFAULT_API_URL = "https://graphapi.kanvas.dev/graphql";
|
|
14
|
+
function resolveConfig(pluginConfig) {
|
|
15
|
+
const cfg = pluginConfig ?? {};
|
|
16
|
+
const apiUrl = cfg.apiUrl || process.env.KANVAS_API_URL || DEFAULT_API_URL;
|
|
17
|
+
const xKanvasApp = cfg.xKanvasApp || process.env.KANVAS_X_APP;
|
|
18
|
+
if (!xKanvasApp)
|
|
19
|
+
throw new Error("Kanvas plugin: xKanvasApp is required (config or KANVAS_X_APP env)");
|
|
20
|
+
const email = cfg.email || process.env.KANVAS_EMAIL;
|
|
21
|
+
const password = cfg.password || process.env.KANVAS_PASSWORD;
|
|
22
|
+
const bearerToken = cfg.bearerToken || process.env.KANVAS_BEARER_TOKEN;
|
|
23
|
+
const xKanvasKey = cfg.xKanvasKey || process.env.KANVAS_X_KEY;
|
|
24
|
+
// Determine auth mode: email/password → bearer (after login), explicit bearer, or app-key
|
|
25
|
+
let authMode;
|
|
26
|
+
if (email && password) {
|
|
27
|
+
authMode = "bearer"; // will authenticate at first use
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
authMode = (cfg.authMode || process.env.KANVAS_AUTH_MODE || "bearer");
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
apiUrl,
|
|
34
|
+
xKanvasApp,
|
|
35
|
+
xKanvasLocation: cfg.xKanvasLocation || process.env.KANVAS_X_LOCATION,
|
|
36
|
+
authMode,
|
|
37
|
+
bearerToken,
|
|
38
|
+
xKanvasKey,
|
|
39
|
+
email,
|
|
40
|
+
password,
|
|
41
|
+
timeoutMs: cfg.timeoutMs || (process.env.KANVAS_TIMEOUT_MS ? Number(process.env.KANVAS_TIMEOUT_MS) : 15000),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a guard that ensures the client is authenticated before any tool executes.
|
|
46
|
+
* If email/password are configured, logs in once and caches the session.
|
|
47
|
+
* If a bearer token is already set, does nothing.
|
|
48
|
+
*/
|
|
49
|
+
function createAuthGuard(client, config, logger) {
|
|
50
|
+
let authPromise = null;
|
|
51
|
+
return function ensureAuth() {
|
|
52
|
+
if (authPromise)
|
|
53
|
+
return authPromise;
|
|
54
|
+
if (config.email && config.password && !config.bearerToken) {
|
|
55
|
+
authPromise = client.login(config.email, config.password).then((session) => {
|
|
56
|
+
logger.info(`Kanvas: authenticated as user ${session.uuid}`);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
authPromise = Promise.resolve();
|
|
61
|
+
}
|
|
62
|
+
return authPromise;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export default definePluginEntry({
|
|
66
|
+
id: "kanvas",
|
|
67
|
+
name: "Kanvas CRM",
|
|
68
|
+
description: "Connects agents to Kanvas — your company's nervous system for CRM, inventory, orders, and messaging.",
|
|
69
|
+
register(api) {
|
|
70
|
+
const config = resolveConfig(api.pluginConfig);
|
|
71
|
+
const client = new KanvasClient(config);
|
|
72
|
+
const ensureAuth = createAuthGuard(client, config, api.logger);
|
|
73
|
+
const crm = new CrmService(client);
|
|
74
|
+
const inventory = new InventoryService(client);
|
|
75
|
+
const orders = new OrdersService(client);
|
|
76
|
+
const social = new SocialService(client);
|
|
77
|
+
registerCrmTools(api, crm, ensureAuth);
|
|
78
|
+
registerInventoryTools(api, inventory, ensureAuth);
|
|
79
|
+
registerOrdersTools(api, orders, ensureAuth);
|
|
80
|
+
registerSocialTools(api, social, ensureAuth);
|
|
81
|
+
api.registerTool({
|
|
82
|
+
name: "kanvas_test_connection",
|
|
83
|
+
label: "Test Connection",
|
|
84
|
+
description: "Test the connection to the Kanvas GraphQL API.",
|
|
85
|
+
parameters: Type.Object({}),
|
|
86
|
+
async execute() {
|
|
87
|
+
await ensureAuth();
|
|
88
|
+
return toolResult(await client.testConnection());
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
// Inject Kanvas context into the agent's system prompt so it knows
|
|
92
|
+
// what these tools are for and how to use them together.
|
|
93
|
+
api.on("before_prompt_build", () => ({
|
|
94
|
+
appendSystemContext: KANVAS_SYSTEM_CONTEXT,
|
|
95
|
+
}));
|
|
96
|
+
// Register `openclaw kanvas setup` CLI command for interactive configuration.
|
|
97
|
+
api.registerCli((ctx) => {
|
|
98
|
+
ctx.program
|
|
99
|
+
.command("setup")
|
|
100
|
+
.description("Interactive setup — configure Kanvas credentials and test the connection")
|
|
101
|
+
.action(async () => {
|
|
102
|
+
const { runSetup } = await import("./cli/setup.js");
|
|
103
|
+
await runSetup();
|
|
104
|
+
});
|
|
105
|
+
}, { commands: ["setup"] });
|
|
106
|
+
api.logger.info("Kanvas plugin registered — 41 tools loaded");
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const KANVAS_SYSTEM_CONTEXT = `
|
|
110
|
+
## Kanvas Plugin
|
|
111
|
+
|
|
112
|
+
You have access to Kanvas — the company's nervous system. Kanvas is the operational engine that connects all business data, tools, and workflows. Through the \`kanvas_*\` tools you can directly operate on CRM, inventory, orders, and messaging. Don't just describe what you'd do — use these tools to actually do it.
|
|
113
|
+
|
|
114
|
+
### Domains & When to Use
|
|
115
|
+
|
|
116
|
+
**CRM (Leads)**
|
|
117
|
+
Use when the user asks about leads, prospects, sales pipeline, contacts, or follow-ups.
|
|
118
|
+
- \`kanvas_search_leads\` → find leads by name, email, or keyword
|
|
119
|
+
- \`kanvas_get_lead\` → full lead detail (pipeline stage, owner, receiver, participants, files, events, messages)
|
|
120
|
+
- \`kanvas_create_lead\` → new lead (requires: title, pipeline_stage_id, people with firstname/lastname)
|
|
121
|
+
- \`kanvas_update_lead\` → edit lead fields (requires: id, branch_id, people_id)
|
|
122
|
+
- \`kanvas_mark_lead_outcome\` → close a lead as Won, Lost, or Close
|
|
123
|
+
- \`kanvas_change_lead_owner\` / \`kanvas_change_lead_receiver\` → reassign leads
|
|
124
|
+
- \`kanvas_add_lead_participant\` / \`kanvas_remove_lead_participant\` → manage related people
|
|
125
|
+
- \`kanvas_follow_lead\` / \`kanvas_unfollow_lead\` → subscribe to lead updates
|
|
126
|
+
- \`kanvas_delete_lead\` / \`kanvas_restore_lead\` → soft-delete and restore
|
|
127
|
+
|
|
128
|
+
**CRM Support Data** — call these first when creating/updating leads to get valid IDs:
|
|
129
|
+
- \`kanvas_list_pipelines\` → pipelines and their stages (needed for pipeline_stage_id)
|
|
130
|
+
- \`kanvas_list_lead_statuses\` → available statuses
|
|
131
|
+
- \`kanvas_list_lead_sources\` → where leads come from
|
|
132
|
+
- \`kanvas_list_lead_types\` → lead categories
|
|
133
|
+
|
|
134
|
+
**Messages & Notes**
|
|
135
|
+
Use when the user asks about notes, comments, conversations, or activity on a lead.
|
|
136
|
+
- \`kanvas_add_lead_message\` → add a note to a lead channel (needs channel_slug)
|
|
137
|
+
- \`kanvas_add_lead_note_by_lead_id\` → add a note by lead ID (auto-resolves channel)
|
|
138
|
+
- \`kanvas_list_lead_messages\` → read conversation history for a lead channel
|
|
139
|
+
- \`kanvas_get_lead_primary_channel_slug\` → find the main channel for a lead
|
|
140
|
+
|
|
141
|
+
**Social / Messages (NoSQL-like storage)**
|
|
142
|
+
Use when the user wants to store, query, or manage structured data as messages. The message field accepts any JSON.
|
|
143
|
+
- \`kanvas_create_message\` → store any JSON document with a message_verb type and optional channel/entity linking
|
|
144
|
+
- \`kanvas_get_message\` → retrieve a message with all metadata
|
|
145
|
+
- \`kanvas_update_message\` → modify message content or metadata
|
|
146
|
+
- \`kanvas_delete_message\` → soft-delete
|
|
147
|
+
- \`kanvas_list_channel_messages\` → list messages in a channel
|
|
148
|
+
- \`kanvas_search_messages\` → search/filter by type (verb), channel, or linked entity
|
|
149
|
+
- \`kanvas_list_message_types\` → see available message verbs
|
|
150
|
+
- \`kanvas_create_message_type\` → define a new verb with optional template
|
|
151
|
+
|
|
152
|
+
**Email**
|
|
153
|
+
- \`kanvas_send_anonymous_email\` → send email to any address using a template (no Kanvas account needed for recipient)
|
|
154
|
+
|
|
155
|
+
**Inventory**
|
|
156
|
+
Use when the user asks about products, stock, warehouses, or catalog.
|
|
157
|
+
- \`kanvas_search_products\` → find products by keyword
|
|
158
|
+
- \`kanvas_get_product\` → full product detail with variants, categories, warehouses
|
|
159
|
+
- \`kanvas_list_variants\` → product variants with pricing, stock, attributes
|
|
160
|
+
- \`kanvas_list_warehouses\` → stock locations
|
|
161
|
+
- \`kanvas_list_channels\` → sales channels
|
|
162
|
+
- \`kanvas_list_categories\` → product categories
|
|
163
|
+
- \`kanvas_list_inventory_statuses\` → product statuses
|
|
164
|
+
|
|
165
|
+
**Orders**
|
|
166
|
+
Use when the user asks about orders, purchases, or sales.
|
|
167
|
+
- \`kanvas_search_orders\` → find orders by number or keyword
|
|
168
|
+
- \`kanvas_get_order\` → full order detail with items, customer, status
|
|
169
|
+
|
|
170
|
+
**Diagnostics**
|
|
171
|
+
- \`kanvas_test_connection\` → verify the API is reachable
|
|
172
|
+
|
|
173
|
+
### Key Conventions
|
|
174
|
+
- IDs are numeric strings. When a tool returns an ID, use it as-is in subsequent calls.
|
|
175
|
+
- Leads live in pipelines with stages. Always check \`kanvas_list_pipelines\` to get valid stage IDs before creating leads.
|
|
176
|
+
- Messages use \`message_verb\` to define their type (e.g. "comment", "note", "sms"). New verbs are auto-created.
|
|
177
|
+
- The \`message\` field in social messages accepts arbitrary JSON — use it to store any structured data.
|
|
178
|
+
- Filtering uses \`where: [{ column, operator, value }]\` format. Common operators: "EQ", "LIKE", "IN".
|
|
179
|
+
`.trim();
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
export function registerCrmTools(api, service, ensureAuth) {
|
|
4
|
+
api.registerTool({
|
|
5
|
+
name: "kanvas_search_leads",
|
|
6
|
+
label: "Search Leads",
|
|
7
|
+
description: "Search leads by keyword in the Kanvas CRM.",
|
|
8
|
+
parameters: Type.Object({
|
|
9
|
+
search: Type.String({ description: "Search keyword" }),
|
|
10
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
|
|
11
|
+
}),
|
|
12
|
+
async execute(_id, params) {
|
|
13
|
+
await ensureAuth();
|
|
14
|
+
return toolResult(await service.searchLeads(params.search, params.first));
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
api.registerTool({
|
|
18
|
+
name: "kanvas_get_lead",
|
|
19
|
+
label: "Get Lead",
|
|
20
|
+
description: "Get full details for a single lead by ID, including channels, participants, events, and files.",
|
|
21
|
+
parameters: Type.Object({
|
|
22
|
+
id: Type.String({ description: "Lead ID" }),
|
|
23
|
+
}),
|
|
24
|
+
async execute(_id, params) {
|
|
25
|
+
await ensureAuth();
|
|
26
|
+
return toolResult(await service.getLead(params.id));
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
api.registerTool({
|
|
30
|
+
name: "kanvas_create_lead",
|
|
31
|
+
label: "Create Lead",
|
|
32
|
+
description: "Create a new lead in the Kanvas CRM.",
|
|
33
|
+
parameters: Type.Object({
|
|
34
|
+
title: Type.String({ description: "Lead title" }),
|
|
35
|
+
pipeline_stage_id: Type.Number({ description: "Pipeline stage ID" }),
|
|
36
|
+
people: Type.Object({
|
|
37
|
+
firstname: Type.String(),
|
|
38
|
+
lastname: Type.String(),
|
|
39
|
+
contacts: Type.Optional(Type.Array(Type.Object({
|
|
40
|
+
value: Type.String(),
|
|
41
|
+
contacts_types_id: Type.Number(),
|
|
42
|
+
weight: Type.Optional(Type.Number()),
|
|
43
|
+
}))),
|
|
44
|
+
}, { description: "Contact person details" }),
|
|
45
|
+
branch_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
46
|
+
leads_owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
47
|
+
receiver_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
48
|
+
status_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
49
|
+
type_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
50
|
+
source_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
51
|
+
description: Type.Optional(Type.String()),
|
|
52
|
+
reason_lost: Type.Optional(Type.String()),
|
|
53
|
+
organization: Type.Optional(Type.Object({ name: Type.String() })),
|
|
54
|
+
custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()))),
|
|
55
|
+
}),
|
|
56
|
+
async execute(_id, params) {
|
|
57
|
+
await ensureAuth();
|
|
58
|
+
return toolResult(await service.createLead(params));
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
api.registerTool({
|
|
62
|
+
name: "kanvas_update_lead",
|
|
63
|
+
label: "Update Lead",
|
|
64
|
+
description: "Update an existing lead's fields.",
|
|
65
|
+
parameters: Type.Object({
|
|
66
|
+
id: Type.String({ description: "Lead ID" }),
|
|
67
|
+
input: Type.Object({
|
|
68
|
+
branch_id: Type.Union([Type.String(), Type.Number()]),
|
|
69
|
+
people_id: Type.Union([Type.String(), Type.Number()]),
|
|
70
|
+
title: Type.Optional(Type.String()),
|
|
71
|
+
leads_owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
72
|
+
organization_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
73
|
+
receiver_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
74
|
+
status_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
75
|
+
type_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
76
|
+
source_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
77
|
+
description: Type.Optional(Type.String()),
|
|
78
|
+
reason_lost: Type.Optional(Type.String()),
|
|
79
|
+
pipeline_stage_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
80
|
+
custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()))),
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
async execute(_id, params) {
|
|
84
|
+
await ensureAuth();
|
|
85
|
+
return toolResult(await service.updateLead(params.id, params.input));
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
api.registerTool({
|
|
89
|
+
name: "kanvas_change_lead_owner",
|
|
90
|
+
label: "Change Lead Owner",
|
|
91
|
+
description: "Change the owner of a lead.",
|
|
92
|
+
parameters: Type.Object({
|
|
93
|
+
leadId: Type.Union([Type.String(), Type.Number()], { description: "Lead ID" }),
|
|
94
|
+
branch_id: Type.Union([Type.String(), Type.Number()]),
|
|
95
|
+
people_id: Type.Union([Type.String(), Type.Number()]),
|
|
96
|
+
title: Type.Optional(Type.String()),
|
|
97
|
+
description: Type.Optional(Type.String()),
|
|
98
|
+
leads_owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "New owner ID" })),
|
|
99
|
+
}),
|
|
100
|
+
async execute(_id, params) {
|
|
101
|
+
await ensureAuth();
|
|
102
|
+
return toolResult(await service.changeLeadOwner(params));
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
api.registerTool({
|
|
106
|
+
name: "kanvas_change_lead_receiver",
|
|
107
|
+
label: "Change Lead Receiver",
|
|
108
|
+
description: "Change the receiver of a lead.",
|
|
109
|
+
parameters: Type.Object({
|
|
110
|
+
leadId: Type.Union([Type.String(), Type.Number()], { description: "Lead ID" }),
|
|
111
|
+
branch_id: Type.Union([Type.String(), Type.Number()]),
|
|
112
|
+
people_id: Type.Union([Type.String(), Type.Number()]),
|
|
113
|
+
title: Type.Optional(Type.String()),
|
|
114
|
+
description: Type.Optional(Type.String()),
|
|
115
|
+
receiver_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "New receiver ID" })),
|
|
116
|
+
}),
|
|
117
|
+
async execute(_id, params) {
|
|
118
|
+
await ensureAuth();
|
|
119
|
+
return toolResult(await service.changeLeadReceiver(params));
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
api.registerTool({
|
|
123
|
+
name: "kanvas_add_lead_participant",
|
|
124
|
+
label: "Add Lead Participant",
|
|
125
|
+
description: "Add a participant to a lead.",
|
|
126
|
+
parameters: Type.Object({
|
|
127
|
+
lead_id: Type.Number({ description: "Lead ID" }),
|
|
128
|
+
people_id: Type.Number({ description: "Person ID" }),
|
|
129
|
+
relationship_id: Type.Number({ description: "Relationship type ID" }),
|
|
130
|
+
}),
|
|
131
|
+
async execute(_id, params) {
|
|
132
|
+
await ensureAuth();
|
|
133
|
+
return toolResult(await service.addLeadParticipant(params));
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
api.registerTool({
|
|
137
|
+
name: "kanvas_remove_lead_participant",
|
|
138
|
+
label: "Remove Lead Participant",
|
|
139
|
+
description: "Remove a participant from a lead.",
|
|
140
|
+
parameters: Type.Object({
|
|
141
|
+
lead_id: Type.Number({ description: "Lead ID" }),
|
|
142
|
+
people_id: Type.Number({ description: "Person ID" }),
|
|
143
|
+
relationship_id: Type.Number({ description: "Relationship type ID" }),
|
|
144
|
+
}),
|
|
145
|
+
async execute(_id, params) {
|
|
146
|
+
await ensureAuth();
|
|
147
|
+
return toolResult(await service.removeLeadParticipant(params));
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
api.registerTool({
|
|
151
|
+
name: "kanvas_follow_lead",
|
|
152
|
+
label: "Follow Lead",
|
|
153
|
+
description: "Follow a lead to receive updates.",
|
|
154
|
+
parameters: Type.Object({
|
|
155
|
+
entity_id: Type.String({ description: "Lead entity ID" }),
|
|
156
|
+
user_id: Type.Union([Type.String(), Type.Number()], { description: "User ID" }),
|
|
157
|
+
}),
|
|
158
|
+
async execute(_id, params) {
|
|
159
|
+
await ensureAuth();
|
|
160
|
+
return toolResult(await service.followLead(params));
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
api.registerTool({
|
|
164
|
+
name: "kanvas_unfollow_lead",
|
|
165
|
+
label: "Unfollow Lead",
|
|
166
|
+
description: "Unfollow a lead to stop receiving updates.",
|
|
167
|
+
parameters: Type.Object({
|
|
168
|
+
entity_id: Type.String({ description: "Lead entity ID" }),
|
|
169
|
+
user_id: Type.Union([Type.String(), Type.Number()], { description: "User ID" }),
|
|
170
|
+
}),
|
|
171
|
+
async execute(_id, params) {
|
|
172
|
+
await ensureAuth();
|
|
173
|
+
return toolResult(await service.unFollowLead(params));
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
api.registerTool({
|
|
177
|
+
name: "kanvas_delete_lead",
|
|
178
|
+
label: "Delete Lead",
|
|
179
|
+
description: "Soft-delete a lead.",
|
|
180
|
+
parameters: Type.Object({
|
|
181
|
+
id: Type.String({ description: "Lead ID" }),
|
|
182
|
+
}),
|
|
183
|
+
async execute(_id, params) {
|
|
184
|
+
await ensureAuth();
|
|
185
|
+
return toolResult(await service.deleteLead(params.id));
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
api.registerTool({
|
|
189
|
+
name: "kanvas_restore_lead",
|
|
190
|
+
label: "Restore Lead",
|
|
191
|
+
description: "Restore a previously deleted lead.",
|
|
192
|
+
parameters: Type.Object({
|
|
193
|
+
id: Type.String({ description: "Lead ID" }),
|
|
194
|
+
}),
|
|
195
|
+
async execute(_id, params) {
|
|
196
|
+
await ensureAuth();
|
|
197
|
+
return toolResult(await service.restoreLead(params.id));
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
api.registerTool({
|
|
201
|
+
name: "kanvas_mark_lead_outcome",
|
|
202
|
+
label: "Mark Lead Outcome",
|
|
203
|
+
description: "Mark a lead as Won, Lost, or Closed.",
|
|
204
|
+
parameters: Type.Object({
|
|
205
|
+
id: Type.String({ description: "Lead ID" }),
|
|
206
|
+
status: Type.Union([Type.Literal("Won"), Type.Literal("Lost"), Type.Literal("Close")], {
|
|
207
|
+
description: "Outcome status",
|
|
208
|
+
}),
|
|
209
|
+
reason_lost: Type.Optional(Type.String({ description: "Reason for losing (if status is Lost)" })),
|
|
210
|
+
}),
|
|
211
|
+
async execute(_id, params) {
|
|
212
|
+
await ensureAuth();
|
|
213
|
+
return toolResult(await service.markLeadOutcome(params.id, params.status, params.reason_lost));
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
api.registerTool({
|
|
217
|
+
name: "kanvas_create_lead_appointment",
|
|
218
|
+
label: "Create Lead Appointment",
|
|
219
|
+
description: "Create a calendar event/appointment linked to a lead.",
|
|
220
|
+
parameters: Type.Object({
|
|
221
|
+
name: Type.String({ description: "Appointment name" }),
|
|
222
|
+
description: Type.Optional(Type.String()),
|
|
223
|
+
resources: Type.Array(Type.Object({
|
|
224
|
+
resources_id: Type.Union([Type.String(), Type.Number()]),
|
|
225
|
+
resources_type: Type.String(),
|
|
226
|
+
custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()))),
|
|
227
|
+
})),
|
|
228
|
+
dates: Type.Array(Type.Object({
|
|
229
|
+
date: Type.String({ description: "Date (YYYY-MM-DD)" }),
|
|
230
|
+
start_time: Type.String({ description: "Start time (HH:MM)" }),
|
|
231
|
+
end_time: Type.String({ description: "End time (HH:MM)" }),
|
|
232
|
+
})),
|
|
233
|
+
}),
|
|
234
|
+
async execute(_id, params) {
|
|
235
|
+
await ensureAuth();
|
|
236
|
+
return toolResult(await service.createLeadAppointment(params));
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
api.registerTool({
|
|
240
|
+
name: "kanvas_add_lead_message",
|
|
241
|
+
label: "Add Lead Message",
|
|
242
|
+
description: "Add a message/note to a lead's channel by channel slug.",
|
|
243
|
+
parameters: Type.Object({
|
|
244
|
+
channel_slug: Type.String({ description: "Channel slug" }),
|
|
245
|
+
message: Type.String({ description: "Message text" }),
|
|
246
|
+
parent_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Parent message ID for threading" })),
|
|
247
|
+
is_public: Type.Optional(Type.Number({ description: "1 = public, 0 = internal" })),
|
|
248
|
+
}),
|
|
249
|
+
async execute(_id, params) {
|
|
250
|
+
await ensureAuth();
|
|
251
|
+
return toolResult(await service.addLeadMessage(params));
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
api.registerTool({
|
|
255
|
+
name: "kanvas_add_lead_note_by_lead_id",
|
|
256
|
+
label: "Add Lead Note by Lead ID",
|
|
257
|
+
description: "Add a message/note to a lead by lead ID. Automatically resolves the primary channel.",
|
|
258
|
+
parameters: Type.Object({
|
|
259
|
+
leadId: Type.Union([Type.String(), Type.Number()], { description: "Lead ID" }),
|
|
260
|
+
message: Type.String({ description: "Message text" }),
|
|
261
|
+
parent_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
262
|
+
is_public: Type.Optional(Type.Number({ description: "1 = public, 0 = internal" })),
|
|
263
|
+
}),
|
|
264
|
+
async execute(_id, params) {
|
|
265
|
+
await ensureAuth();
|
|
266
|
+
return toolResult(await service.addLeadMessageByLeadId(params));
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
api.registerTool({
|
|
270
|
+
name: "kanvas_list_lead_messages",
|
|
271
|
+
label: "List Lead Messages",
|
|
272
|
+
description: "List messages/notes in a lead's channel.",
|
|
273
|
+
parameters: Type.Object({
|
|
274
|
+
channel_slug: Type.String({ description: "Channel slug" }),
|
|
275
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
276
|
+
page: Type.Optional(Type.Number({ description: "Page number (default 1)" })),
|
|
277
|
+
}),
|
|
278
|
+
async execute(_id, params) {
|
|
279
|
+
await ensureAuth();
|
|
280
|
+
return toolResult(await service.listLeadMessages(params.channel_slug, params.first, params.page));
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
api.registerTool({
|
|
284
|
+
name: "kanvas_get_lead_primary_channel_slug",
|
|
285
|
+
label: "Get Lead Primary Channel",
|
|
286
|
+
description: "Get the primary channel slug for a lead.",
|
|
287
|
+
parameters: Type.Object({
|
|
288
|
+
leadId: Type.String({ description: "Lead ID" }),
|
|
289
|
+
}),
|
|
290
|
+
async execute(_id, params) {
|
|
291
|
+
await ensureAuth();
|
|
292
|
+
return toolResult(await service.getLeadPrimaryChannelSlug(params.leadId));
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
api.registerTool({
|
|
296
|
+
name: "kanvas_list_pipelines",
|
|
297
|
+
label: "List Pipelines",
|
|
298
|
+
description: "List all pipelines and their stages.",
|
|
299
|
+
parameters: Type.Object({
|
|
300
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
301
|
+
}),
|
|
302
|
+
async execute(_id, params) {
|
|
303
|
+
await ensureAuth();
|
|
304
|
+
return toolResult(await service.listPipelines(params.first));
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
api.registerTool({
|
|
308
|
+
name: "kanvas_list_lead_statuses",
|
|
309
|
+
label: "List Lead Statuses",
|
|
310
|
+
description: "List available lead statuses.",
|
|
311
|
+
parameters: Type.Object({
|
|
312
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
313
|
+
}),
|
|
314
|
+
async execute(_id, params) {
|
|
315
|
+
await ensureAuth();
|
|
316
|
+
return toolResult(await service.listLeadStatuses(params.first));
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
api.registerTool({
|
|
320
|
+
name: "kanvas_list_lead_sources",
|
|
321
|
+
label: "List Lead Sources",
|
|
322
|
+
description: "List available lead sources.",
|
|
323
|
+
parameters: Type.Object({
|
|
324
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
325
|
+
}),
|
|
326
|
+
async execute(_id, params) {
|
|
327
|
+
await ensureAuth();
|
|
328
|
+
return toolResult(await service.listLeadSources(params.first));
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
api.registerTool({
|
|
332
|
+
name: "kanvas_list_lead_types",
|
|
333
|
+
label: "List Lead Types",
|
|
334
|
+
description: "List available lead types.",
|
|
335
|
+
parameters: Type.Object({
|
|
336
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
337
|
+
}),
|
|
338
|
+
async execute(_id, params) {
|
|
339
|
+
await ensureAuth();
|
|
340
|
+
return toolResult(await service.listLeadTypes(params.first));
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
const WhereClause = Type.Optional(Type.Array(Type.Object({
|
|
4
|
+
column: Type.String(),
|
|
5
|
+
operator: Type.String({ description: 'e.g. "EQ", "LIKE"' }),
|
|
6
|
+
value: Type.Unknown(),
|
|
7
|
+
}), { description: "Filter conditions" }));
|
|
8
|
+
export function registerInventoryTools(api, service, ensureAuth) {
|
|
9
|
+
api.registerTool({
|
|
10
|
+
name: "kanvas_search_products",
|
|
11
|
+
label: "Search Products",
|
|
12
|
+
description: "Search products by keyword in the Kanvas inventory.",
|
|
13
|
+
parameters: Type.Object({
|
|
14
|
+
search: Type.String({ description: "Search keyword" }),
|
|
15
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
|
|
16
|
+
}),
|
|
17
|
+
async execute(_id, params) {
|
|
18
|
+
await ensureAuth();
|
|
19
|
+
return toolResult(await service.searchProducts(params.search, params.first));
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
api.registerTool({
|
|
23
|
+
name: "kanvas_get_product",
|
|
24
|
+
label: "Get Product",
|
|
25
|
+
description: "Get full details for a product by ID, including variants and warehouses.",
|
|
26
|
+
parameters: Type.Object({
|
|
27
|
+
id: Type.String({ description: "Product ID" }),
|
|
28
|
+
}),
|
|
29
|
+
async execute(_id, params) {
|
|
30
|
+
await ensureAuth();
|
|
31
|
+
return toolResult(await service.getProduct(params.id));
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
api.registerTool({
|
|
35
|
+
name: "kanvas_list_variants",
|
|
36
|
+
label: "List Variants",
|
|
37
|
+
description: "List product variants with optional filtering.",
|
|
38
|
+
parameters: Type.Object({
|
|
39
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
|
|
40
|
+
where: WhereClause,
|
|
41
|
+
}),
|
|
42
|
+
async execute(_id, params) {
|
|
43
|
+
await ensureAuth();
|
|
44
|
+
return toolResult(await service.listVariants(params.first, params.where));
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
api.registerTool({
|
|
48
|
+
name: "kanvas_list_warehouses",
|
|
49
|
+
label: "List Warehouses",
|
|
50
|
+
description: "List warehouses with optional filtering.",
|
|
51
|
+
parameters: Type.Object({
|
|
52
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
53
|
+
where: WhereClause,
|
|
54
|
+
}),
|
|
55
|
+
async execute(_id, params) {
|
|
56
|
+
await ensureAuth();
|
|
57
|
+
return toolResult(await service.listWarehouses(params.first, params.where));
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
api.registerTool({
|
|
61
|
+
name: "kanvas_list_channels",
|
|
62
|
+
label: "List Sales Channels",
|
|
63
|
+
description: "List sales channels with optional filtering.",
|
|
64
|
+
parameters: Type.Object({
|
|
65
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
66
|
+
where: WhereClause,
|
|
67
|
+
}),
|
|
68
|
+
async execute(_id, params) {
|
|
69
|
+
await ensureAuth();
|
|
70
|
+
return toolResult(await service.listChannels(params.first, params.where));
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
api.registerTool({
|
|
74
|
+
name: "kanvas_list_categories",
|
|
75
|
+
label: "List Categories",
|
|
76
|
+
description: "List product categories with optional filtering.",
|
|
77
|
+
parameters: Type.Object({
|
|
78
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
79
|
+
where: WhereClause,
|
|
80
|
+
}),
|
|
81
|
+
async execute(_id, params) {
|
|
82
|
+
await ensureAuth();
|
|
83
|
+
return toolResult(await service.listCategories(params.first, params.where));
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
api.registerTool({
|
|
87
|
+
name: "kanvas_list_inventory_statuses",
|
|
88
|
+
label: "List Inventory Statuses",
|
|
89
|
+
description: "List inventory statuses with optional filtering.",
|
|
90
|
+
parameters: Type.Object({
|
|
91
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
92
|
+
where: WhereClause,
|
|
93
|
+
}),
|
|
94
|
+
async execute(_id, params) {
|
|
95
|
+
await ensureAuth();
|
|
96
|
+
return toolResult(await service.listStatuses(params.first, params.where));
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
export function registerOrdersTools(api, service, ensureAuth) {
|
|
4
|
+
api.registerTool({
|
|
5
|
+
name: "kanvas_search_orders",
|
|
6
|
+
label: "Search Orders",
|
|
7
|
+
description: "Search orders by keyword.",
|
|
8
|
+
parameters: Type.Object({
|
|
9
|
+
search: Type.String({ description: "Search keyword" }),
|
|
10
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
|
|
11
|
+
}),
|
|
12
|
+
async execute(_id, params) {
|
|
13
|
+
await ensureAuth();
|
|
14
|
+
return toolResult(await service.searchOrders(params.search, params.first));
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
api.registerTool({
|
|
18
|
+
name: "kanvas_get_order",
|
|
19
|
+
label: "Get Order",
|
|
20
|
+
description: "Get full details for an order by ID, including items and customer info.",
|
|
21
|
+
parameters: Type.Object({
|
|
22
|
+
id: Type.String({ description: "Order ID" }),
|
|
23
|
+
}),
|
|
24
|
+
async execute(_id, params) {
|
|
25
|
+
await ensureAuth();
|
|
26
|
+
return toolResult(await service.getOrder(params.id));
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|