@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.
@@ -0,0 +1,171 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { toolResult } from "./helpers.js";
3
+ const WhereCondition = Type.Optional(Type.Object({
4
+ column: Type.String({ description: 'Column name (e.g. "ID", "SLUG", "VERB")' }),
5
+ operator: Type.String({ description: 'Operator (e.g. "EQ", "LIKE", "IN")' }),
6
+ value: Type.Unknown({ description: "Filter value" }),
7
+ }));
8
+ const TagInput = Type.Object({
9
+ name: Type.String(),
10
+ slug: Type.Optional(Type.String()),
11
+ });
12
+ export function registerSocialTools(api, service, ensureAuth) {
13
+ api.registerTool({
14
+ name: "kanvas_create_message",
15
+ label: "Create Message",
16
+ description: "Create a message with an arbitrary JSON payload. Messages act as NoSQL-like document storage — " +
17
+ "the message field accepts any JSON structure. Use message_verb to define the type (auto-created if new). " +
18
+ "Optionally link to an entity via channel_slug or entity_id.",
19
+ parameters: Type.Object({
20
+ message_verb: Type.String({
21
+ description: 'Message type verb (e.g. "comment", "note", "sms", or any custom verb). Auto-created if it doesn\'t exist.',
22
+ }),
23
+ message: Type.Record(Type.String(), Type.Unknown(), {
24
+ description: "Arbitrary JSON payload — any structure is accepted.",
25
+ }),
26
+ channel_slug: Type.Optional(Type.String({ description: "Channel slug to post the message to" })),
27
+ entity_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Entity ID to link this message to" })),
28
+ parent_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Parent message ID for threading" })),
29
+ is_public: Type.Optional(Type.Number({ description: "1 = public, 0 = internal (default)" })),
30
+ tags: Type.Optional(Type.Array(TagInput, { description: "Tags to attach" })),
31
+ custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()), { description: "Custom field key-value pairs" })),
32
+ }),
33
+ async execute(_id, params) {
34
+ await ensureAuth();
35
+ return toolResult(await service.createMessage(params));
36
+ },
37
+ });
38
+ api.registerTool({
39
+ name: "kanvas_get_message",
40
+ label: "Get Message",
41
+ description: "Get full details for a single message by ID, including children, files, tags, and entity links.",
42
+ parameters: Type.Object({
43
+ id: Type.String({ description: "Message ID" }),
44
+ }),
45
+ async execute(_id, params) {
46
+ await ensureAuth();
47
+ return toolResult(await service.getMessage(params.id));
48
+ },
49
+ });
50
+ api.registerTool({
51
+ name: "kanvas_update_message",
52
+ label: "Update Message",
53
+ description: "Update a message's JSON content, visibility, lock status, or tags.",
54
+ parameters: Type.Object({
55
+ id: Type.String({ description: "Message ID" }),
56
+ message: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Updated JSON payload" })),
57
+ message_verb: Type.Optional(Type.String({ description: "Change the message type verb" })),
58
+ is_public: Type.Optional(Type.Number({ description: "1 = public, 0 = internal" })),
59
+ is_locked: Type.Optional(Type.Number({ description: "1 = locked, 0 = unlocked" })),
60
+ tags: Type.Optional(Type.Array(TagInput)),
61
+ custom_fields: Type.Optional(Type.Array(Type.Record(Type.String(), Type.Unknown()))),
62
+ }),
63
+ async execute(_id, params) {
64
+ await ensureAuth();
65
+ const { id, ...input } = params;
66
+ return toolResult(await service.updateMessage(id, input));
67
+ },
68
+ });
69
+ api.registerTool({
70
+ name: "kanvas_delete_message",
71
+ label: "Delete Message",
72
+ description: "Soft-delete a message.",
73
+ parameters: Type.Object({
74
+ id: Type.String({ description: "Message ID" }),
75
+ }),
76
+ async execute(_id, params) {
77
+ await ensureAuth();
78
+ return toolResult(await service.deleteMessage(params.id));
79
+ },
80
+ });
81
+ api.registerTool({
82
+ name: "kanvas_list_channel_messages",
83
+ label: "List Channel Messages",
84
+ description: "List messages in a channel by slug. Useful for getting all messages linked to an entity (lead, order, etc.).",
85
+ parameters: Type.Object({
86
+ channel_slug: Type.String({ description: "Channel slug" }),
87
+ first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
88
+ page: Type.Optional(Type.Number({ description: "Page number (default 1)" })),
89
+ }),
90
+ async execute(_id, params) {
91
+ await ensureAuth();
92
+ return toolResult(await service.listChannelMessages(params.channel_slug, params.first, params.page));
93
+ },
94
+ });
95
+ api.registerTool({
96
+ name: "kanvas_search_messages",
97
+ label: "Search Messages",
98
+ description: "Search messages with optional filters by message type (verb), channel, entity, or free text. " +
99
+ "Use hasType to filter by verb, hasChannel to filter by channel slug/entity, hasAppModuleMessage to filter by linked entity.",
100
+ parameters: Type.Object({
101
+ search: Type.Optional(Type.String({ description: "Free text search" })),
102
+ first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
103
+ where: WhereCondition,
104
+ hasType: Type.Optional(Type.Object({
105
+ column: Type.String({ description: 'e.g. "VERB" or "NAME"' }),
106
+ operator: Type.String({ description: 'e.g. "EQ"' }),
107
+ value: Type.Unknown({ description: 'e.g. "comment"' }),
108
+ }, { description: "Filter by message type" })),
109
+ hasChannel: Type.Optional(Type.Object({
110
+ column: Type.String({ description: 'e.g. "SLUG", "ENTITY_ID", "ENTITY_NAMESPACE"' }),
111
+ operator: Type.String({ description: 'e.g. "EQ"' }),
112
+ value: Type.Unknown(),
113
+ }, { description: "Filter by channel" })),
114
+ hasAppModuleMessage: Type.Optional(Type.Object({
115
+ column: Type.String({ description: 'e.g. "ENTITY_ID", "SYSTEM_MODULES"' }),
116
+ operator: Type.String({ description: 'e.g. "EQ"' }),
117
+ value: Type.Unknown(),
118
+ }, { description: "Filter by linked entity" })),
119
+ }),
120
+ async execute(_id, params) {
121
+ await ensureAuth();
122
+ return toolResult(await service.searchMessages(params.search, params.first, params.hasType, params.hasChannel, params.hasAppModuleMessage, params.where));
123
+ },
124
+ });
125
+ api.registerTool({
126
+ name: "kanvas_list_message_types",
127
+ label: "List Message Types",
128
+ description: "List all available message types (verbs) for the current app.",
129
+ parameters: Type.Object({
130
+ first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
131
+ }),
132
+ async execute(_id, params) {
133
+ await ensureAuth();
134
+ return toolResult(await service.listMessageTypes(params.first));
135
+ },
136
+ });
137
+ api.registerTool({
138
+ name: "kanvas_create_message_type",
139
+ label: "Create Message Type",
140
+ description: "Create a new message type with a unique verb and optional JSON template schema.",
141
+ parameters: Type.Object({
142
+ name: Type.String({ description: "Display name" }),
143
+ verb: Type.String({ description: 'Unique verb identifier (e.g. "invoice_data", "vehicle_spec")' }),
144
+ languages_id: Type.Number({ description: "Language ID (typically 1 for English)" }),
145
+ template: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "JSON template/schema for this message type" })),
146
+ templates_plura: Type.Optional(Type.String({ description: "Plural form of the name" })),
147
+ }),
148
+ async execute(_id, params) {
149
+ await ensureAuth();
150
+ return toolResult(await service.createMessageType(params));
151
+ },
152
+ });
153
+ api.registerTool({
154
+ name: "kanvas_send_anonymous_email",
155
+ label: "Send Anonymous Email",
156
+ description: "Send an email to any address using a notification template. Does not require the recipient to be a Kanvas user. " +
157
+ "Requires xKanvasKey (app-key) to be configured.",
158
+ parameters: Type.Object({
159
+ template_name: Type.String({ description: "Notification template name" }),
160
+ data: Type.Record(Type.String(), Type.Unknown(), {
161
+ description: "Template data — variables to interpolate into the template",
162
+ }),
163
+ email: Type.String({ description: "Recipient email address" }),
164
+ subject: Type.String({ description: "Email subject line" }),
165
+ attachment: Type.Optional(Type.Array(Type.String(), { description: "File paths/URLs to attach" })),
166
+ }),
167
+ async execute(_id, params) {
168
+ return toolResult(await service.sendAnonymousEmail(params));
169
+ },
170
+ });
171
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "id": "kanvas",
3
+ "name": "Kanvas CRM",
4
+ "description": "Connects agents to Kanvas — your company's nervous system for CRM, inventory, orders, and messaging.",
5
+ "version": "0.1.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "required": ["xKanvasApp", "email", "password"],
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "apiUrl": {
12
+ "type": "string",
13
+ "default": "https://graphapi.kanvas.dev/graphql",
14
+ "description": "Kanvas GraphQL API endpoint URL"
15
+ },
16
+ "xKanvasApp": {
17
+ "type": "string",
18
+ "description": "Kanvas application/tenant identifier"
19
+ },
20
+ "email": {
21
+ "type": "string",
22
+ "description": "Kanvas user email for agent authentication"
23
+ },
24
+ "password": {
25
+ "type": "string",
26
+ "description": "Kanvas user password for agent authentication"
27
+ },
28
+ "xKanvasLocation": {
29
+ "type": "string",
30
+ "description": "Optional branch/location UUID"
31
+ },
32
+ "authMode": {
33
+ "type": "string",
34
+ "enum": ["bearer", "app-key"],
35
+ "default": "bearer",
36
+ "description": "Authentication mode (auto-set when using email/password)"
37
+ },
38
+ "bearerToken": {
39
+ "type": "string",
40
+ "description": "Pre-existing bearer token (optional — use email/password instead)"
41
+ },
42
+ "xKanvasKey": {
43
+ "type": "string",
44
+ "description": "App key for app-scoped auth"
45
+ },
46
+ "timeoutMs": {
47
+ "type": "number",
48
+ "default": 15000,
49
+ "description": "Request timeout in milliseconds"
50
+ }
51
+ }
52
+ },
53
+ "uiHints": {
54
+ "apiUrl": { "label": "API URL", "placeholder": "https://graphapi.kanvas.dev/graphql" },
55
+ "xKanvasApp": { "label": "App ID", "placeholder": "your-app-key" },
56
+ "email": { "label": "Email", "placeholder": "agent@yourcompany.com" },
57
+ "password": { "label": "Password", "sensitive": true },
58
+ "xKanvasLocation": { "label": "Branch UUID", "advanced": true },
59
+ "authMode": { "label": "Auth Mode", "advanced": true },
60
+ "bearerToken": { "label": "Bearer Token", "sensitive": true, "advanced": true },
61
+ "xKanvasKey": { "label": "App Key", "sensitive": true, "advanced": true },
62
+ "timeoutMs": { "label": "Timeout (ms)", "advanced": true }
63
+ }
64
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@kanvas/openclaw-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Connects agents to Kanvas — your company's nervous system for CRM, inventory, orders, and messaging.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/bakaphp/kanvas-openclaw-plugin"
9
+ },
10
+ "keywords": ["openclaw", "kanvas", "crm", "plugin", "ai-agent"],
11
+ "type": "module",
12
+ "openclaw": {
13
+ "extensions": [
14
+ "./dist/index.js"
15
+ ]
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "openclaw.plugin.json",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "prepublishOnly": "npm run build",
24
+ "build": "tsc -p tsconfig.json",
25
+ "check": "tsc --noEmit -p tsconfig.json",
26
+ "dev": "node --watch --loader ts-node/esm src/index.ts"
27
+ },
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "dependencies": {
32
+ "@sinclair/typebox": "^0.34.48"
33
+ },
34
+ "peerDependencies": {
35
+ "openclaw": ">=2026.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^24.5.2",
39
+ "openclaw": "^2026.3.24",
40
+ "ts-node": "^10.9.2",
41
+ "typescript": "^5.9.2"
42
+ }
43
+ }