@crmy/openclaw-plugin 0.5.9

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,29 @@
1
+ interface ToolDef {
2
+ id: string;
3
+ name: string;
4
+ 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
+ handler: (input: Record<string, unknown>) => Promise<unknown>;
15
+ }
16
+ interface OpenClawApi {
17
+ registerTool(tool: ToolDef): void;
18
+ config?: {
19
+ serverUrl?: string;
20
+ apiKey?: string;
21
+ };
22
+ logger?: {
23
+ info(msg: string): void;
24
+ error(msg: string): void;
25
+ };
26
+ }
27
+ declare const _default: (api: OpenClawApi) => void;
28
+
29
+ export { _default as default };
package/dist/index.js ADDED
@@ -0,0 +1,291 @@
1
+ // src/client.ts
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ function resolveConfig(pluginConfig) {
6
+ let fileConfig = {};
7
+ try {
8
+ const raw = fs.readFileSync(path.join(os.homedir(), ".crmy", "config.json"), "utf-8");
9
+ fileConfig = JSON.parse(raw);
10
+ } catch {
11
+ }
12
+ const serverUrl = pluginConfig?.serverUrl ?? process.env.CRMY_SERVER_URL ?? fileConfig.serverUrl ?? "http://localhost:3000";
13
+ const apiKey = pluginConfig?.apiKey ?? process.env.CRMY_API_KEY ?? fileConfig.apiKey ?? "";
14
+ return { serverUrl: serverUrl.replace(/\/$/, ""), apiKey };
15
+ }
16
+ var CrmyClient = class {
17
+ base;
18
+ headers;
19
+ constructor(cfg) {
20
+ this.base = `${cfg.serverUrl}/api/v1`;
21
+ this.headers = {
22
+ "Authorization": `Bearer ${cfg.apiKey}`,
23
+ "Content-Type": "application/json"
24
+ };
25
+ }
26
+ async get(endpoint, params) {
27
+ const url = new URL(`${this.base}${endpoint}`);
28
+ if (params) {
29
+ for (const [k, v] of Object.entries(params)) {
30
+ if (v !== void 0 && v !== null && v !== "") {
31
+ url.searchParams.set(k, String(v));
32
+ }
33
+ }
34
+ }
35
+ const res = await fetch(url.toString(), { headers: this.headers });
36
+ return this.parse(res);
37
+ }
38
+ async post(endpoint, body) {
39
+ const res = await fetch(`${this.base}${endpoint}`, {
40
+ method: "POST",
41
+ headers: this.headers,
42
+ body: JSON.stringify(body)
43
+ });
44
+ return this.parse(res);
45
+ }
46
+ async patch(endpoint, body) {
47
+ const res = await fetch(`${this.base}${endpoint}`, {
48
+ method: "PATCH",
49
+ headers: this.headers,
50
+ body: JSON.stringify(body)
51
+ });
52
+ return this.parse(res);
53
+ }
54
+ async parse(res) {
55
+ const text = await res.text();
56
+ if (!res.ok) {
57
+ let msg = text;
58
+ try {
59
+ const j = JSON.parse(text);
60
+ msg = j.error ?? j.message ?? text;
61
+ } catch {
62
+ }
63
+ throw new Error(`CRMy API error ${res.status}: ${msg}`);
64
+ }
65
+ try {
66
+ return JSON.parse(text);
67
+ } catch {
68
+ return text;
69
+ }
70
+ }
71
+ };
72
+
73
+ // src/index.ts
74
+ var index_default = (api) => {
75
+ const cfg = resolveConfig(api.config);
76
+ const client = new CrmyClient(cfg);
77
+ api.registerTool({
78
+ id: "crmy_search",
79
+ name: "CRMy: Search",
80
+ description: "Search across all CRMy records \u2014 contacts, accounts, opportunities, activities, and more. Use this as a first step when you are not sure which record type the user is referring to.",
81
+ input: {
82
+ type: "object",
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
+ input: {
111
+ type: "object",
112
+ properties: {
113
+ name: { type: "string", description: "Full name (required)" },
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: {
155
+ type: "string",
156
+ description: "Record type the activity is attached to: contact, account, or opportunity",
157
+ enum: ["contact", "account", "opportunity"]
158
+ },
159
+ subject_id: { type: "string", description: "UUID of the contact, account, or opportunity" },
160
+ summary: { type: "string", description: "Short summary of what happened" },
161
+ outcome: { type: "string", description: "Outcome of the activity (e.g. positive, neutral, negative)" },
162
+ performed_at: { type: "string", description: "ISO 8601 timestamp (defaults to now)" },
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" }
267
+ },
268
+ required: ["id", "stage"]
269
+ },
270
+ handler: async ({ id, ...rest }) => client.patch(`/opportunities/${id}`, rest)
271
+ });
272
+ api.registerTool({
273
+ id: "crmy_pipeline_summary",
274
+ name: "CRMy: Pipeline Summary",
275
+ description: 'Get a summary of the sales pipeline \u2014 total deal count and value by stage. Useful for answering questions like "how many deals do we have?" or "what is our pipeline worth?"',
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
+ }
284
+ }
285
+ },
286
+ handler: async (input) => client.get("/analytics/pipeline", { group_by: input.group_by ?? "stage" })
287
+ });
288
+ };
289
+ export {
290
+ index_default as default
291
+ };
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "crmy",
3
+ "name": "CRMy — Agent-first CRM",
4
+ "configSchema": {
5
+ "type": "object",
6
+ "properties": {
7
+ "serverUrl": {
8
+ "type": "string",
9
+ "description": "CRMy server URL. Defaults to the value in ~/.crmy/config.json, then http://localhost:3000."
10
+ },
11
+ "apiKey": {
12
+ "type": "string",
13
+ "description": "CRMy API key (starts with crmy_). Defaults to the value in ~/.crmy/config.json."
14
+ }
15
+ }
16
+ },
17
+ "uiHints": {
18
+ "serverUrl": {
19
+ "label": "Server URL",
20
+ "placeholder": "http://localhost:3000"
21
+ },
22
+ "apiKey": {
23
+ "label": "API Key",
24
+ "placeholder": "crmy_..."
25
+ }
26
+ }
27
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@crmy/openclaw-plugin",
3
+ "version": "0.5.9",
4
+ "description": "CRMy plugin for OpenClaw — expose CRM tools (contacts, accounts, opportunities, pipeline) in any AI assistant running OpenClaw",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "openclaw.plugin.json"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format esm --dts --clean",
14
+ "prepare": "npm run build",
15
+ "dev": "tsup src/index.ts --format esm --watch"
16
+ },
17
+ "devDependencies": {
18
+ "tsup": "^8.3.0",
19
+ "typescript": "^5.6.3"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "keywords": ["openclaw", "crm", "mcp", "crmy", "plugin"],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/codycharris/crmy"
26
+ }
27
+ }