@kanvas/openclaw-plugin 0.1.16 → 0.1.17

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.
@@ -0,0 +1,103 @@
1
+ export class DealsService {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ async listDeals(first = 25, search, where) {
7
+ const query = `
8
+ query ListDeals($first: Int, $search: String, $where: QueryDealsWhereWhereConditions) {
9
+ deals(first: $first, search: $search, where: $where) {
10
+ data {
11
+ id
12
+ uuid
13
+ title
14
+ description
15
+ created_at
16
+ updated_at
17
+ owner { id uuid displayname }
18
+ lead { id uuid }
19
+ people { id uuid firstname lastname }
20
+ organization { id name }
21
+ pipeline { id name }
22
+ stage { id name }
23
+ status { id name }
24
+ }
25
+ paginatorInfo {
26
+ currentPage
27
+ lastPage
28
+ total
29
+ }
30
+ }
31
+ }
32
+ `;
33
+ return this.client.query(query, { first, search, where });
34
+ }
35
+ async getDeal(id) {
36
+ const query = `
37
+ query GetDeal($id: ID!) {
38
+ deal(id: $id) {
39
+ id
40
+ uuid
41
+ title
42
+ description
43
+ created_at
44
+ updated_at
45
+ owner { id uuid displayname }
46
+ lead { id uuid }
47
+ people { id uuid firstname lastname }
48
+ organization { id name }
49
+ pipeline { id name }
50
+ stage { id name }
51
+ status { id name }
52
+ tags { id name }
53
+ custom_fields { name value }
54
+ }
55
+ }
56
+ `;
57
+ return this.client.query(query, { id });
58
+ }
59
+ async createDeal(input) {
60
+ const mutation = `
61
+ mutation CreateDeal($input: DealInput!) {
62
+ createDeal(input: $input) {
63
+ id
64
+ uuid
65
+ title
66
+ description
67
+ created_at
68
+ owner { id uuid displayname }
69
+ pipeline { id name }
70
+ stage { id name }
71
+ status { id name }
72
+ }
73
+ }
74
+ `;
75
+ return this.client.query(mutation, { input });
76
+ }
77
+ async updateDeal(id, input) {
78
+ const mutation = `
79
+ mutation UpdateDeal($id: ID!, $input: UpdateDealInput!) {
80
+ updateDeal(id: $id, input: $input) {
81
+ id
82
+ uuid
83
+ title
84
+ description
85
+ updated_at
86
+ owner { id uuid displayname }
87
+ pipeline { id name }
88
+ stage { id name }
89
+ status { id name }
90
+ }
91
+ }
92
+ `;
93
+ return this.client.query(mutation, { id, input });
94
+ }
95
+ async deleteDeal(id) {
96
+ const mutation = `
97
+ mutation DeleteDeal($id: ID!) {
98
+ deleteDeal(id: $id)
99
+ }
100
+ `;
101
+ return this.client.query(mutation, { id });
102
+ }
103
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,157 @@
1
+ export class EventsService {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ async listEvents(first = 25, search, where) {
7
+ const query = `
8
+ query ListEvents($first: Int, $search: String, $where: QueryEventsWhereWhereConditions) {
9
+ events(first: $first, search: $search, where: $where) {
10
+ data {
11
+ id
12
+ uuid
13
+ name
14
+ slug
15
+ description
16
+ created_at
17
+ updated_at
18
+ type { id name }
19
+ eventStatus { id name }
20
+ category { id name }
21
+ versions {
22
+ data {
23
+ id
24
+ start_at
25
+ end_at
26
+ dates {
27
+ id
28
+ date
29
+ start_time
30
+ end_time
31
+ }
32
+ }
33
+ }
34
+ }
35
+ paginatorInfo {
36
+ currentPage
37
+ lastPage
38
+ total
39
+ }
40
+ }
41
+ }
42
+ `;
43
+ return this.client.query(query, { first, search, where });
44
+ }
45
+ async getEvent(id) {
46
+ const query = `
47
+ query GetEvent($where: QueryEventsWhereWhereConditions) {
48
+ events(first: 1, where: $where) {
49
+ data {
50
+ id
51
+ uuid
52
+ name
53
+ slug
54
+ description
55
+ created_at
56
+ updated_at
57
+ type { id name }
58
+ eventStatus { id name }
59
+ category { id name }
60
+ resources_id
61
+ resources_type
62
+ versions {
63
+ data {
64
+ id
65
+ version
66
+ start_at
67
+ end_at
68
+ max_capacity
69
+ dates {
70
+ id
71
+ date
72
+ start_time
73
+ end_time
74
+ }
75
+ }
76
+ }
77
+ tags { id name }
78
+ custom_fields { name value }
79
+ }
80
+ }
81
+ }
82
+ `;
83
+ return this.client.query(query, {
84
+ where: { column: "ID", operator: "EQ", value: id },
85
+ });
86
+ }
87
+ async createEvent(input) {
88
+ const mutation = `
89
+ mutation CreateEvent($input: EventInput!) {
90
+ createEvent(input: $input) {
91
+ id
92
+ uuid
93
+ name
94
+ slug
95
+ description
96
+ created_at
97
+ type { id name }
98
+ eventStatus { id name }
99
+ versions {
100
+ data {
101
+ id
102
+ start_at
103
+ end_at
104
+ dates {
105
+ id
106
+ date
107
+ start_time
108
+ end_time
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ `;
115
+ return this.client.query(mutation, { input });
116
+ }
117
+ async updateEvent(id, input) {
118
+ const mutation = `
119
+ mutation UpdateEvent($id: ID!, $input: EventUpdateInput!) {
120
+ updateEvent(id: $id, input: $input) {
121
+ id
122
+ uuid
123
+ name
124
+ description
125
+ updated_at
126
+ type { id name }
127
+ eventStatus { id name }
128
+ }
129
+ }
130
+ `;
131
+ return this.client.query(mutation, { id, input });
132
+ }
133
+ async deleteEvent(id) {
134
+ const mutation = `
135
+ mutation DeleteEvent($id: ID!) {
136
+ deleteEvent(id: $id)
137
+ }
138
+ `;
139
+ return this.client.query(mutation, { id });
140
+ }
141
+ async followEvent(input) {
142
+ const mutation = `
143
+ mutation FollowEvent($input: FollowInput!) {
144
+ followEvent(input: $input)
145
+ }
146
+ `;
147
+ return this.client.query(mutation, { input });
148
+ }
149
+ async unFollowEvent(input) {
150
+ const mutation = `
151
+ mutation UnFollowEvent($input: FollowInput!) {
152
+ unFollowEvent(input: $input)
153
+ }
154
+ `;
155
+ return this.client.query(mutation, { input });
156
+ }
157
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,6 +3,7 @@ export class OrdersService {
3
3
  constructor(client) {
4
4
  this.client = client;
5
5
  }
6
+ // ── Read ───────────────────────────────────────────────────
6
7
  async searchOrders(search, first = 10) {
7
8
  const query = `
8
9
  query SearchOrders($first: Int!, $search: String) {
@@ -54,4 +55,148 @@ export class OrdersService {
54
55
  where: { column: "ID", operator: "EQ", value: id },
55
56
  });
56
57
  }
58
+ // ── Lookups ────────────────────────────────────────────────
59
+ async listOrderStatuses(first = 50) {
60
+ const query = `
61
+ query ListOrderStatuses($first: Int) {
62
+ orderStatuses(first: $first) {
63
+ data {
64
+ id
65
+ name
66
+ slug
67
+ is_default
68
+ is_final
69
+ sequence
70
+ order_type_id
71
+ }
72
+ }
73
+ }
74
+ `;
75
+ return this.client.query(query, { first });
76
+ }
77
+ async listOrderTypes(first = 50) {
78
+ const query = `
79
+ query ListOrderTypes($first: Int) {
80
+ orderTypes(first: $first) {
81
+ data {
82
+ id
83
+ name
84
+ slug
85
+ title
86
+ total_statuses
87
+ }
88
+ }
89
+ }
90
+ `;
91
+ return this.client.query(query, { first });
92
+ }
93
+ async listRegions(first = 50) {
94
+ const query = `
95
+ query ListRegions($first: Int) {
96
+ regions(first: $first) {
97
+ data {
98
+ id
99
+ uuid
100
+ name
101
+ slug
102
+ currency
103
+ is_default
104
+ }
105
+ }
106
+ }
107
+ `;
108
+ return this.client.query(query, { first });
109
+ }
110
+ // ── Write ──────────────────────────────────────────────────
111
+ async createDraftOrder(input) {
112
+ const mutation = `
113
+ mutation CreateDraftOrder($input: DraftOrderInput!) {
114
+ createDraftOrder(input: $input) {
115
+ id
116
+ uuid
117
+ order_number
118
+ status
119
+ total_gross_amount
120
+ total_net_amount
121
+ created_at
122
+ }
123
+ }
124
+ `;
125
+ return this.client.query(mutation, { input });
126
+ }
127
+ async updateOrder(id, input) {
128
+ const mutation = `
129
+ mutation UpdateOrder($id: ID!, $input: UpdateOrderInput!) {
130
+ updateOrder(id: $id, input: $input) {
131
+ order {
132
+ id
133
+ uuid
134
+ order_number
135
+ status
136
+ fulfillment_status
137
+ payment_status
138
+ total_gross_amount
139
+ }
140
+ message
141
+ }
142
+ }
143
+ `;
144
+ return this.client.query(mutation, { id, input });
145
+ }
146
+ async updateDraftOrderStatus(orderId, status) {
147
+ const mutation = `
148
+ mutation UpdateDraftOrderStatus($order_id: ID!, $status: OrderStatusEnum!) {
149
+ updateDraftOrderStatus(order_id: $order_id, status: $status) {
150
+ id
151
+ uuid
152
+ order_number
153
+ status
154
+ }
155
+ }
156
+ `;
157
+ return this.client.query(mutation, { order_id: orderId, status });
158
+ }
159
+ async transitionOrderStatus(orderId, statusSlug, date) {
160
+ const mutation = `
161
+ mutation TransitionOrderStatus($input: TransitionOrderStatusInput!) {
162
+ transitionOrderStatus(input: $input) {
163
+ status
164
+ message
165
+ }
166
+ }
167
+ `;
168
+ return this.client.query(mutation, {
169
+ input: { order_id: orderId, status_slug: statusSlug, date },
170
+ });
171
+ }
172
+ async changeOrderCustomer(orderId, customerId) {
173
+ const mutation = `
174
+ mutation OrderChangeCustomer($order_id: ID!, $customer_id: ID!) {
175
+ orderChangeCustomer(order_id: $order_id, customer_id: $customer_id)
176
+ }
177
+ `;
178
+ return this.client.queryWithAppKey(mutation, {
179
+ order_id: orderId,
180
+ customer_id: customerId,
181
+ });
182
+ }
183
+ async deleteOrder(id) {
184
+ const mutation = `
185
+ mutation DeleteOrder($id: ID!) {
186
+ deleteOrder(id: $id)
187
+ }
188
+ `;
189
+ return this.client.query(mutation, { id });
190
+ }
191
+ async sendOrderEmail(orderId, template) {
192
+ const mutation = `
193
+ mutation SendOrderEmail($order_id: ID!, $template: String) {
194
+ sendOrderEmail(order_id: $order_id, template: $template)
195
+ }
196
+ `;
197
+ return this.client.queryWithAppKey(mutation, {
198
+ order_id: orderId,
199
+ template,
200
+ });
201
+ }
57
202
  }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js CHANGED
@@ -5,11 +5,15 @@ import { InventoryService } from "./domains/inventory/index.js";
5
5
  import { OrdersService } from "./domains/orders/index.js";
6
6
  import { SocialService } from "./domains/social/index.js";
7
7
  import { EcosystemService } from "./domains/ecosystem/index.js";
8
+ import { DealsService } from "./domains/deals/index.js";
9
+ import { EventsService } from "./domains/events/index.js";
8
10
  import { registerCrmTools } from "./tools/crm.js";
9
11
  import { registerInventoryTools } from "./tools/inventory.js";
10
12
  import { registerOrdersTools } from "./tools/orders.js";
11
13
  import { registerSocialTools } from "./tools/social.js";
12
14
  import { registerEcosystemTools } from "./tools/ecosystem.js";
15
+ import { registerDealsTools } from "./tools/deals.js";
16
+ import { registerEventsTools } from "./tools/events.js";
13
17
  import { toolResult } from "./tools/helpers.js";
14
18
  const DEFAULT_API_URL = "https://graphapi.kanvas.dev/graphql";
15
19
  // OpenClaw v2026.4.5+ calls register() per-agent-context (main, subagents,
@@ -23,6 +27,8 @@ let sharedInventory = null;
23
27
  let sharedOrders = null;
24
28
  let sharedSocial = null;
25
29
  let sharedEcosystem = null;
30
+ let sharedDeals = null;
31
+ let sharedEvents = null;
26
32
  let startupBannerShown = false;
27
33
  let skipBannerShown = false;
28
34
  function resolveConfig(pluginConfig) {
@@ -115,6 +121,8 @@ export default {
115
121
  sharedOrders = new OrdersService(sharedClient);
116
122
  sharedSocial = new SocialService(sharedClient);
117
123
  sharedEcosystem = new EcosystemService(sharedClient);
124
+ sharedDeals = new DealsService(sharedClient);
125
+ sharedEvents = new EventsService(sharedClient);
118
126
  }
119
127
  // Tools and hooks must be registered on every api object — each one is
120
128
  // a separate agent context (main, subagents, cron lanes).
@@ -124,6 +132,8 @@ export default {
124
132
  registerOrdersTools(api, sharedOrders, ensureAuth);
125
133
  registerSocialTools(api, sharedSocial, ensureAuth);
126
134
  registerEcosystemTools(api, sharedEcosystem, ensureAuth);
135
+ registerDealsTools(api, sharedDeals, ensureAuth);
136
+ registerEventsTools(api, sharedEvents, ensureAuth);
127
137
  api.registerTool({
128
138
  name: "kanvas_test_connection",
129
139
  label: "Test Connection",
@@ -148,7 +158,7 @@ export default {
148
158
  }, { commands: ["setup"] });
149
159
  if (!startupBannerShown) {
150
160
  startupBannerShown = true;
151
- api.logger.info("Kanvas plugin registered — 53 tools loaded");
161
+ api.logger.info("Kanvas plugin registered — 73 tools loaded");
152
162
  }
153
163
  },
154
164
  };
@@ -222,6 +232,16 @@ Use when the user asks about products, stock, warehouses, or catalog.
222
232
  Use when the user asks about orders, purchases, or sales.
223
233
  - \`kanvas_search_orders\` → find orders by number or keyword
224
234
  - \`kanvas_get_order\` → full order detail with items, customer, status
235
+ - \`kanvas_list_order_statuses\` → get status slugs for transitions
236
+ - \`kanvas_list_order_types\` → order type pipelines (standard, subscription, etc.)
237
+ - \`kanvas_list_regions\` → regions for order creation (get region_id)
238
+ - \`kanvas_create_draft_order\` → create a draft order. Requires email, customer, region_id (from kanvas_list_regions), and items with variant_id+quantity (from kanvas_list_variants)
239
+ - \`kanvas_update_order\` → update items, status, metadata
240
+ - \`kanvas_update_draft_order_status\` → change draft order status (PENDING, COMPLETED, DRAFT, CANCELED, FAILED)
241
+ - \`kanvas_transition_order_status\` → move through the order status pipeline (use status_slug from kanvas_list_order_statuses)
242
+ - \`kanvas_order_change_customer\` → change the customer on an order (requires xKanvasKey)
243
+ - \`kanvas_delete_order\` → delete an order
244
+ - \`kanvas_send_order_email\` → send a confirmation/receipt email (requires xKanvasKey)
225
245
 
226
246
  **Follow-ups & Reminders**
227
247
  ALWAYS schedule follow-ups in Kanvas so the human team can see them — NEVER store them only in local memory.
@@ -229,6 +249,23 @@ ALWAYS schedule follow-ups in Kanvas so the human team can see them — NEVER st
229
249
  - \`kanvas_list_events\` → list scheduled events/follow-ups
230
250
  - For structured follow-up data (tracking status, priority, custom fields), use \`kanvas_create_message\` with verb "follow_up" and a JSON payload containing { due_date, lead_id, action, status, priority }.
231
251
 
252
+ **Deals (Sales Opportunities)**
253
+ Use when the user asks about deals, opportunities, sales tracking. A deal is a potential sale, often tied to a lead/person/organization and moved through a pipeline.
254
+ - \`kanvas_list_deals\` → list/search deals with owner, pipeline, stage, status
255
+ - \`kanvas_get_deal\` → full deal detail including tags and custom fields
256
+ - \`kanvas_create_deal\` → create a deal (only title required; optionally link to lead, person, org, pipeline, owner)
257
+ - \`kanvas_update_deal\` → update title, description, pipeline stage, owner, linked entities
258
+ - \`kanvas_delete_deal\` → delete a deal
259
+
260
+ **Events (Full event management)**
261
+ For simple follow-ups linked to a lead, prefer \`kanvas_create_follow_up\`. Use these when the user wants richer event management (categories, types, resources, tags).
262
+ - \`kanvas_list_events_full\` → list events with versions and dates
263
+ - \`kanvas_get_event\` → event detail including versions, tags, custom fields
264
+ - \`kanvas_create_event\` → create an event with one or more dates. Supports linking to resources (leads, deals), categories, types, tags.
265
+ - \`kanvas_update_event\` → update name, description, dates, status
266
+ - \`kanvas_delete_event\` → delete an event
267
+ - \`kanvas_follow_event\` / \`kanvas_unfollow_event\` → subscribe/unsubscribe user to event updates
268
+
232
269
  **Ecosystem (Companies, Branches, Roles, Users)**
233
270
  Use when the user asks about companies, branches/locations, user management, roles, or permissions.
234
271
  - \`kanvas_list_companies\` → list/search companies
@@ -0,0 +1,91 @@
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 registerDealsTools(api, service, ensureAuth) {
9
+ api.registerTool({
10
+ name: "kanvas_list_deals",
11
+ label: "List Deals",
12
+ description: "List or search deals. A deal represents a potential sale/opportunity linked to a lead, person, or organization.",
13
+ parameters: Type.Object({
14
+ first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
15
+ search: Type.Optional(Type.String({ description: "Search keyword" })),
16
+ where: WhereClause,
17
+ }),
18
+ async execute(_id, params) {
19
+ await ensureAuth();
20
+ return toolResult(await service.listDeals(params.first, params.search, params.where));
21
+ },
22
+ });
23
+ api.registerTool({
24
+ name: "kanvas_get_deal",
25
+ label: "Get Deal",
26
+ description: "Get full details for a deal by ID, including pipeline stage, owner, linked lead/person/org, tags, and custom fields.",
27
+ parameters: Type.Object({
28
+ id: Type.String({ description: "Deal ID" }),
29
+ }),
30
+ async execute(_id, params) {
31
+ await ensureAuth();
32
+ return toolResult(await service.getDeal(params.id));
33
+ },
34
+ });
35
+ api.registerTool({
36
+ name: "kanvas_create_deal",
37
+ label: "Create Deal",
38
+ description: "Create a deal. Only title is required. Optionally link to a lead, person, organization, or pipeline stage.",
39
+ parameters: Type.Object({
40
+ title: Type.String({ description: "Deal title" }),
41
+ description: Type.Optional(Type.String()),
42
+ leads_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Linked lead ID" })),
43
+ people_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Linked person ID" })),
44
+ organization_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
45
+ owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Deal owner user ID" })),
46
+ pipeline_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
47
+ pipeline_stage_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
48
+ status_id: Type.Optional(Type.Number()),
49
+ companies_branches_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
50
+ }),
51
+ async execute(_id, params) {
52
+ await ensureAuth();
53
+ return toolResult(await service.createDeal(params));
54
+ },
55
+ });
56
+ api.registerTool({
57
+ name: "kanvas_update_deal",
58
+ label: "Update Deal",
59
+ description: "Update a deal's title, description, pipeline stage, owner, or linked entities.",
60
+ parameters: Type.Object({
61
+ id: Type.String({ description: "Deal ID" }),
62
+ title: Type.Optional(Type.String()),
63
+ description: Type.Optional(Type.String()),
64
+ leads_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
65
+ people_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
66
+ organization_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
67
+ owner_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
68
+ pipeline_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
69
+ pipeline_stage_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
70
+ status_id: Type.Optional(Type.Number()),
71
+ companies_branches_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
72
+ }),
73
+ async execute(_id, params) {
74
+ await ensureAuth();
75
+ const { id, ...input } = params;
76
+ return toolResult(await service.updateDeal(id, input));
77
+ },
78
+ });
79
+ api.registerTool({
80
+ name: "kanvas_delete_deal",
81
+ label: "Delete Deal",
82
+ description: "Delete a deal by ID.",
83
+ parameters: Type.Object({
84
+ id: Type.String({ description: "Deal ID" }),
85
+ }),
86
+ async execute(_id, params) {
87
+ await ensureAuth();
88
+ return toolResult(await service.deleteDeal(params.id));
89
+ },
90
+ });
91
+ }
@@ -0,0 +1,134 @@
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
+ const EventDateInput = Type.Object({
9
+ date: Type.String({ description: "YYYY-MM-DD" }),
10
+ start_time: Type.String({ description: "HH:MM (24h)" }),
11
+ end_time: Type.String({ description: "HH:MM (24h)" }),
12
+ });
13
+ const EventResourceInput = Type.Object({
14
+ resources_id: Type.String({ description: "Entity ID this event is attached to" }),
15
+ resources_type: Type.String({ description: 'e.g. "lead", "deal"' }),
16
+ });
17
+ const TagInput = Type.Object({
18
+ name: Type.String(),
19
+ slug: Type.Optional(Type.String()),
20
+ });
21
+ export function registerEventsTools(api, service, ensureAuth) {
22
+ api.registerTool({
23
+ name: "kanvas_list_events_full",
24
+ label: "List Events (full)",
25
+ description: "List or search events with full details. For follow-ups only, see kanvas_list_events.",
26
+ parameters: Type.Object({
27
+ first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
28
+ search: Type.Optional(Type.String({ description: "Search keyword" })),
29
+ where: WhereClause,
30
+ }),
31
+ async execute(_id, params) {
32
+ await ensureAuth();
33
+ return toolResult(await service.listEvents(params.first, params.search, params.where));
34
+ },
35
+ });
36
+ api.registerTool({
37
+ name: "kanvas_get_event",
38
+ label: "Get Event",
39
+ description: "Get full details for an event by ID, including versions, dates, tags, and custom fields.",
40
+ parameters: Type.Object({
41
+ id: Type.String({ description: "Event ID" }),
42
+ }),
43
+ async execute(_id, params) {
44
+ await ensureAuth();
45
+ return toolResult(await service.getEvent(params.id));
46
+ },
47
+ });
48
+ api.registerTool({
49
+ name: "kanvas_create_event",
50
+ label: "Create Event",
51
+ description: "Create an event with dates. Supports linking to resources (leads, deals) and categorization. For a simple lead follow-up, use kanvas_create_follow_up.",
52
+ parameters: Type.Object({
53
+ name: Type.String({ description: "Event name" }),
54
+ slug: Type.Optional(Type.String()),
55
+ description: Type.Optional(Type.String()),
56
+ dates: Type.Array(EventDateInput, { description: "At least one date (YYYY-MM-DD + HH:MM times)" }),
57
+ type_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
58
+ status_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
59
+ class_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
60
+ category_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
61
+ theme_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
62
+ theme_area_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
63
+ resources: Type.Optional(Type.Array(EventResourceInput)),
64
+ participants: Type.Optional(Type.Array(Type.String())),
65
+ tags: Type.Optional(Type.Array(TagInput)),
66
+ custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()))),
67
+ }),
68
+ async execute(_id, params) {
69
+ await ensureAuth();
70
+ return toolResult(await service.createEvent(params));
71
+ },
72
+ });
73
+ api.registerTool({
74
+ name: "kanvas_update_event",
75
+ label: "Update Event",
76
+ description: "Update an event's name, description, dates, status, or linked resources.",
77
+ parameters: Type.Object({
78
+ id: Type.String({ description: "Event ID" }),
79
+ name: Type.Optional(Type.String()),
80
+ description: Type.Optional(Type.String()),
81
+ dates: Type.Optional(Type.Array(EventDateInput)),
82
+ type_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
83
+ status_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
84
+ class_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
85
+ category_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
86
+ resources_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
87
+ resources_type: Type.Optional(Type.String()),
88
+ tags: Type.Optional(Type.Array(TagInput)),
89
+ }),
90
+ async execute(_id, params) {
91
+ await ensureAuth();
92
+ const { id, ...input } = params;
93
+ return toolResult(await service.updateEvent(id, input));
94
+ },
95
+ });
96
+ api.registerTool({
97
+ name: "kanvas_delete_event",
98
+ label: "Delete Event",
99
+ description: "Delete an event by ID.",
100
+ parameters: Type.Object({
101
+ id: Type.String({ description: "Event ID" }),
102
+ }),
103
+ async execute(_id, params) {
104
+ await ensureAuth();
105
+ return toolResult(await service.deleteEvent(params.id));
106
+ },
107
+ });
108
+ api.registerTool({
109
+ name: "kanvas_follow_event",
110
+ label: "Follow Event",
111
+ description: "Subscribe a user to event updates.",
112
+ parameters: Type.Object({
113
+ entity_id: Type.String({ description: "Event UUID" }),
114
+ user_id: Type.Union([Type.String(), Type.Number()], { description: "User ID" }),
115
+ }),
116
+ async execute(_id, params) {
117
+ await ensureAuth();
118
+ return toolResult(await service.followEvent(params));
119
+ },
120
+ });
121
+ api.registerTool({
122
+ name: "kanvas_unfollow_event",
123
+ label: "Unfollow Event",
124
+ description: "Unsubscribe a user from event updates.",
125
+ parameters: Type.Object({
126
+ entity_id: Type.String({ description: "Event UUID" }),
127
+ user_id: Type.Union([Type.String(), Type.Number()], { description: "User ID" }),
128
+ }),
129
+ async execute(_id, params) {
130
+ await ensureAuth();
131
+ return toolResult(await service.unFollowEvent(params));
132
+ },
133
+ });
134
+ }
@@ -1,6 +1,42 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import { toolResult } from "./helpers.js";
3
+ const OrderLineItemInput = Type.Object({
4
+ variant_id: Type.Union([Type.String(), Type.Number()], { description: "Variant ID" }),
5
+ quantity: Type.Number({ description: "Quantity" }),
6
+ price: Type.Optional(Type.Union([Type.String(), Type.Number()])),
7
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
8
+ channel_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
9
+ });
10
+ const OrderAddressInput = Type.Object({
11
+ address: Type.String(),
12
+ address_2: Type.Optional(Type.String()),
13
+ city: Type.Optional(Type.String()),
14
+ state: Type.Optional(Type.String()),
15
+ zip: Type.Optional(Type.String()),
16
+ country: Type.Optional(Type.String()),
17
+ is_default: Type.Optional(Type.Boolean()),
18
+ });
19
+ const OrderBillingInput = Type.Object({
20
+ address: Type.String(),
21
+ address2: Type.Optional(Type.String()),
22
+ city: Type.String(),
23
+ state: Type.String(),
24
+ zip: Type.String(),
25
+ country: Type.String(),
26
+ });
27
+ const OrderCustomerInput = Type.Object({
28
+ id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
29
+ firstname: Type.String(),
30
+ lastname: Type.Optional(Type.String()),
31
+ contacts: Type.Optional(Type.Array(Type.Object({
32
+ value: Type.String(),
33
+ contacts_types_id: Type.Number(),
34
+ weight: Type.Optional(Type.Number()),
35
+ }))),
36
+ address: Type.Optional(Type.Array(OrderAddressInput)),
37
+ });
3
38
  export function registerOrdersTools(api, service, ensureAuth) {
39
+ // ── Read ───────────────────────────────────────────────────
4
40
  api.registerTool({
5
41
  name: "kanvas_search_orders",
6
42
  label: "Search Orders",
@@ -26,4 +62,153 @@ export function registerOrdersTools(api, service, ensureAuth) {
26
62
  return toolResult(await service.getOrder(params.id));
27
63
  },
28
64
  });
65
+ // ── Lookups ────────────────────────────────────────────────
66
+ api.registerTool({
67
+ name: "kanvas_list_order_statuses",
68
+ label: "List Order Statuses",
69
+ description: "List order statuses. Use to find status slugs for status transitions.",
70
+ parameters: Type.Object({
71
+ first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
72
+ }),
73
+ async execute(_id, params) {
74
+ await ensureAuth();
75
+ return toolResult(await service.listOrderStatuses(params.first));
76
+ },
77
+ });
78
+ api.registerTool({
79
+ name: "kanvas_list_order_types",
80
+ label: "List Order Types",
81
+ description: "List order types (e.g. standard, subscription). Each type has its own status pipeline.",
82
+ parameters: Type.Object({
83
+ first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
84
+ }),
85
+ async execute(_id, params) {
86
+ await ensureAuth();
87
+ return toolResult(await service.listOrderTypes(params.first));
88
+ },
89
+ });
90
+ api.registerTool({
91
+ name: "kanvas_list_regions",
92
+ label: "List Regions",
93
+ description: "List regions for order creation. Regions define currency and tax rules.",
94
+ parameters: Type.Object({
95
+ first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
96
+ }),
97
+ async execute(_id, params) {
98
+ await ensureAuth();
99
+ return toolResult(await service.listRegions(params.first));
100
+ },
101
+ });
102
+ // ── Write ──────────────────────────────────────────────────
103
+ api.registerTool({
104
+ name: "kanvas_create_draft_order",
105
+ label: "Create Draft Order",
106
+ description: "Create a draft order. Requires email, customer (firstname), region_id, and at least one item with variant_id+quantity. Use kanvas_list_regions to get a region ID and kanvas_list_variants to find variants.",
107
+ parameters: Type.Object({
108
+ email: Type.String({ description: "Customer email" }),
109
+ phone: Type.Optional(Type.String()),
110
+ customer: OrderCustomerInput,
111
+ region_id: Type.Union([Type.String(), Type.Number()], { description: "Region ID (use kanvas_list_regions)" }),
112
+ items: Type.Array(OrderLineItemInput, { description: "Line items" }),
113
+ channel_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Channel ID" })),
114
+ billing_address: Type.Optional(OrderBillingInput),
115
+ shipping_address: Type.Optional(OrderAddressInput),
116
+ note: Type.Optional(Type.String()),
117
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
118
+ }),
119
+ async execute(_id, params) {
120
+ await ensureAuth();
121
+ return toolResult(await service.createDraftOrder(params));
122
+ },
123
+ });
124
+ api.registerTool({
125
+ name: "kanvas_update_order",
126
+ label: "Update Order",
127
+ description: "Update an order's items, fulfillment status, payment status, or metadata.",
128
+ parameters: Type.Object({
129
+ id: Type.String({ description: "Order ID" }),
130
+ items: Type.Optional(Type.Array(OrderLineItemInput)),
131
+ fulfillment_status: Type.Optional(Type.String()),
132
+ status: Type.Optional(Type.String()),
133
+ payment_status: Type.Optional(Type.String()),
134
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
135
+ metadata_action: Type.Optional(Type.Union([Type.Literal("MERGE"), Type.Literal("REPLACE")])),
136
+ }),
137
+ async execute(_id, params) {
138
+ await ensureAuth();
139
+ const { id, ...input } = params;
140
+ return toolResult(await service.updateOrder(id, input));
141
+ },
142
+ });
143
+ api.registerTool({
144
+ name: "kanvas_update_draft_order_status",
145
+ label: "Update Draft Order Status",
146
+ description: "Change a draft order's status (PENDING, COMPLETED, DRAFT, CANCELED, FAILED).",
147
+ parameters: Type.Object({
148
+ order_id: Type.String({ description: "Order ID" }),
149
+ status: Type.Union([
150
+ Type.Literal("PENDING"),
151
+ Type.Literal("COMPLETED"),
152
+ Type.Literal("DRAFT"),
153
+ Type.Literal("CANCELED"),
154
+ Type.Literal("FAILED"),
155
+ ], { description: "New status" }),
156
+ }),
157
+ async execute(_id, params) {
158
+ await ensureAuth();
159
+ return toolResult(await service.updateDraftOrderStatus(params.order_id, params.status));
160
+ },
161
+ });
162
+ api.registerTool({
163
+ name: "kanvas_transition_order_status",
164
+ label: "Transition Order Status",
165
+ description: "Move an order through its status pipeline using a status slug (from kanvas_list_order_statuses).",
166
+ parameters: Type.Object({
167
+ order_id: Type.String({ description: "Order ID" }),
168
+ status_slug: Type.Optional(Type.String({ description: "Target status slug" })),
169
+ date: Type.Optional(Type.String({ description: "Transition date" })),
170
+ }),
171
+ async execute(_id, params) {
172
+ await ensureAuth();
173
+ return toolResult(await service.transitionOrderStatus(params.order_id, params.status_slug, params.date));
174
+ },
175
+ });
176
+ api.registerTool({
177
+ name: "kanvas_order_change_customer",
178
+ label: "Change Order Customer",
179
+ description: "Change the customer on an order. Requires xKanvasKey (app-key authenticated).",
180
+ parameters: Type.Object({
181
+ order_id: Type.String({ description: "Order ID" }),
182
+ customer_id: Type.String({ description: "New customer ID" }),
183
+ }),
184
+ async execute(_id, params) {
185
+ await ensureAuth();
186
+ return toolResult(await service.changeOrderCustomer(params.order_id, params.customer_id));
187
+ },
188
+ });
189
+ api.registerTool({
190
+ name: "kanvas_delete_order",
191
+ label: "Delete Order",
192
+ description: "Delete an order.",
193
+ parameters: Type.Object({
194
+ id: Type.String({ description: "Order ID" }),
195
+ }),
196
+ async execute(_id, params) {
197
+ await ensureAuth();
198
+ return toolResult(await service.deleteOrder(params.id));
199
+ },
200
+ });
201
+ api.registerTool({
202
+ name: "kanvas_send_order_email",
203
+ label: "Send Order Email",
204
+ description: "Send an email for an order (confirmation, receipt, etc.). Requires xKanvasKey (app-key authenticated).",
205
+ parameters: Type.Object({
206
+ order_id: Type.String({ description: "Order ID" }),
207
+ template: Type.Optional(Type.String({ description: "Email template name" })),
208
+ }),
209
+ async execute(_id, params) {
210
+ await ensureAuth();
211
+ return toolResult(await service.sendOrderEmail(params.order_id, params.template));
212
+ },
213
+ });
29
214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanvas/openclaw-plugin",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Connects agents to Kanvas — your company's nervous system for CRM, inventory, orders, and messaging.",
5
5
  "license": "MIT",
6
6
  "repository": {