@kanvas/openclaw-plugin 0.1.12 → 0.1.14

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.
@@ -635,9 +635,15 @@ export class CrmService {
635
635
  data {
636
636
  id
637
637
  name
638
+ slug
639
+ weight
640
+ is_default
641
+ description
638
642
  stages {
639
643
  id
640
644
  name
645
+ rotting_days
646
+ weight
641
647
  }
642
648
  }
643
649
  }
@@ -645,6 +651,92 @@ export class CrmService {
645
651
  `;
646
652
  return this.client.query(query, { first });
647
653
  }
654
+ async createPipeline(input) {
655
+ const mutation = `
656
+ mutation CreatePipeline($input: PipelineInput!) {
657
+ createPipeline(input: $input) {
658
+ id
659
+ name
660
+ slug
661
+ weight
662
+ is_default
663
+ description
664
+ stages {
665
+ id
666
+ name
667
+ rotting_days
668
+ weight
669
+ }
670
+ }
671
+ }
672
+ `;
673
+ return this.client.query(mutation, { input });
674
+ }
675
+ async updatePipeline(id, input) {
676
+ const mutation = `
677
+ mutation UpdatePipeline($id: ID!, $input: PipelineInput!) {
678
+ updatePipeline(id: $id, input: $input) {
679
+ id
680
+ name
681
+ slug
682
+ weight
683
+ is_default
684
+ description
685
+ stages {
686
+ id
687
+ name
688
+ rotting_days
689
+ weight
690
+ }
691
+ }
692
+ }
693
+ `;
694
+ return this.client.query(mutation, { id, input });
695
+ }
696
+ async deletePipeline(id) {
697
+ const mutation = `
698
+ mutation DeletePipeline($id: ID!) {
699
+ deletePipeline(id: $id)
700
+ }
701
+ `;
702
+ return this.client.query(mutation, { id });
703
+ }
704
+ async createPipelineStage(input) {
705
+ const mutation = `
706
+ mutation CreatePipelineStage($input: PipelineStageInput!) {
707
+ createPipelineStage(input: $input) {
708
+ id
709
+ name
710
+ rotting_days
711
+ weight
712
+ pipeline { id name }
713
+ }
714
+ }
715
+ `;
716
+ return this.client.query(mutation, { input });
717
+ }
718
+ async updatePipelineStage(id, input) {
719
+ const mutation = `
720
+ mutation UpdatePipelineStage($id: ID!, $input: PipelineStageInput!) {
721
+ updatePipelineStage(id: $id, input: $input) {
722
+ id
723
+ name
724
+ rotting_days
725
+ weight
726
+ pipeline { id name }
727
+ }
728
+ }
729
+ `;
730
+ return this.client.query(mutation, { id, input });
731
+ }
732
+ async deletePipelineStage(id) {
733
+ const mutation = `
734
+ mutation DeletePipelineStage($id: ID!) {
735
+ deletePipelineStage(id: $id)
736
+ }
737
+ `;
738
+ return this.client.query(mutation, { id });
739
+ }
648
740
  async attachFileToLeadByUrl(leadId, fileUrl, fileName) {
649
741
  const mutation = `
650
742
  mutation CreateFilesystem($input: FilesystemInputUrl!) {
@@ -33,8 +33,10 @@ export class SocialService {
33
33
  system_modules
34
34
  }
35
35
  tags {
36
- id
37
- name
36
+ data {
37
+ id
38
+ name
39
+ }
38
40
  }
39
41
  custom_fields {
40
42
  name
@@ -118,8 +120,10 @@ export class SocialService {
118
120
  }
119
121
  }
120
122
  tags {
121
- id
122
- name
123
+ data {
124
+ id
125
+ name
126
+ }
123
127
  }
124
128
  custom_fields {
125
129
  name
@@ -150,8 +154,10 @@ export class SocialService {
150
154
  verb
151
155
  }
152
156
  tags {
153
- id
154
- name
157
+ data {
158
+ id
159
+ name
160
+ }
155
161
  }
156
162
  custom_fields {
157
163
  name
@@ -219,8 +225,10 @@ export class SocialService {
219
225
  }
220
226
  }
221
227
  tags {
222
- id
223
- name
228
+ data {
229
+ id
230
+ name
231
+ }
224
232
  }
225
233
  }
226
234
  paginatorInfo {
@@ -286,8 +294,10 @@ export class SocialService {
286
294
  system_modules
287
295
  }
288
296
  tags {
289
- id
290
- name
297
+ data {
298
+ id
299
+ name
300
+ }
291
301
  }
292
302
  }
293
303
  paginatorInfo {
package/dist/index.js CHANGED
@@ -79,20 +79,31 @@ export default {
79
79
  description: "Connects agents to Kanvas — your company's nervous system for CRM, inventory, orders, and messaging.",
80
80
  configSchema: { type: "object" },
81
81
  register(api) {
82
- // Resolve config log once and bail silently on missing credentials.
83
- let config;
82
+ // Resolve plugin config. api.pluginConfig is set on the gateway init
83
+ // context but often undefined on agent contexts (Slack, subagents, cron).
84
+ // Fall back to extracting from the full app config (api.config), which
85
+ // is always available — matching the pattern used by budget-guard and
86
+ // lossless-claw.
87
+ const pluginCfg = api.pluginConfig
88
+ ?? api.config?.plugins?.entries?.kanvas?.config
89
+ ?? {};
90
+ let config = null;
84
91
  try {
85
- config = resolveConfig(api.pluginConfig);
92
+ config = resolveConfig(pluginCfg);
86
93
  }
87
94
  catch (err) {
88
- if (!skipBannerShown) {
89
- api.logger.info(`Kanvas plugin skipped: ${err.message}. Run "openclaw kanvas setup" to configure.`);
90
- skipBannerShown = true;
95
+ // If shared state exists from a prior init, fall through to register
96
+ // tools. Otherwise this is genuinely unconfigured bail.
97
+ if (!sharedClient) {
98
+ if (!skipBannerShown) {
99
+ api.logger.info(`Kanvas plugin skipped: ${err.message}. Run "openclaw kanvas setup" to configure.`);
100
+ skipBannerShown = true;
101
+ }
102
+ return;
91
103
  }
92
- return;
93
104
  }
94
105
  // Create heavy objects once; reuse across agent contexts.
95
- if (!sharedClient) {
106
+ if (!sharedClient && config) {
96
107
  sharedConfig = config;
97
108
  sharedClient = new KanvasClient(config);
98
109
  sharedEnsureAuth = createAuthGuard(sharedClient, config, api.logger);
package/dist/tools/crm.js CHANGED
@@ -304,6 +304,109 @@ export function registerCrmTools(api, service, ensureAuth) {
304
304
  return toolResult(await service.listPipelines(params.first));
305
305
  },
306
306
  });
307
+ // --- Pipeline Management ---
308
+ api.registerTool({
309
+ name: "kanvas_create_pipeline",
310
+ label: "Create Pipeline",
311
+ description: "Create a new lead pipeline. Optionally include stages in one call. " +
312
+ "Weight controls display order (lower = first). is_default marks the pipeline used for new leads.",
313
+ parameters: Type.Object({
314
+ name: Type.String({ description: "Pipeline name" }),
315
+ weight: Type.Number({ description: "Sort order (lower = first)" }),
316
+ is_default: Type.Boolean({ description: "Whether this is the default pipeline for new leads" }),
317
+ description: Type.Optional(Type.String({ description: "Pipeline description" })),
318
+ slug: Type.Optional(Type.String({ description: "URL-friendly slug (auto-generated from name if omitted)" })),
319
+ stages: Type.Optional(Type.Array(Type.Object({
320
+ name: Type.String({ description: "Stage name" }),
321
+ rotting_days: Type.Number({ description: "Days before a lead in this stage is considered stale (0 = no rotting)" }),
322
+ weight: Type.Number({ description: "Sort order within pipeline (lower = first)" }),
323
+ }), { description: "Initial stages to create with the pipeline" })),
324
+ }),
325
+ async execute(_id, params) {
326
+ await ensureAuth();
327
+ return toolResult(await service.createPipeline(params));
328
+ },
329
+ });
330
+ api.registerTool({
331
+ name: "kanvas_update_pipeline",
332
+ label: "Update Pipeline",
333
+ description: "Update an existing pipeline's name, weight, default status, or stages. " +
334
+ "To update existing stages, include stages_id in each stage object.",
335
+ parameters: Type.Object({
336
+ id: Type.String({ description: "Pipeline ID" }),
337
+ name: Type.String({ description: "Pipeline name" }),
338
+ weight: Type.Number({ description: "Sort order" }),
339
+ is_default: Type.Boolean({ description: "Whether this is the default pipeline" }),
340
+ description: Type.Optional(Type.String({ description: "Pipeline description" })),
341
+ slug: Type.Optional(Type.String({ description: "URL-friendly slug" })),
342
+ stages: Type.Optional(Type.Array(Type.Object({
343
+ name: Type.String({ description: "Stage name" }),
344
+ rotting_days: Type.Number({ description: "Days before rotting (0 = disabled)" }),
345
+ weight: Type.Number({ description: "Sort order within pipeline" }),
346
+ stages_id: Type.Optional(Type.String({ description: "Existing stage ID to update (omit to create new)" })),
347
+ }))),
348
+ }),
349
+ async execute(_id, params) {
350
+ await ensureAuth();
351
+ const { id, ...input } = params;
352
+ return toolResult(await service.updatePipeline(id, input));
353
+ },
354
+ });
355
+ api.registerTool({
356
+ name: "kanvas_delete_pipeline",
357
+ label: "Delete Pipeline",
358
+ description: "Delete a pipeline. Will fail if the pipeline still has leads assigned to it.",
359
+ parameters: Type.Object({
360
+ id: Type.String({ description: "Pipeline ID" }),
361
+ }),
362
+ async execute(_id, params) {
363
+ await ensureAuth();
364
+ return toolResult(await service.deletePipeline(params.id));
365
+ },
366
+ });
367
+ api.registerTool({
368
+ name: "kanvas_create_pipeline_stage",
369
+ label: "Create Pipeline Stage",
370
+ description: "Add a new stage to an existing pipeline.",
371
+ parameters: Type.Object({
372
+ pipeline_id: Type.String({ description: "Pipeline ID to add the stage to" }),
373
+ name: Type.String({ description: "Stage name" }),
374
+ rotting_days: Type.Number({ description: "Days before a lead in this stage is considered stale (0 = no rotting)" }),
375
+ weight: Type.Number({ description: "Sort order within pipeline (lower = first)" }),
376
+ }),
377
+ async execute(_id, params) {
378
+ await ensureAuth();
379
+ return toolResult(await service.createPipelineStage(params));
380
+ },
381
+ });
382
+ api.registerTool({
383
+ name: "kanvas_update_pipeline_stage",
384
+ label: "Update Pipeline Stage",
385
+ description: "Update an existing pipeline stage's name, rotting days, or weight.",
386
+ parameters: Type.Object({
387
+ id: Type.String({ description: "Stage ID" }),
388
+ name: Type.String({ description: "Stage name" }),
389
+ rotting_days: Type.Number({ description: "Days before rotting (0 = disabled)" }),
390
+ weight: Type.Number({ description: "Sort order within pipeline" }),
391
+ }),
392
+ async execute(_id, params) {
393
+ await ensureAuth();
394
+ const { id, ...input } = params;
395
+ return toolResult(await service.updatePipelineStage(id, input));
396
+ },
397
+ });
398
+ api.registerTool({
399
+ name: "kanvas_delete_pipeline_stage",
400
+ label: "Delete Pipeline Stage",
401
+ description: "Delete a stage from a pipeline. Will fail if leads are currently in this stage.",
402
+ parameters: Type.Object({
403
+ id: Type.String({ description: "Stage ID" }),
404
+ }),
405
+ async execute(_id, params) {
406
+ await ensureAuth();
407
+ return toolResult(await service.deletePipelineStage(params.id));
408
+ },
409
+ });
307
410
  api.registerTool({
308
411
  name: "kanvas_list_lead_statuses",
309
412
  label: "List Lead Statuses",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanvas/openclaw-plugin",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
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": {
@@ -32,7 +32,9 @@
32
32
  "prepublishOnly": "npm run build",
33
33
  "build": "tsc -p tsconfig.json",
34
34
  "check": "tsc --noEmit -p tsconfig.json",
35
- "dev": "node --watch --loader ts-node/esm src/index.ts"
35
+ "dev": "node --watch --loader ts-node/esm src/index.ts",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
36
38
  },
37
39
  "engines": {
38
40
  "node": ">=20"
@@ -45,8 +47,10 @@
45
47
  },
46
48
  "devDependencies": {
47
49
  "@types/node": "^24.5.2",
50
+ "dotenv": "^17.4.1",
48
51
  "openclaw": "^2026.3.24",
49
52
  "ts-node": "^10.9.2",
50
- "typescript": "^5.9.2"
53
+ "typescript": "^5.9.2",
54
+ "vitest": "^4.1.4"
51
55
  }
52
56
  }