@ebowwa/crm 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.
- package/README.md +174 -0
- package/dist/cli/commands/activities.d.ts +11 -0
- package/dist/cli/commands/activities.d.ts.map +1 -0
- package/dist/cli/commands/activities.js +427 -0
- package/dist/cli/commands/activities.js.map +1 -0
- package/dist/cli/commands/contacts.d.ts +11 -0
- package/dist/cli/commands/contacts.d.ts.map +1 -0
- package/dist/cli/commands/contacts.js +458 -0
- package/dist/cli/commands/contacts.js.map +1 -0
- package/dist/cli/commands/deals.d.ts +11 -0
- package/dist/cli/commands/deals.d.ts.map +1 -0
- package/dist/cli/commands/deals.js +498 -0
- package/dist/cli/commands/deals.js.map +1 -0
- package/dist/cli/commands/media.d.ts +11 -0
- package/dist/cli/commands/media.d.ts.map +1 -0
- package/dist/cli/commands/media.js +417 -0
- package/dist/cli/commands/media.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +346 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl.d.ts +15 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +318 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/utils/config.d.ts +91 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +212 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/output.d.ts +136 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +323 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/cli/utils/prompt.d.ts +81 -0
- package/dist/cli/utils/prompt.d.ts.map +1 -0
- package/dist/cli/utils/prompt.js +341 -0
- package/dist/cli/utils/prompt.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +32 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemas.d.ts +3050 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +667 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/types.d.ts +597 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +18 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/storage/client.d.ts +109 -0
- package/dist/mcp/storage/client.d.ts.map +1 -0
- package/dist/mcp/storage/client.js +355 -0
- package/dist/mcp/storage/client.js.map +1 -0
- package/dist/mcp/storage/index.d.ts +7 -0
- package/dist/mcp/storage/index.d.ts.map +1 -0
- package/dist/mcp/storage/index.js +6 -0
- package/dist/mcp/storage/index.js.map +1 -0
- package/dist/mcp/storage/types.d.ts +44 -0
- package/dist/mcp/storage/types.d.ts.map +1 -0
- package/dist/mcp/storage/types.js +35 -0
- package/dist/mcp/storage/types.js.map +1 -0
- package/dist/mcp/tools/definitions.d.ts +16 -0
- package/dist/mcp/tools/definitions.d.ts.map +1 -0
- package/dist/mcp/tools/definitions.js +914 -0
- package/dist/mcp/tools/definitions.js.map +1 -0
- package/dist/mcp/tools/handlers.d.ts +50 -0
- package/dist/mcp/tools/handlers.d.ts.map +1 -0
- package/dist/mcp/tools/handlers.js +760 -0
- package/dist/mcp/tools/handlers.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +7 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +6 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/types.d.ts +314 -0
- package/dist/mcp/tools/types.d.ts.map +1 -0
- package/dist/mcp/tools/types.js +5 -0
- package/dist/mcp/tools/types.js.map +1 -0
- package/dist/mcp/transports/stdio.d.ts +27 -0
- package/dist/mcp/transports/stdio.d.ts.map +1 -0
- package/dist/mcp/transports/stdio.js +237 -0
- package/dist/mcp/transports/stdio.js.map +1 -0
- package/dist/telemetry/index.d.ts +58 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +109 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/logger.d.ts +116 -0
- package/dist/telemetry/logger.d.ts.map +1 -0
- package/dist/telemetry/logger.js +256 -0
- package/dist/telemetry/logger.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +115 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +292 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tracer.d.ts +227 -0
- package/dist/telemetry/tracer.d.ts.map +1 -0
- package/dist/telemetry/tracer.js +355 -0
- package/dist/telemetry/tracer.js.map +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +115 -0
- package/dist/web/app.js.map +1 -0
- package/dist/web/components/ContactList.d.ts +3 -0
- package/dist/web/components/ContactList.d.ts.map +1 -0
- package/dist/web/components/ContactList.js +262 -0
- package/dist/web/components/ContactList.js.map +1 -0
- package/dist/web/components/Dashboard.d.ts +3 -0
- package/dist/web/components/Dashboard.d.ts.map +1 -0
- package/dist/web/components/Dashboard.js +158 -0
- package/dist/web/components/Dashboard.js.map +1 -0
- package/dist/web/components/DealPipeline.d.ts +3 -0
- package/dist/web/components/DealPipeline.d.ts.map +1 -0
- package/dist/web/components/DealPipeline.js +306 -0
- package/dist/web/components/DealPipeline.js.map +1 -0
- package/dist/web/index.d.ts +2 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +269 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/types.d.ts +75 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +3 -0
- package/dist/web/types.js.map +1 -0
- package/native/index.d.ts +571 -0
- package/native/index.js +687 -0
- package/package.json +105 -0
- package/src/cli/commands/activities.ts +543 -0
- package/src/cli/commands/contacts.ts +563 -0
- package/src/cli/commands/deals.ts +637 -0
- package/src/cli/commands/media.ts +521 -0
- package/src/cli/commands/search.ts +426 -0
- package/src/cli/index.ts +203 -0
- package/src/cli/repl.ts +379 -0
- package/src/cli/utils/config.ts +299 -0
- package/src/cli/utils/output.ts +386 -0
- package/src/cli/utils/prompt.ts +444 -0
- package/src/cli.ts +11 -0
- package/src/core/index.ts +184 -0
- package/src/core/schemas.ts +770 -0
- package/src/core/types.ts +969 -0
- package/src/index.ts +8 -0
- package/src/mcp/index.ts +17 -0
- package/src/mcp/server.ts +26 -0
- package/src/mcp/storage/client.ts +408 -0
- package/src/mcp/storage/index.ts +7 -0
- package/src/mcp/storage/types.ts +72 -0
- package/src/mcp/tools/definitions.ts +961 -0
- package/src/mcp/tools/handlers.ts +805 -0
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/types.ts +390 -0
- package/src/mcp/transports/stdio.ts +225 -0
- package/src/telemetry/index.ts +131 -0
- package/src/telemetry/logger.ts +318 -0
- package/src/telemetry/metrics.ts +393 -0
- package/src/telemetry/tracer.ts +487 -0
- package/src/web/api/activities.ts +41 -0
- package/src/web/api/contacts.ts +114 -0
- package/src/web/api/deals.ts +108 -0
- package/src/web/api/media.ts +98 -0
- package/src/web/app.tsx +143 -0
- package/src/web/components/ActivityFeed.tsx +195 -0
- package/src/web/components/ContactList.tsx +340 -0
- package/src/web/components/Dashboard.tsx +214 -0
- package/src/web/components/DealPipeline.tsx +405 -0
- package/src/web/components/MediaGallery.tsx +334 -0
- package/src/web/index.html +14 -0
- package/src/web/index.ts +326 -0
- package/src/web/styles/main.css +180 -0
- package/src/web/types.ts +311 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool handlers for CRM operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CRMStorageClient } from '../storage/client.js';
|
|
6
|
+
import type { ToolInput, ToolOutput } from './types.js';
|
|
7
|
+
import { CRMError, NotFoundError } from '../storage/types.js';
|
|
8
|
+
import type { Contact, Deal, Activity, Media, Note, Tag, Company } from '../../core/types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tool handlers class containing all MCP tool implementations
|
|
12
|
+
*/
|
|
13
|
+
export class ToolHandlers {
|
|
14
|
+
constructor(private storage: CRMStorageClient) {}
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Contact Handlers
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
async createContact(input: ToolInput): Promise<ToolOutput> {
|
|
21
|
+
try {
|
|
22
|
+
const name = input.name as string | undefined;
|
|
23
|
+
if (!name) {
|
|
24
|
+
return { success: false, error: 'Contact name is required' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const contact = await this.storage.insert<Contact>('contacts', {
|
|
29
|
+
name,
|
|
30
|
+
firstName: input.firstName as string | undefined,
|
|
31
|
+
lastName: input.lastName as string | undefined,
|
|
32
|
+
emails: (input.emails as Contact['emails']) ?? [],
|
|
33
|
+
phones: (input.phones as Contact['phones']) ?? [],
|
|
34
|
+
addresses: [],
|
|
35
|
+
socialProfiles: [],
|
|
36
|
+
company: input.company as string | undefined,
|
|
37
|
+
title: input.title as string | undefined,
|
|
38
|
+
department: undefined,
|
|
39
|
+
website: undefined,
|
|
40
|
+
tags: (input.tags as string[]) ?? [],
|
|
41
|
+
customFields: [],
|
|
42
|
+
source: input.source as Contact['source'],
|
|
43
|
+
status: (input.status as Contact['status']) ?? 'lead',
|
|
44
|
+
ownerId: undefined,
|
|
45
|
+
avatar: undefined,
|
|
46
|
+
preferredContact: undefined,
|
|
47
|
+
language: undefined,
|
|
48
|
+
timezone: undefined,
|
|
49
|
+
preferences: undefined,
|
|
50
|
+
leadScore: undefined,
|
|
51
|
+
doNotContact: false,
|
|
52
|
+
lastContactedAt: undefined,
|
|
53
|
+
nextFollowUpAt: undefined,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return { success: true, data: contact };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return this.handleError(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getContact(input: ToolInput): Promise<ToolOutput> {
|
|
63
|
+
try {
|
|
64
|
+
const id = input.id as string | undefined;
|
|
65
|
+
if (!id) {
|
|
66
|
+
return { success: false, error: 'Contact ID is required' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const contact = this.storage.get<Contact>('contacts', id);
|
|
70
|
+
if (!contact) {
|
|
71
|
+
return { success: false, error: `Contact not found: ${id}` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { success: true, data: contact };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return this.handleError(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async updateContact(input: ToolInput): Promise<ToolOutput> {
|
|
81
|
+
try {
|
|
82
|
+
const id = input.id as string | undefined;
|
|
83
|
+
if (!id) {
|
|
84
|
+
return { success: false, error: 'Contact ID is required' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const updates: Partial<Contact> = {};
|
|
88
|
+
if (input.name !== undefined) updates.name = input.name as string;
|
|
89
|
+
if (input.firstName !== undefined) updates.firstName = input.firstName as string;
|
|
90
|
+
if (input.lastName !== undefined) updates.lastName = input.lastName as string;
|
|
91
|
+
if (input.emails !== undefined) updates.emails = input.emails as Contact['emails'];
|
|
92
|
+
if (input.phones !== undefined) updates.phones = input.phones as Contact['phones'];
|
|
93
|
+
if (input.company !== undefined) updates.company = input.company as string;
|
|
94
|
+
if (input.title !== undefined) updates.title = input.title as string;
|
|
95
|
+
if (input.tags !== undefined) updates.tags = input.tags as string[];
|
|
96
|
+
if (input.status !== undefined) updates.status = input.status as Contact['status'];
|
|
97
|
+
|
|
98
|
+
const contact = await this.storage.update<Contact>('contacts', id, updates);
|
|
99
|
+
return { success: true, data: contact };
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return this.handleError(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async deleteContact(input: ToolInput): Promise<ToolOutput> {
|
|
106
|
+
try {
|
|
107
|
+
const id = input.id as string | undefined;
|
|
108
|
+
if (!id) {
|
|
109
|
+
return { success: false, error: 'Contact ID is required' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const deleted = await this.storage.delete('contacts', id);
|
|
113
|
+
return { success: true, data: { id, deleted } };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return this.handleError(error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async listContacts(input: ToolInput): Promise<ToolOutput> {
|
|
120
|
+
try {
|
|
121
|
+
const limit = (input.limit as number) ?? 20;
|
|
122
|
+
const offset = (input.offset as number) ?? 0;
|
|
123
|
+
|
|
124
|
+
const contacts = this.storage.list<Contact>('contacts', { limit, offset });
|
|
125
|
+
const total = this.storage.count('contacts');
|
|
126
|
+
|
|
127
|
+
return { success: true, data: { contacts, total } };
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return this.handleError(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async searchContacts(input: ToolInput): Promise<ToolOutput> {
|
|
134
|
+
try {
|
|
135
|
+
const email = input.email as string | undefined;
|
|
136
|
+
if (!email) {
|
|
137
|
+
return { success: false, error: 'Email is required' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const contacts = this.storage.findByIndex<Contact>('contacts_by_email', email.toLowerCase());
|
|
141
|
+
return { success: true, data: { contacts } };
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return this.handleError(error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Deal Handlers
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
async createDeal(input: ToolInput): Promise<ToolOutput> {
|
|
152
|
+
try {
|
|
153
|
+
const title = input.title as string | undefined;
|
|
154
|
+
const contactId = input.contactId as string | undefined;
|
|
155
|
+
const value = input.value as number | undefined;
|
|
156
|
+
const expectedClose = input.expectedClose as string | undefined;
|
|
157
|
+
|
|
158
|
+
if (!title) return { success: false, error: 'Deal title is required' };
|
|
159
|
+
if (!contactId) return { success: false, error: 'Contact ID is required' };
|
|
160
|
+
if (value === undefined) return { success: false, error: 'Deal value is required' };
|
|
161
|
+
if (!expectedClose) return { success: false, error: 'Expected close date is required' };
|
|
162
|
+
|
|
163
|
+
const deal = await this.storage.insert<Deal>('deals', {
|
|
164
|
+
title,
|
|
165
|
+
contactId,
|
|
166
|
+
companyId: undefined,
|
|
167
|
+
value,
|
|
168
|
+
currency: (input.currency as Deal['currency']) ?? 'USD',
|
|
169
|
+
stage: (input.stage as Deal['stage']) ?? 'prospecting',
|
|
170
|
+
probability: (input.probability as number) ?? 0,
|
|
171
|
+
expectedClose,
|
|
172
|
+
actualClose: undefined,
|
|
173
|
+
priority: (input.priority as Deal['priority']) ?? 'medium',
|
|
174
|
+
lineItems: [],
|
|
175
|
+
discount: undefined,
|
|
176
|
+
discountType: undefined,
|
|
177
|
+
totalValue: value,
|
|
178
|
+
notes: (input.notes as string) ?? '',
|
|
179
|
+
tags: (input.tags as string[]) ?? [],
|
|
180
|
+
competitors: [],
|
|
181
|
+
lossReason: undefined,
|
|
182
|
+
nextSteps: undefined,
|
|
183
|
+
ownerId: undefined,
|
|
184
|
+
source: undefined,
|
|
185
|
+
customFields: [],
|
|
186
|
+
lastActivityAt: undefined,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return { success: true, data: deal };
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return this.handleError(error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async getDeal(input: ToolInput): Promise<ToolOutput> {
|
|
196
|
+
try {
|
|
197
|
+
const id = input.id as string | undefined;
|
|
198
|
+
if (!id) {
|
|
199
|
+
return { success: false, error: 'Deal ID is required' };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const deal = this.storage.get<Deal>('deals', id);
|
|
203
|
+
if (!deal) {
|
|
204
|
+
return { success: false, error: `Deal not found: ${id}` };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { success: true, data: deal };
|
|
208
|
+
} catch (error) {
|
|
209
|
+
return this.handleError(error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async updateDeal(input: ToolInput): Promise<ToolOutput> {
|
|
214
|
+
try {
|
|
215
|
+
const id = input.id as string | undefined;
|
|
216
|
+
if (!id) {
|
|
217
|
+
return { success: false, error: 'Deal ID is required' };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const updates: Partial<Deal> = {};
|
|
221
|
+
if (input.title !== undefined) updates.title = input.title as string;
|
|
222
|
+
if (input.value !== undefined) updates.value = input.value as number;
|
|
223
|
+
if (input.stage !== undefined) updates.stage = input.stage as Deal['stage'];
|
|
224
|
+
if (input.probability !== undefined) updates.probability = input.probability as number;
|
|
225
|
+
if (input.expectedClose !== undefined) updates.expectedClose = input.expectedClose as string;
|
|
226
|
+
if (input.actualClose !== undefined) updates.actualClose = input.actualClose as string;
|
|
227
|
+
if (input.priority !== undefined) updates.priority = input.priority as Deal['priority'];
|
|
228
|
+
if (input.notes !== undefined) updates.notes = input.notes as string;
|
|
229
|
+
if (input.tags !== undefined) updates.tags = input.tags as string[];
|
|
230
|
+
|
|
231
|
+
const deal = await this.storage.update<Deal>('deals', id, updates);
|
|
232
|
+
return { success: true, data: deal };
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return this.handleError(error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async deleteDeal(input: ToolInput): Promise<ToolOutput> {
|
|
239
|
+
try {
|
|
240
|
+
const id = input.id as string | undefined;
|
|
241
|
+
if (!id) {
|
|
242
|
+
return { success: false, error: 'Deal ID is required' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const deleted = await this.storage.delete('deals', id);
|
|
246
|
+
return { success: true, data: { id, deleted } };
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return this.handleError(error);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async listDeals(input: ToolInput): Promise<ToolOutput> {
|
|
253
|
+
try {
|
|
254
|
+
const limit = (input.limit as number) ?? 20;
|
|
255
|
+
const offset = (input.offset as number) ?? 0;
|
|
256
|
+
|
|
257
|
+
const deals = this.storage.list<Deal>('deals', { limit, offset });
|
|
258
|
+
const total = this.storage.count('deals');
|
|
259
|
+
|
|
260
|
+
return { success: true, data: { deals, total } };
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return this.handleError(error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async getDealsByStage(input: ToolInput): Promise<ToolOutput> {
|
|
267
|
+
try {
|
|
268
|
+
const stage = input.stage as string | undefined;
|
|
269
|
+
if (!stage) {
|
|
270
|
+
return { success: false, error: 'Stage is required' };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const deals = this.storage.findByIndex<Deal>('deals_by_stage', stage);
|
|
274
|
+
return { success: true, data: { deals } };
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return this.handleError(error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// Activity Handlers
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
async createActivity(input: ToolInput): Promise<ToolOutput> {
|
|
285
|
+
try {
|
|
286
|
+
const type = input.type as string | undefined;
|
|
287
|
+
const title = input.title as string | undefined;
|
|
288
|
+
if (!type) return { success: false, error: 'Activity type is required' };
|
|
289
|
+
if (!title) return { success: false, error: 'Activity title is required' };
|
|
290
|
+
|
|
291
|
+
const activity = await this.storage.insert<Activity>('activities', {
|
|
292
|
+
contactId: input.contactId as string | undefined,
|
|
293
|
+
dealId: input.dealId as string | undefined,
|
|
294
|
+
type: type as Activity['type'],
|
|
295
|
+
title,
|
|
296
|
+
description: (input.description as string) ?? '',
|
|
297
|
+
timestamp: (input.timestamp as string) ?? new Date().toISOString(),
|
|
298
|
+
duration: input.duration as number | undefined,
|
|
299
|
+
metadata: (input.metadata as Activity['metadata']) ?? {},
|
|
300
|
+
createdBy: undefined,
|
|
301
|
+
tags: (input.tags as string[]) ?? [],
|
|
302
|
+
customFields: [],
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return { success: true, data: activity };
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return this.handleError(error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async getActivity(input: ToolInput): Promise<ToolOutput> {
|
|
312
|
+
try {
|
|
313
|
+
const id = input.id as string | undefined;
|
|
314
|
+
if (!id) {
|
|
315
|
+
return { success: false, error: 'Activity ID is required' };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const activity = this.storage.get<Activity>('activities', id);
|
|
319
|
+
if (!activity) {
|
|
320
|
+
return { success: false, error: `Activity not found: ${id}` };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { success: true, data: activity };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return this.handleError(error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async listActivities(input: ToolInput): Promise<ToolOutput> {
|
|
330
|
+
try {
|
|
331
|
+
const limit = (input.limit as number) ?? 20;
|
|
332
|
+
const offset = (input.offset as number) ?? 0;
|
|
333
|
+
|
|
334
|
+
const activities = this.storage.list<Activity>('activities', { limit, offset });
|
|
335
|
+
const total = this.storage.count('activities');
|
|
336
|
+
|
|
337
|
+
return { success: true, data: { activities, total } };
|
|
338
|
+
} catch (error) {
|
|
339
|
+
return this.handleError(error);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async deleteActivity(input: ToolInput): Promise<ToolOutput> {
|
|
344
|
+
try {
|
|
345
|
+
const id = input.id as string | undefined;
|
|
346
|
+
if (!id) {
|
|
347
|
+
return { success: false, error: 'Activity ID is required' };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const deleted = await this.storage.delete('activities', id);
|
|
351
|
+
return { success: true, data: { id, deleted } };
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return this.handleError(error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ============================================================================
|
|
358
|
+
// Media Handlers
|
|
359
|
+
// ============================================================================
|
|
360
|
+
|
|
361
|
+
async uploadMedia(input: ToolInput): Promise<ToolOutput> {
|
|
362
|
+
try {
|
|
363
|
+
const entityType = input.entityType as Media['entityType'];
|
|
364
|
+
const entityId = input.entityId as string;
|
|
365
|
+
const type = input.type as Media['type'];
|
|
366
|
+
const filename = input.filename as string;
|
|
367
|
+
const mimeType = input.mimeType as string;
|
|
368
|
+
const size = input.size as number;
|
|
369
|
+
const url = input.url as string;
|
|
370
|
+
|
|
371
|
+
if (!entityType || !entityId || !type || !filename || !mimeType || size === undefined || !url) {
|
|
372
|
+
return { success: false, error: 'Missing required media fields' };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const media = await this.storage.insert<Media>('media', {
|
|
376
|
+
entityType,
|
|
377
|
+
entityId,
|
|
378
|
+
type,
|
|
379
|
+
filename,
|
|
380
|
+
mimeType,
|
|
381
|
+
size,
|
|
382
|
+
url,
|
|
383
|
+
thumbnailUrl: input.thumbnailUrl as string | undefined,
|
|
384
|
+
metadata: (input.metadata as Media['metadata']) ?? {},
|
|
385
|
+
altText: input.altText as string | undefined,
|
|
386
|
+
caption: input.caption as string | undefined,
|
|
387
|
+
isPublic: (input.isPublic as boolean) ?? false,
|
|
388
|
+
downloadCount: 0,
|
|
389
|
+
uploadedBy: undefined,
|
|
390
|
+
expiresAt: undefined,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return { success: true, data: media };
|
|
394
|
+
} catch (error) {
|
|
395
|
+
return this.handleError(error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async getMedia(input: ToolInput): Promise<ToolOutput> {
|
|
400
|
+
try {
|
|
401
|
+
const id = input.id as string | undefined;
|
|
402
|
+
if (!id) {
|
|
403
|
+
return { success: false, error: 'Media ID is required' };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const media = this.storage.get<Media>('media', id);
|
|
407
|
+
if (!media) {
|
|
408
|
+
return { success: false, error: `Media not found: ${id}` };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { success: true, data: media };
|
|
412
|
+
} catch (error) {
|
|
413
|
+
return this.handleError(error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async listMedia(input: ToolInput): Promise<ToolOutput> {
|
|
418
|
+
try {
|
|
419
|
+
const entityId = input.entityId as string;
|
|
420
|
+
if (!entityId) {
|
|
421
|
+
return { success: false, error: 'Entity ID is required' };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const media = this.storage.findByIndex<Media>('media_by_entity', entityId);
|
|
425
|
+
return { success: true, data: { media } };
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return this.handleError(error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async deleteMedia(input: ToolInput): Promise<ToolOutput> {
|
|
432
|
+
try {
|
|
433
|
+
const id = input.id as string | undefined;
|
|
434
|
+
if (!id) {
|
|
435
|
+
return { success: false, error: 'Media ID is required' };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const deleted = await this.storage.delete('media', id);
|
|
439
|
+
return { success: true, data: { id, deleted } };
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return this.handleError(error);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ============================================================================
|
|
446
|
+
// Note Handlers
|
|
447
|
+
// ============================================================================
|
|
448
|
+
|
|
449
|
+
async createNote(input: ToolInput): Promise<ToolOutput> {
|
|
450
|
+
try {
|
|
451
|
+
const content = input.content as string;
|
|
452
|
+
if (!content) {
|
|
453
|
+
return { success: false, error: 'Note content is required' };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const note = await this.storage.insert<Note>('notes', {
|
|
457
|
+
contactId: input.contactId as string | undefined,
|
|
458
|
+
dealId: input.dealId as string | undefined,
|
|
459
|
+
activityId: undefined,
|
|
460
|
+
content,
|
|
461
|
+
format: (input.format as Note['format']) ?? 'markdown',
|
|
462
|
+
title: input.title as string | undefined,
|
|
463
|
+
visibility: (input.visibility as Note['visibility']) ?? 'team',
|
|
464
|
+
createdBy: undefined,
|
|
465
|
+
pinned: (input.pinned as boolean) ?? false,
|
|
466
|
+
tags: (input.tags as string[]) ?? [],
|
|
467
|
+
mediaIds: [],
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return { success: true, data: note };
|
|
471
|
+
} catch (error) {
|
|
472
|
+
return this.handleError(error);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async getNote(input: ToolInput): Promise<ToolOutput> {
|
|
477
|
+
try {
|
|
478
|
+
const id = input.id as string | undefined;
|
|
479
|
+
if (!id) {
|
|
480
|
+
return { success: false, error: 'Note ID is required' };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const note = this.storage.get<Note>('notes', id);
|
|
484
|
+
if (!note) {
|
|
485
|
+
return { success: false, error: `Note not found: ${id}` };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return { success: true, data: note };
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return this.handleError(error);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async updateNote(input: ToolInput): Promise<ToolOutput> {
|
|
495
|
+
try {
|
|
496
|
+
const id = input.id as string | undefined;
|
|
497
|
+
if (!id) {
|
|
498
|
+
return { success: false, error: 'Note ID is required' };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const updates: Partial<Note> = {};
|
|
502
|
+
if (input.content !== undefined) updates.content = input.content as string;
|
|
503
|
+
if (input.title !== undefined) updates.title = input.title as string;
|
|
504
|
+
if (input.visibility !== undefined) updates.visibility = input.visibility as Note['visibility'];
|
|
505
|
+
if (input.pinned !== undefined) updates.pinned = input.pinned as boolean;
|
|
506
|
+
if (input.tags !== undefined) updates.tags = input.tags as string[];
|
|
507
|
+
|
|
508
|
+
const note = await this.storage.update<Note>('notes', id, updates);
|
|
509
|
+
return { success: true, data: note };
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return this.handleError(error);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async deleteNote(input: ToolInput): Promise<ToolOutput> {
|
|
516
|
+
try {
|
|
517
|
+
const id = input.id as string | undefined;
|
|
518
|
+
if (!id) {
|
|
519
|
+
return { success: false, error: 'Note ID is required' };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const deleted = await this.storage.delete('notes', id);
|
|
523
|
+
return { success: true, data: { id, deleted } };
|
|
524
|
+
} catch (error) {
|
|
525
|
+
return this.handleError(error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async listNotes(input: ToolInput): Promise<ToolOutput> {
|
|
530
|
+
try {
|
|
531
|
+
const limit = (input.limit as number) ?? 20;
|
|
532
|
+
const offset = (input.offset as number) ?? 0;
|
|
533
|
+
|
|
534
|
+
const notes = this.storage.list<Note>('notes', { limit, offset });
|
|
535
|
+
const total = this.storage.count('notes');
|
|
536
|
+
|
|
537
|
+
return { success: true, data: { notes, total } };
|
|
538
|
+
} catch (error) {
|
|
539
|
+
return this.handleError(error);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ============================================================================
|
|
544
|
+
// Tag Handlers
|
|
545
|
+
// ============================================================================
|
|
546
|
+
|
|
547
|
+
async createTag(input: ToolInput): Promise<ToolOutput> {
|
|
548
|
+
try {
|
|
549
|
+
const name = input.name as string;
|
|
550
|
+
const label = input.label as string;
|
|
551
|
+
const color = input.color as string;
|
|
552
|
+
|
|
553
|
+
if (!name || !label || !color) {
|
|
554
|
+
return { success: false, error: 'Tag name, label, and color are required' };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const tag = await this.storage.insert<Tag>('tags', {
|
|
558
|
+
name,
|
|
559
|
+
label,
|
|
560
|
+
color,
|
|
561
|
+
category: (input.category as Tag['category']) ?? 'general',
|
|
562
|
+
description: input.description as string | undefined,
|
|
563
|
+
icon: input.icon as string | undefined,
|
|
564
|
+
usageCount: 0,
|
|
565
|
+
parentId: input.parentId as string | undefined,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
return { success: true, data: tag };
|
|
569
|
+
} catch (error) {
|
|
570
|
+
return this.handleError(error);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async getTag(input: ToolInput): Promise<ToolOutput> {
|
|
575
|
+
try {
|
|
576
|
+
const id = input.id as string | undefined;
|
|
577
|
+
const name = input.name as string | undefined;
|
|
578
|
+
|
|
579
|
+
if (id) {
|
|
580
|
+
const tag = this.storage.get<Tag>('tags', id);
|
|
581
|
+
if (!tag) {
|
|
582
|
+
return { success: false, error: `Tag not found: ${id}` };
|
|
583
|
+
}
|
|
584
|
+
return { success: true, data: tag };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (name) {
|
|
588
|
+
const tags = this.storage.search<Tag>('tags', 'name', name);
|
|
589
|
+
if (tags.length === 0) {
|
|
590
|
+
return { success: false, error: `Tag not found: ${name}` };
|
|
591
|
+
}
|
|
592
|
+
return { success: true, data: tags[0] };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return { success: false, error: 'Either id or name is required' };
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return this.handleError(error);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async listTags(input: ToolInput): Promise<ToolOutput> {
|
|
602
|
+
try {
|
|
603
|
+
let tags = this.storage.list<Tag>('tags', { limit: 100, offset: 0 });
|
|
604
|
+
|
|
605
|
+
if (input.category && typeof input.category === 'string') {
|
|
606
|
+
tags = tags.filter((t) => t.category === input.category);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return { success: true, data: { tags } };
|
|
610
|
+
} catch (error) {
|
|
611
|
+
return this.handleError(error);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async deleteTag(input: ToolInput): Promise<ToolOutput> {
|
|
616
|
+
try {
|
|
617
|
+
const id = input.id as string | undefined;
|
|
618
|
+
if (!id) {
|
|
619
|
+
return { success: false, error: 'Tag ID is required' };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const deleted = await this.storage.delete('tags', id);
|
|
623
|
+
return { success: true, data: { id, deleted } };
|
|
624
|
+
} catch (error) {
|
|
625
|
+
return this.handleError(error);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ============================================================================
|
|
630
|
+
// Company Handlers
|
|
631
|
+
// ============================================================================
|
|
632
|
+
|
|
633
|
+
async createCompany(input: ToolInput): Promise<ToolOutput> {
|
|
634
|
+
try {
|
|
635
|
+
const name = input.name as string;
|
|
636
|
+
if (!name) {
|
|
637
|
+
return { success: false, error: 'Company name is required' };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const company = await this.storage.insert<Company>('companies', {
|
|
641
|
+
name,
|
|
642
|
+
website: input.website as string | undefined,
|
|
643
|
+
industry: input.industry as Company['industry'],
|
|
644
|
+
size: input.size as Company['size'],
|
|
645
|
+
employeeCount: input.employeeCount as number | undefined,
|
|
646
|
+
annualRevenue: input.annualRevenue as number | undefined,
|
|
647
|
+
currency: undefined,
|
|
648
|
+
address: undefined,
|
|
649
|
+
phone: undefined,
|
|
650
|
+
emailDomain: undefined,
|
|
651
|
+
linkedInUrl: undefined,
|
|
652
|
+
tags: (input.tags as string[]) ?? [],
|
|
653
|
+
customFields: [],
|
|
654
|
+
ownerId: undefined,
|
|
655
|
+
notes: undefined,
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
return { success: true, data: company };
|
|
659
|
+
} catch (error) {
|
|
660
|
+
return this.handleError(error);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async getCompany(input: ToolInput): Promise<ToolOutput> {
|
|
665
|
+
try {
|
|
666
|
+
const id = input.id as string | undefined;
|
|
667
|
+
if (!id) {
|
|
668
|
+
return { success: false, error: 'Company ID is required' };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const company = this.storage.get<Company>('companies', id);
|
|
672
|
+
if (!company) {
|
|
673
|
+
return { success: false, error: `Company not found: ${id}` };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return { success: true, data: company };
|
|
677
|
+
} catch (error) {
|
|
678
|
+
return this.handleError(error);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async updateCompany(input: ToolInput): Promise<ToolOutput> {
|
|
683
|
+
try {
|
|
684
|
+
const id = input.id as string | undefined;
|
|
685
|
+
if (!id) {
|
|
686
|
+
return { success: false, error: 'Company ID is required' };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const updates: Partial<Company> = {};
|
|
690
|
+
if (input.name !== undefined) updates.name = input.name as string;
|
|
691
|
+
if (input.website !== undefined) updates.website = input.website as string;
|
|
692
|
+
if (input.industry !== undefined) updates.industry = input.industry as Company['industry'];
|
|
693
|
+
if (input.size !== undefined) updates.size = input.size as Company['size'];
|
|
694
|
+
if (input.employeeCount !== undefined) updates.employeeCount = input.employeeCount as number;
|
|
695
|
+
if (input.annualRevenue !== undefined) updates.annualRevenue = input.annualRevenue as number;
|
|
696
|
+
if (input.tags !== undefined) updates.tags = input.tags as string[];
|
|
697
|
+
|
|
698
|
+
const company = await this.storage.update<Company>('companies', id, updates);
|
|
699
|
+
return { success: true, data: company };
|
|
700
|
+
} catch (error) {
|
|
701
|
+
return this.handleError(error);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async deleteCompany(input: ToolInput): Promise<ToolOutput> {
|
|
706
|
+
try {
|
|
707
|
+
const id = input.id as string | undefined;
|
|
708
|
+
if (!id) {
|
|
709
|
+
return { success: false, error: 'Company ID is required' };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const deleted = await this.storage.delete('companies', id);
|
|
713
|
+
return { success: true, data: { id, deleted } };
|
|
714
|
+
} catch (error) {
|
|
715
|
+
return this.handleError(error);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async listCompanies(input: ToolInput): Promise<ToolOutput> {
|
|
720
|
+
try {
|
|
721
|
+
const limit = (input.limit as number) ?? 20;
|
|
722
|
+
const offset = (input.offset as number) ?? 0;
|
|
723
|
+
|
|
724
|
+
let companies = this.storage.list<Company>('companies', { limit, offset });
|
|
725
|
+
|
|
726
|
+
if (input.industry && typeof input.industry === 'string') {
|
|
727
|
+
companies = companies.filter((c) => c.industry === input.industry);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const total = this.storage.count('companies');
|
|
731
|
+
|
|
732
|
+
return { success: true, data: { companies, total } };
|
|
733
|
+
} catch (error) {
|
|
734
|
+
return this.handleError(error);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// ============================================================================
|
|
739
|
+
// Stats & Dashboard Handlers
|
|
740
|
+
// ============================================================================
|
|
741
|
+
|
|
742
|
+
async getStats(): Promise<ToolOutput> {
|
|
743
|
+
try {
|
|
744
|
+
const stats = this.storage.getStats();
|
|
745
|
+
return { success: true, data: stats };
|
|
746
|
+
} catch (error) {
|
|
747
|
+
return this.handleError(error);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
async getDashboard(input: ToolInput): Promise<ToolOutput> {
|
|
752
|
+
try {
|
|
753
|
+
const stats = this.storage.getStats();
|
|
754
|
+
|
|
755
|
+
// Get deals by stage
|
|
756
|
+
const stages = ['prospecting', 'qualification', 'needs_analysis', 'proposal', 'negotiation', 'closed_won', 'closed_lost'];
|
|
757
|
+
const dealsByStage: Record<string, number> = {};
|
|
758
|
+
|
|
759
|
+
for (const stage of stages) {
|
|
760
|
+
const deals = this.storage.findByIndex<Deal>('deals_by_stage', stage);
|
|
761
|
+
dealsByStage[stage] = deals.length;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Get recent activities
|
|
765
|
+
const recentActivities = this.storage.list<Activity>('activities', { limit: 10, offset: 0 });
|
|
766
|
+
|
|
767
|
+
return {
|
|
768
|
+
success: true,
|
|
769
|
+
data: {
|
|
770
|
+
stats,
|
|
771
|
+
dealsByStage,
|
|
772
|
+
recentActivities,
|
|
773
|
+
},
|
|
774
|
+
};
|
|
775
|
+
} catch (error) {
|
|
776
|
+
return this.handleError(error);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// ============================================================================
|
|
781
|
+
// Error Handling
|
|
782
|
+
// ============================================================================
|
|
783
|
+
|
|
784
|
+
private handleError(error: unknown): ToolOutput {
|
|
785
|
+
if (error instanceof CRMError) {
|
|
786
|
+
return {
|
|
787
|
+
success: false,
|
|
788
|
+
error: error.message,
|
|
789
|
+
metadata: { code: error.code },
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (error instanceof Error) {
|
|
794
|
+
return {
|
|
795
|
+
success: false,
|
|
796
|
+
error: error.message,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
success: false,
|
|
802
|
+
error: String(error),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
}
|