@crmy/openclaw-plugin 0.5.11 → 0.6.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/SKILL.md +221 -83
- package/dist/index.d.ts +10 -9
- package/dist/index.js +55 -201
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -1,140 +1,278 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: crmy
|
|
3
|
-
description: CRMy agent — manages contacts, accounts, deals, and pipeline
|
|
3
|
+
description: CRMy agent — manages contacts, accounts, deals, and pipeline. Search before creating. Log every meaningful interaction. Always suggest a next step.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# CRMy — Your AI-Native CRM
|
|
7
7
|
|
|
8
|
-
You have full access to CRMy
|
|
8
|
+
You have full access to CRMy via the **`crmy` tool**. Every call takes an `action` string and an optional `params` object.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
crmy({ action: "<action>", params: { ... } })
|
|
12
|
+
```
|
|
9
13
|
|
|
10
14
|
---
|
|
11
15
|
|
|
12
16
|
## Core Principles
|
|
13
17
|
|
|
14
18
|
### 1. Search before you create
|
|
15
|
-
|
|
19
|
+
Always search before creating any record. Duplicates are expensive.
|
|
16
20
|
|
|
17
21
|
```
|
|
18
|
-
User: "Add
|
|
19
|
-
→
|
|
20
|
-
→
|
|
21
|
-
→ If not found: create with crmy_contact_create
|
|
22
|
+
User: "Add Sarah Chen at Acme"
|
|
23
|
+
→ crmy({ action: "contact.search", params: { q: "Sarah Chen" } })
|
|
24
|
+
→ Found? Confirm before updating. Not found? Create.
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
### 2. Log every meaningful interaction
|
|
25
|
-
Any time the user mentions
|
|
28
|
+
Any time the user mentions a call, meeting, email, or deal news — offer to log it. Don't wait to be asked.
|
|
26
29
|
|
|
27
30
|
```
|
|
28
|
-
User: "Just got off a call with Marcus, he's interested in
|
|
29
|
-
→
|
|
30
|
-
→
|
|
31
|
-
→ Ask if there's a follow-up to schedule
|
|
31
|
+
User: "Just got off a call with Marcus, he's interested in enterprise"
|
|
32
|
+
→ crmy({ action: "contact.log_activity", params: { activity_type: "call", subject_type: "contact", subject_id: "<marcus-id>", summary: "...", outcome: "positive" } })
|
|
33
|
+
→ Offer to advance the opportunity stage
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
### 3. Link everything
|
|
35
|
-
Contacts belong to accounts. Opportunities belong to accounts
|
|
37
|
+
Contacts belong to accounts. Opportunities belong to accounts. Ask about relationships when not provided.
|
|
36
38
|
|
|
37
39
|
### 4. Always suggest a next step
|
|
38
|
-
After any CRM action, end with one concrete suggestion:
|
|
39
40
|
- After logging a call → "Want me to advance the deal stage or set a follow-up?"
|
|
40
41
|
- After creating a contact → "Should I create an opportunity for this relationship?"
|
|
41
42
|
- After advancing a stage → "Want me to log what triggered this move?"
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
45
|
-
##
|
|
46
|
+
## Actions Reference
|
|
47
|
+
|
|
48
|
+
### `search`
|
|
49
|
+
Global cross-entity search — contacts, accounts, opportunities, activities.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
crmy({ action: "search", params: { q: "Acme", limit: 10 } })
|
|
53
|
+
```
|
|
54
|
+
| Param | Type | Notes |
|
|
55
|
+
|-------|------|-------|
|
|
56
|
+
| q | string | **required** — search query |
|
|
57
|
+
| limit | number | max results (default 10) |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### `contact.search`
|
|
62
|
+
Search contacts by name, email, company, or keyword.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
crmy({ action: "contact.search", params: { q: "Sarah", stage: "customer", limit: 20 } })
|
|
66
|
+
```
|
|
67
|
+
| Param | Type | Notes |
|
|
68
|
+
|-------|------|-------|
|
|
69
|
+
| q | string | **required** |
|
|
70
|
+
| stage | string | filter by lifecycle stage |
|
|
71
|
+
| limit | number | default 20 |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### `contact.create`
|
|
76
|
+
Create a new contact.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
crmy({ action: "contact.create", params: {
|
|
80
|
+
name: "Sarah Chen",
|
|
81
|
+
email: "sarah@acme.com",
|
|
82
|
+
phone: "+1 555 0100",
|
|
83
|
+
title: "VP Engineering",
|
|
84
|
+
account_id: "<uuid>",
|
|
85
|
+
lifecycle_stage: "prospect",
|
|
86
|
+
notes: "Met at SaaStr 2026"
|
|
87
|
+
}})
|
|
88
|
+
```
|
|
89
|
+
| Param | Required |
|
|
90
|
+
|-------|----------|
|
|
91
|
+
| name | ✓ |
|
|
92
|
+
| email, phone, title, account_id, lifecycle_stage, notes | optional |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `contact.update`
|
|
97
|
+
Update fields on an existing contact.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
crmy({ action: "contact.update", params: { id: "<uuid>", email: "new@acme.com" } })
|
|
101
|
+
```
|
|
102
|
+
`id` is **required**. Include only the fields to change.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### `contact.set_stage`
|
|
107
|
+
Change a contact's lifecycle stage.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
crmy({ action: "contact.set_stage", params: { id: "<uuid>", stage: "customer", note: "Signed contract" } })
|
|
111
|
+
```
|
|
112
|
+
**Lifecycle stages in order:** `lead` → `prospect` → `customer` → `churned` / `partner`
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### `contact.log_activity`
|
|
117
|
+
Log a call, email, meeting, demo, proposal, or note against any record.
|
|
46
118
|
|
|
47
|
-
|
|
48
|
-
|
|
119
|
+
```
|
|
120
|
+
crmy({ action: "contact.log_activity", params: {
|
|
121
|
+
activity_type: "call",
|
|
122
|
+
subject_type: "contact",
|
|
123
|
+
subject_id: "<uuid>",
|
|
124
|
+
summary: "Discussed enterprise pricing",
|
|
125
|
+
outcome: "positive",
|
|
126
|
+
duration_minutes: 30,
|
|
127
|
+
notes: "Wants a proposal by Friday"
|
|
128
|
+
}})
|
|
129
|
+
```
|
|
130
|
+
| Param | Required | Values |
|
|
131
|
+
|-------|----------|--------|
|
|
132
|
+
| activity_type | ✓ | call, email, meeting, demo, proposal, note |
|
|
133
|
+
| subject_type | ✓ | contact, account, opportunity |
|
|
134
|
+
| subject_id | ✓ | UUID of the record |
|
|
135
|
+
| summary | ✓ | short description |
|
|
136
|
+
| outcome | | positive, neutral, negative |
|
|
137
|
+
| duration_minutes | | for calls and meetings |
|
|
138
|
+
| performed_at | | ISO 8601 (defaults to now) |
|
|
139
|
+
| notes | | detailed notes |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `account.search`
|
|
144
|
+
Search companies/accounts.
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
crmy({ action: "account.search", params: { q: "Acme", industry: "SaaS", limit: 20 } })
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### `account.create`
|
|
153
|
+
Create a new company/account.
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
crmy({ action: "account.create", params: {
|
|
157
|
+
name: "Acme Corp",
|
|
158
|
+
domain: "acme.com",
|
|
159
|
+
industry: "SaaS",
|
|
160
|
+
size: "51-200"
|
|
161
|
+
}})
|
|
162
|
+
```
|
|
163
|
+
`name` is **required**.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `opportunity.search`
|
|
168
|
+
Search deals/opportunities.
|
|
49
169
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
170
|
+
```
|
|
171
|
+
crmy({ action: "opportunity.search", params: { q: "Acme", stage: "proposal", limit: 20 } })
|
|
172
|
+
```
|
|
173
|
+
| Param | Notes |
|
|
174
|
+
|-------|-------|
|
|
175
|
+
| q | **required** |
|
|
176
|
+
| stage | filter by deal stage |
|
|
177
|
+
| account_id | filter by account UUID |
|
|
56
178
|
|
|
57
|
-
|
|
179
|
+
---
|
|
58
180
|
|
|
59
|
-
###
|
|
60
|
-
|
|
181
|
+
### `opportunity.create`
|
|
182
|
+
Create a new deal.
|
|
61
183
|
|
|
62
|
-
|
|
63
|
-
|
|
184
|
+
```
|
|
185
|
+
crmy({ action: "opportunity.create", params: {
|
|
186
|
+
name: "Acme Corp — Enterprise",
|
|
187
|
+
account_id: "<uuid>",
|
|
188
|
+
value: 48000,
|
|
189
|
+
stage: "prospecting",
|
|
190
|
+
close_date: "2026-09-30"
|
|
191
|
+
}})
|
|
192
|
+
```
|
|
193
|
+
`name` is **required**.
|
|
64
194
|
|
|
65
|
-
|
|
66
|
-
- `prospecting` → `qualification` → `proposal` → `negotiation` → `closed_won` / `closed_lost`
|
|
195
|
+
---
|
|
67
196
|
|
|
68
|
-
|
|
197
|
+
### `opportunity.advance`
|
|
198
|
+
Move a deal to a new stage.
|
|
69
199
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
200
|
+
```
|
|
201
|
+
crmy({ action: "opportunity.advance", params: {
|
|
202
|
+
id: "<uuid>",
|
|
203
|
+
stage: "closed_won",
|
|
204
|
+
note: "Signed MSA received",
|
|
205
|
+
lost_reason: ""
|
|
206
|
+
}})
|
|
207
|
+
```
|
|
208
|
+
`id` and `stage` are **required**. Always include a `note`.
|
|
78
209
|
|
|
79
|
-
|
|
210
|
+
**Deal stages:** `prospecting` → `qualification` → `proposal` → `negotiation` → `closed_won` / `closed_lost`
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### `pipeline.summary`
|
|
215
|
+
Get pipeline analytics grouped by stage (or owner/forecast_cat).
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
crmy({ action: "pipeline.summary", params: { group_by: "stage" } })
|
|
219
|
+
```
|
|
80
220
|
|
|
81
221
|
---
|
|
82
222
|
|
|
83
223
|
## Multi-Step Workflows
|
|
84
224
|
|
|
85
225
|
### "Log a call I just had"
|
|
86
|
-
1.
|
|
87
|
-
2.
|
|
88
|
-
3. If
|
|
89
|
-
4. Suggest: "Want me to update
|
|
226
|
+
1. `contact.search` — find the contact
|
|
227
|
+
2. `contact.log_activity` — type: call, summary, outcome
|
|
228
|
+
3. If deal mentioned → `opportunity.search` → offer `opportunity.advance`
|
|
229
|
+
4. Suggest: "Want me to update their lifecycle stage?"
|
|
90
230
|
|
|
91
231
|
### "We just closed a deal"
|
|
92
|
-
1.
|
|
93
|
-
2.
|
|
94
|
-
3.
|
|
95
|
-
4.
|
|
96
|
-
5. Celebrate, then
|
|
97
|
-
|
|
98
|
-
### "How
|
|
99
|
-
1.
|
|
100
|
-
2.
|
|
101
|
-
3.
|
|
102
|
-
|
|
103
|
-
### "Find everyone at Acme Corp"
|
|
104
|
-
1. Search accounts for Acme (`crmy_account_search`)
|
|
105
|
-
2. Search contacts at that account (`crmy_contact_search` with the account name or id)
|
|
106
|
-
3. Present a clean summary: contacts, their titles, lifecycle stages, and any open deals
|
|
232
|
+
1. `opportunity.search` — find the deal
|
|
233
|
+
2. `opportunity.advance` — stage: closed_won + note
|
|
234
|
+
3. `contact.set_stage` — primary contact → customer
|
|
235
|
+
4. `contact.log_activity` — type: meeting, outcome: positive
|
|
236
|
+
5. Celebrate, then: "Should I set up an onboarding follow-up?"
|
|
237
|
+
|
|
238
|
+
### "How's the pipeline?"
|
|
239
|
+
1. `pipeline.summary` — group_by: stage
|
|
240
|
+
2. Present as a table: stage | deal count | total value
|
|
241
|
+
3. Highlight any deals stuck in the same stage for 30+ days
|
|
242
|
+
4. Ask: "Want me to look at any of these in detail?"
|
|
107
243
|
|
|
108
244
|
### "New lead from the conference"
|
|
109
|
-
1.
|
|
110
|
-
2.
|
|
111
|
-
3.
|
|
112
|
-
4.
|
|
113
|
-
5.
|
|
114
|
-
6. Ask: "Want to create an opportunity
|
|
245
|
+
1. `contact.search` — avoid duplicate
|
|
246
|
+
2. `contact.create` — lifecycle_stage: lead
|
|
247
|
+
3. `account.search` or `account.create` — find/create their company
|
|
248
|
+
4. `contact.update` — link account_id
|
|
249
|
+
5. `contact.log_activity` — type: meeting (where you met)
|
|
250
|
+
6. Ask: "Want to create an opportunity?"
|
|
251
|
+
|
|
252
|
+
### "Who do we know at Stripe?"
|
|
253
|
+
1. `account.search` — q: "Stripe"
|
|
254
|
+
2. `contact.search` — q: "Stripe" (or filter by account_id)
|
|
255
|
+
3. Present: name, title, lifecycle stage, any open deals
|
|
115
256
|
|
|
116
257
|
---
|
|
117
258
|
|
|
118
|
-
##
|
|
259
|
+
## Presentation Guidelines
|
|
119
260
|
|
|
120
|
-
- **
|
|
121
|
-
- **
|
|
122
|
-
- **Confirm before bulk
|
|
123
|
-
- **
|
|
124
|
-
-
|
|
261
|
+
- **Summarize results** — don't dump raw JSON. Use names, not UUIDs.
|
|
262
|
+
- **Format pipeline data** as a table or bullets, not raw numbers.
|
|
263
|
+
- **Confirm before bulk changes** — "I'll update 5 contacts — proceed?"
|
|
264
|
+
- **On API errors** — explain in plain English and suggest a fix:
|
|
265
|
+
- "Server not reachable — is `npx @crmy/cli server` running?"
|
|
266
|
+
- "Record not found — want me to search first?"
|
|
125
267
|
|
|
126
268
|
---
|
|
127
269
|
|
|
128
|
-
##
|
|
129
|
-
|
|
130
|
-
> "Sarah from Acme said she's ready to move forward"
|
|
131
|
-
→ Search for Sarah → find opportunity → advance stage → log activity → update lifecycle → suggest next step
|
|
132
|
-
|
|
133
|
-
> "Pull up our pipeline"
|
|
134
|
-
→ `crmy_pipeline_summary` → present as table with stage, deal count, total value → highlight any stuck deals
|
|
135
|
-
|
|
136
|
-
> "Who do we know at Stripe?"
|
|
137
|
-
→ `crmy_account_search("Stripe")` → `crmy_contact_search` filtered by account → list contacts with titles and stages
|
|
270
|
+
## Quick Examples
|
|
138
271
|
|
|
139
|
-
|
|
140
|
-
|
|
272
|
+
| User says | Actions |
|
|
273
|
+
|-----------|---------|
|
|
274
|
+
| "Sarah from Acme is ready to move forward" | contact.search → opportunity.search → opportunity.advance → contact.log_activity → contact.set_stage |
|
|
275
|
+
| "Pull up our pipeline" | pipeline.summary → present table → offer drill-down |
|
|
276
|
+
| "Who do we know at Stripe?" | account.search → contact.search → list with stages |
|
|
277
|
+
| "Log that I sent a proposal to Marcus" | contact.search → contact.log_activity (type: proposal) → offer opportunity.advance |
|
|
278
|
+
| "Add a new lead: Jamie Lee, CTO at Horizon" | contact.search (check dup) → contact.create → account.search → link account |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
interface ToolInput {
|
|
2
|
+
type: 'object';
|
|
3
|
+
properties: Record<string, {
|
|
4
|
+
type: string;
|
|
5
|
+
description: string;
|
|
6
|
+
enum?: string[];
|
|
7
|
+
}>;
|
|
8
|
+
required?: string[];
|
|
9
|
+
}
|
|
1
10
|
interface ToolDef {
|
|
2
11
|
id: string;
|
|
3
12
|
name: string;
|
|
4
13
|
description: string;
|
|
5
|
-
input:
|
|
6
|
-
type: 'object';
|
|
7
|
-
properties: Record<string, {
|
|
8
|
-
type: string;
|
|
9
|
-
description: string;
|
|
10
|
-
enum?: string[];
|
|
11
|
-
}>;
|
|
12
|
-
required?: string[];
|
|
13
|
-
};
|
|
14
|
+
input: ToolInput;
|
|
14
15
|
handler: (input: Record<string, unknown>) => Promise<unknown>;
|
|
15
16
|
}
|
|
16
17
|
interface OpenClawApi {
|
package/dist/index.js
CHANGED
|
@@ -71,219 +71,73 @@ var CrmyClient = class {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
// src/index.ts
|
|
74
|
+
var ROUTES = {
|
|
75
|
+
// Global cross-entity search
|
|
76
|
+
"search": async (c, p) => c.get("/search", { q: str(p.q), limit: num(p.limit, 10) }),
|
|
77
|
+
// Contacts
|
|
78
|
+
"contact.search": async (c, p) => c.get("/contacts", { q: str(p.q), stage: str(p.stage), limit: num(p.limit, 20) }),
|
|
79
|
+
"contact.create": async (c, p) => c.post("/contacts", p),
|
|
80
|
+
"contact.update": async (c, { id, ...rest }) => c.patch(`/contacts/${str(id)}`, rest),
|
|
81
|
+
"contact.set_stage": async (c, { id, ...rest }) => c.patch(`/contacts/${str(id)}`, rest),
|
|
82
|
+
"contact.log_activity": async (c, p) => c.post("/activities", p),
|
|
83
|
+
// Accounts
|
|
84
|
+
"account.search": async (c, p) => c.get("/accounts", { q: str(p.q), industry: str(p.industry), limit: num(p.limit, 20) }),
|
|
85
|
+
"account.create": async (c, p) => c.post("/accounts", p),
|
|
86
|
+
// Opportunities
|
|
87
|
+
"opportunity.search": async (c, p) => c.get("/opportunities", {
|
|
88
|
+
q: str(p.q),
|
|
89
|
+
stage: str(p.stage),
|
|
90
|
+
account_id: str(p.account_id),
|
|
91
|
+
limit: num(p.limit, 20)
|
|
92
|
+
}),
|
|
93
|
+
"opportunity.create": async (c, p) => c.post("/opportunities", p),
|
|
94
|
+
"opportunity.advance": async (c, { id, ...rest }) => c.patch(`/opportunities/${str(id)}`, rest),
|
|
95
|
+
// Analytics
|
|
96
|
+
"pipeline.summary": async (c, p) => c.get("/analytics/pipeline", { group_by: str(p.group_by) ?? "stage" })
|
|
97
|
+
};
|
|
98
|
+
var VALID_ACTIONS = Object.keys(ROUTES);
|
|
99
|
+
function str(v) {
|
|
100
|
+
return v != null && v !== "" ? String(v) : void 0;
|
|
101
|
+
}
|
|
102
|
+
function num(v, fallback) {
|
|
103
|
+
const n = Number(v);
|
|
104
|
+
return isNaN(n) ? fallback : n;
|
|
105
|
+
}
|
|
74
106
|
var index_default = (api) => {
|
|
75
107
|
const cfg = resolveConfig(api.config);
|
|
76
108
|
const client = new CrmyClient(cfg);
|
|
77
109
|
api.registerTool({
|
|
78
|
-
id: "
|
|
79
|
-
name: "CRMy
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
properties: {
|
|
84
|
-
q: { type: "string", description: "Search query" },
|
|
85
|
-
limit: { type: "number", description: "Max results (default 10)" }
|
|
86
|
-
},
|
|
87
|
-
required: ["q"]
|
|
88
|
-
},
|
|
89
|
-
handler: async (input) => client.get("/search", { q: input.q, limit: input.limit ?? 10 })
|
|
90
|
-
});
|
|
91
|
-
api.registerTool({
|
|
92
|
-
id: "crmy_contact_search",
|
|
93
|
-
name: "CRMy: Search Contacts",
|
|
94
|
-
description: "Search for contacts by name, email, company, or any keyword. Supports optional lifecycle stage filter.",
|
|
95
|
-
input: {
|
|
96
|
-
type: "object",
|
|
97
|
-
properties: {
|
|
98
|
-
q: { type: "string", description: "Search query (name, email, company, etc.)" },
|
|
99
|
-
stage: { type: "string", description: "Filter by lifecycle stage (e.g. prospect, customer, churned)" },
|
|
100
|
-
limit: { type: "number", description: "Max results (default 20)" }
|
|
101
|
-
},
|
|
102
|
-
required: ["q"]
|
|
103
|
-
},
|
|
104
|
-
handler: async (input) => client.get("/contacts", { q: input.q, stage: input.stage, limit: input.limit ?? 20 })
|
|
105
|
-
});
|
|
106
|
-
api.registerTool({
|
|
107
|
-
id: "crmy_contact_create",
|
|
108
|
-
name: "CRMy: Create Contact",
|
|
109
|
-
description: "Create a new contact in CRMy. At minimum provide a name. Email, phone, title, and account_id are optional.",
|
|
110
|
+
id: "crmy",
|
|
111
|
+
name: "CRMy CRM",
|
|
112
|
+
// Keep the top-level description short — OpenClaw loads it for every turn.
|
|
113
|
+
// Detail lives in SKILL.md which is loaded only when this tool is active.
|
|
114
|
+
description: "Interact with the CRMy CRM. Specify an action and optional params. Valid actions: " + VALID_ACTIONS.join(", ") + ". See SKILL.md for param shapes and workflow guidance.",
|
|
110
115
|
input: {
|
|
111
116
|
type: "object",
|
|
112
117
|
properties: {
|
|
113
|
-
|
|
114
|
-
email: { type: "string", description: "Email address" },
|
|
115
|
-
phone: { type: "string", description: "Phone number" },
|
|
116
|
-
title: { type: "string", description: "Job title" },
|
|
117
|
-
account_id: { type: "string", description: "UUID of the associated account/company" },
|
|
118
|
-
lifecycle_stage: { type: "string", description: "Initial lifecycle stage (e.g. prospect, lead, customer)" },
|
|
119
|
-
notes: { type: "string", description: "Any initial notes about this contact" }
|
|
120
|
-
},
|
|
121
|
-
required: ["name"]
|
|
122
|
-
},
|
|
123
|
-
handler: async (input) => client.post("/contacts", input)
|
|
124
|
-
});
|
|
125
|
-
api.registerTool({
|
|
126
|
-
id: "crmy_contact_update",
|
|
127
|
-
name: "CRMy: Update Contact",
|
|
128
|
-
description: "Update fields on an existing contact. Provide the contact id and any fields to change.",
|
|
129
|
-
input: {
|
|
130
|
-
type: "object",
|
|
131
|
-
properties: {
|
|
132
|
-
id: { type: "string", description: "Contact UUID (required)" },
|
|
133
|
-
name: { type: "string", description: "Updated name" },
|
|
134
|
-
email: { type: "string", description: "Updated email" },
|
|
135
|
-
phone: { type: "string", description: "Updated phone" },
|
|
136
|
-
title: { type: "string", description: "Updated job title" },
|
|
137
|
-
account_id: { type: "string", description: "Move to a different account UUID" }
|
|
138
|
-
},
|
|
139
|
-
required: ["id"]
|
|
140
|
-
},
|
|
141
|
-
handler: async ({ id, ...rest }) => client.patch(`/contacts/${id}`, rest)
|
|
142
|
-
});
|
|
143
|
-
api.registerTool({
|
|
144
|
-
id: "crmy_contact_log_activity",
|
|
145
|
-
name: "CRMy: Log Activity",
|
|
146
|
-
description: "Log a call, email, meeting, or other activity against a contact, account, or opportunity. Provide subject_type + subject_id to attach it to the right record.",
|
|
147
|
-
input: {
|
|
148
|
-
type: "object",
|
|
149
|
-
properties: {
|
|
150
|
-
activity_type: {
|
|
151
|
-
type: "string",
|
|
152
|
-
description: "Type of activity \u2014 e.g. call, email, meeting, demo, proposal, note"
|
|
153
|
-
},
|
|
154
|
-
subject_type: {
|
|
118
|
+
action: {
|
|
155
119
|
type: "string",
|
|
156
|
-
description: "
|
|
157
|
-
enum:
|
|
120
|
+
description: "CRMy action to perform (see SKILL.md for the full list).",
|
|
121
|
+
enum: VALID_ACTIONS
|
|
158
122
|
},
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
duration_minutes: { type: "number", description: "Duration in minutes (for calls and meetings)" },
|
|
164
|
-
notes: { type: "string", description: "Detailed notes" }
|
|
165
|
-
},
|
|
166
|
-
required: ["activity_type", "subject_type", "subject_id", "summary"]
|
|
167
|
-
},
|
|
168
|
-
handler: async (input) => client.post("/activities", input)
|
|
169
|
-
});
|
|
170
|
-
api.registerTool({
|
|
171
|
-
id: "crmy_contact_set_lifecycle",
|
|
172
|
-
name: "CRMy: Set Contact Lifecycle Stage",
|
|
173
|
-
description: "Change the lifecycle stage of a contact (e.g. lead \u2192 prospect \u2192 customer \u2192 churned).",
|
|
174
|
-
input: {
|
|
175
|
-
type: "object",
|
|
176
|
-
properties: {
|
|
177
|
-
id: { type: "string", description: "Contact UUID" },
|
|
178
|
-
stage: { type: "string", description: "New lifecycle stage (e.g. lead, prospect, customer, churned)" },
|
|
179
|
-
note: { type: "string", description: "Optional note explaining the stage change" }
|
|
180
|
-
},
|
|
181
|
-
required: ["id", "stage"]
|
|
182
|
-
},
|
|
183
|
-
handler: async ({ id, ...rest }) => client.patch(`/contacts/${id}`, rest)
|
|
184
|
-
});
|
|
185
|
-
api.registerTool({
|
|
186
|
-
id: "crmy_account_search",
|
|
187
|
-
name: "CRMy: Search Accounts",
|
|
188
|
-
description: "Search for companies/accounts by name, industry, or keyword.",
|
|
189
|
-
input: {
|
|
190
|
-
type: "object",
|
|
191
|
-
properties: {
|
|
192
|
-
q: { type: "string", description: "Search query (company name, domain, etc.)" },
|
|
193
|
-
industry: { type: "string", description: "Filter by industry" },
|
|
194
|
-
limit: { type: "number", description: "Max results (default 20)" }
|
|
195
|
-
},
|
|
196
|
-
required: ["q"]
|
|
197
|
-
},
|
|
198
|
-
handler: async (input) => client.get("/accounts", { q: input.q, industry: input.industry, limit: input.limit ?? 20 })
|
|
199
|
-
});
|
|
200
|
-
api.registerTool({
|
|
201
|
-
id: "crmy_account_create",
|
|
202
|
-
name: "CRMy: Create Account",
|
|
203
|
-
description: "Create a new company/account record in CRMy.",
|
|
204
|
-
input: {
|
|
205
|
-
type: "object",
|
|
206
|
-
properties: {
|
|
207
|
-
name: { type: "string", description: "Company name (required)" },
|
|
208
|
-
domain: { type: "string", description: "Company website domain (e.g. acme.com)" },
|
|
209
|
-
industry: { type: "string", description: "Industry sector" },
|
|
210
|
-
size: { type: "string", description: "Company size (e.g. 1-10, 11-50, 51-200, 201-1000, 1000+)" },
|
|
211
|
-
notes: { type: "string", description: "Any initial notes about this company" }
|
|
212
|
-
},
|
|
213
|
-
required: ["name"]
|
|
214
|
-
},
|
|
215
|
-
handler: async (input) => client.post("/accounts", input)
|
|
216
|
-
});
|
|
217
|
-
api.registerTool({
|
|
218
|
-
id: "crmy_opportunity_search",
|
|
219
|
-
name: "CRMy: Search Opportunities",
|
|
220
|
-
description: "Search for deals/opportunities by name, account, or stage.",
|
|
221
|
-
input: {
|
|
222
|
-
type: "object",
|
|
223
|
-
properties: {
|
|
224
|
-
q: { type: "string", description: "Search query (deal name, account, etc.)" },
|
|
225
|
-
stage: { type: "string", description: "Filter by deal stage (e.g. prospecting, proposal, closed_won)" },
|
|
226
|
-
account_id: { type: "string", description: "Filter by account UUID" },
|
|
227
|
-
limit: { type: "number", description: "Max results (default 20)" }
|
|
228
|
-
},
|
|
229
|
-
required: ["q"]
|
|
230
|
-
},
|
|
231
|
-
handler: async (input) => client.get("/opportunities", {
|
|
232
|
-
q: input.q,
|
|
233
|
-
stage: input.stage,
|
|
234
|
-
account_id: input.account_id,
|
|
235
|
-
limit: input.limit ?? 20
|
|
236
|
-
})
|
|
237
|
-
});
|
|
238
|
-
api.registerTool({
|
|
239
|
-
id: "crmy_opportunity_create",
|
|
240
|
-
name: "CRMy: Create Opportunity",
|
|
241
|
-
description: "Create a new deal/opportunity in CRMy. Provide a name and optionally an account, value, and stage.",
|
|
242
|
-
input: {
|
|
243
|
-
type: "object",
|
|
244
|
-
properties: {
|
|
245
|
-
name: { type: "string", description: "Deal name (required)" },
|
|
246
|
-
account_id: { type: "string", description: "Associated account UUID" },
|
|
247
|
-
value: { type: "number", description: "Deal value in your base currency" },
|
|
248
|
-
stage: { type: "string", description: "Initial deal stage (defaults to prospecting)" },
|
|
249
|
-
close_date: { type: "string", description: "Expected close date (ISO 8601, e.g. 2026-06-30)" },
|
|
250
|
-
notes: { type: "string", description: "Any notes about this deal" }
|
|
251
|
-
},
|
|
252
|
-
required: ["name"]
|
|
253
|
-
},
|
|
254
|
-
handler: async (input) => client.post("/opportunities", input)
|
|
255
|
-
});
|
|
256
|
-
api.registerTool({
|
|
257
|
-
id: "crmy_opportunity_advance_stage",
|
|
258
|
-
name: "CRMy: Advance Opportunity Stage",
|
|
259
|
-
description: "Move a deal to a new stage (e.g. proposal \u2192 negotiation \u2192 closed_won). Optionally add a note.",
|
|
260
|
-
input: {
|
|
261
|
-
type: "object",
|
|
262
|
-
properties: {
|
|
263
|
-
id: { type: "string", description: "Opportunity UUID (required)" },
|
|
264
|
-
stage: { type: "string", description: "New stage name (required)" },
|
|
265
|
-
note: { type: "string", description: "Note explaining the stage transition" },
|
|
266
|
-
lost_reason: { type: "string", description: "If closing as lost, the reason" }
|
|
123
|
+
params: {
|
|
124
|
+
type: "object",
|
|
125
|
+
description: "Parameters for the chosen action. Shape varies by action \u2014 see SKILL.md. All fields are optional except where noted (e.g. id for updates, name for creates)."
|
|
126
|
+
}
|
|
267
127
|
},
|
|
268
|
-
required: ["
|
|
128
|
+
required: ["action"]
|
|
269
129
|
},
|
|
270
|
-
handler: async (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
input: {
|
|
277
|
-
type: "object",
|
|
278
|
-
properties: {
|
|
279
|
-
group_by: {
|
|
280
|
-
type: "string",
|
|
281
|
-
description: "Group results by: stage (default), owner, or forecast_cat",
|
|
282
|
-
enum: ["stage", "owner", "forecast_cat"]
|
|
283
|
-
}
|
|
130
|
+
handler: async (input) => {
|
|
131
|
+
const action = str(input.action);
|
|
132
|
+
if (!action || !ROUTES[action]) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Unknown CRMy action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`
|
|
135
|
+
);
|
|
284
136
|
}
|
|
285
|
-
|
|
286
|
-
|
|
137
|
+
const params = input.params ?? {};
|
|
138
|
+
api.logger?.info(`[crmy] action=${action}`);
|
|
139
|
+
return ROUTES[action](client, params);
|
|
140
|
+
}
|
|
287
141
|
});
|
|
288
142
|
};
|
|
289
143
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crmy/openclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "CRMy plugin for OpenClaw — expose CRM tools (contacts, accounts, opportunities, pipeline) in any AI assistant running OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|