@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.
Files changed (187) hide show
  1. package/README.md +174 -0
  2. package/dist/cli/commands/activities.d.ts +11 -0
  3. package/dist/cli/commands/activities.d.ts.map +1 -0
  4. package/dist/cli/commands/activities.js +427 -0
  5. package/dist/cli/commands/activities.js.map +1 -0
  6. package/dist/cli/commands/contacts.d.ts +11 -0
  7. package/dist/cli/commands/contacts.d.ts.map +1 -0
  8. package/dist/cli/commands/contacts.js +458 -0
  9. package/dist/cli/commands/contacts.js.map +1 -0
  10. package/dist/cli/commands/deals.d.ts +11 -0
  11. package/dist/cli/commands/deals.d.ts.map +1 -0
  12. package/dist/cli/commands/deals.js +498 -0
  13. package/dist/cli/commands/deals.js.map +1 -0
  14. package/dist/cli/commands/media.d.ts +11 -0
  15. package/dist/cli/commands/media.d.ts.map +1 -0
  16. package/dist/cli/commands/media.js +417 -0
  17. package/dist/cli/commands/media.js.map +1 -0
  18. package/dist/cli/commands/search.d.ts +11 -0
  19. package/dist/cli/commands/search.d.ts.map +1 -0
  20. package/dist/cli/commands/search.js +346 -0
  21. package/dist/cli/commands/search.js.map +1 -0
  22. package/dist/cli/index.d.ts +13 -0
  23. package/dist/cli/index.d.ts.map +1 -0
  24. package/dist/cli/index.js +173 -0
  25. package/dist/cli/index.js.map +1 -0
  26. package/dist/cli/repl.d.ts +15 -0
  27. package/dist/cli/repl.d.ts.map +1 -0
  28. package/dist/cli/repl.js +318 -0
  29. package/dist/cli/repl.js.map +1 -0
  30. package/dist/cli/utils/config.d.ts +91 -0
  31. package/dist/cli/utils/config.d.ts.map +1 -0
  32. package/dist/cli/utils/config.js +212 -0
  33. package/dist/cli/utils/config.js.map +1 -0
  34. package/dist/cli/utils/output.d.ts +136 -0
  35. package/dist/cli/utils/output.d.ts.map +1 -0
  36. package/dist/cli/utils/output.js +323 -0
  37. package/dist/cli/utils/output.js.map +1 -0
  38. package/dist/cli/utils/prompt.d.ts +81 -0
  39. package/dist/cli/utils/prompt.d.ts.map +1 -0
  40. package/dist/cli/utils/prompt.js +341 -0
  41. package/dist/cli/utils/prompt.js.map +1 -0
  42. package/dist/cli.d.ts +3 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +8 -0
  45. package/dist/cli.js.map +1 -0
  46. package/dist/core/index.d.ts +6 -0
  47. package/dist/core/index.d.ts.map +1 -0
  48. package/dist/core/index.js +32 -0
  49. package/dist/core/index.js.map +1 -0
  50. package/dist/core/schemas.d.ts +3050 -0
  51. package/dist/core/schemas.d.ts.map +1 -0
  52. package/dist/core/schemas.js +667 -0
  53. package/dist/core/schemas.js.map +1 -0
  54. package/dist/core/types.d.ts +597 -0
  55. package/dist/core/types.d.ts.map +1 -0
  56. package/dist/core/types.js +8 -0
  57. package/dist/core/types.js.map +1 -0
  58. package/dist/index.d.ts +7 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +8 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/mcp/index.d.ts +14 -0
  63. package/dist/mcp/index.d.ts.map +1 -0
  64. package/dist/mcp/index.js +11 -0
  65. package/dist/mcp/index.js.map +1 -0
  66. package/dist/mcp/server.d.ts +13 -0
  67. package/dist/mcp/server.d.ts.map +1 -0
  68. package/dist/mcp/server.js +18 -0
  69. package/dist/mcp/server.js.map +1 -0
  70. package/dist/mcp/storage/client.d.ts +109 -0
  71. package/dist/mcp/storage/client.d.ts.map +1 -0
  72. package/dist/mcp/storage/client.js +355 -0
  73. package/dist/mcp/storage/client.js.map +1 -0
  74. package/dist/mcp/storage/index.d.ts +7 -0
  75. package/dist/mcp/storage/index.d.ts.map +1 -0
  76. package/dist/mcp/storage/index.js +6 -0
  77. package/dist/mcp/storage/index.js.map +1 -0
  78. package/dist/mcp/storage/types.d.ts +44 -0
  79. package/dist/mcp/storage/types.d.ts.map +1 -0
  80. package/dist/mcp/storage/types.js +35 -0
  81. package/dist/mcp/storage/types.js.map +1 -0
  82. package/dist/mcp/tools/definitions.d.ts +16 -0
  83. package/dist/mcp/tools/definitions.d.ts.map +1 -0
  84. package/dist/mcp/tools/definitions.js +914 -0
  85. package/dist/mcp/tools/definitions.js.map +1 -0
  86. package/dist/mcp/tools/handlers.d.ts +50 -0
  87. package/dist/mcp/tools/handlers.d.ts.map +1 -0
  88. package/dist/mcp/tools/handlers.js +760 -0
  89. package/dist/mcp/tools/handlers.js.map +1 -0
  90. package/dist/mcp/tools/index.d.ts +7 -0
  91. package/dist/mcp/tools/index.d.ts.map +1 -0
  92. package/dist/mcp/tools/index.js +6 -0
  93. package/dist/mcp/tools/index.js.map +1 -0
  94. package/dist/mcp/tools/types.d.ts +314 -0
  95. package/dist/mcp/tools/types.d.ts.map +1 -0
  96. package/dist/mcp/tools/types.js +5 -0
  97. package/dist/mcp/tools/types.js.map +1 -0
  98. package/dist/mcp/transports/stdio.d.ts +27 -0
  99. package/dist/mcp/transports/stdio.d.ts.map +1 -0
  100. package/dist/mcp/transports/stdio.js +237 -0
  101. package/dist/mcp/transports/stdio.js.map +1 -0
  102. package/dist/telemetry/index.d.ts +58 -0
  103. package/dist/telemetry/index.d.ts.map +1 -0
  104. package/dist/telemetry/index.js +109 -0
  105. package/dist/telemetry/index.js.map +1 -0
  106. package/dist/telemetry/logger.d.ts +116 -0
  107. package/dist/telemetry/logger.d.ts.map +1 -0
  108. package/dist/telemetry/logger.js +256 -0
  109. package/dist/telemetry/logger.js.map +1 -0
  110. package/dist/telemetry/metrics.d.ts +115 -0
  111. package/dist/telemetry/metrics.d.ts.map +1 -0
  112. package/dist/telemetry/metrics.js +292 -0
  113. package/dist/telemetry/metrics.js.map +1 -0
  114. package/dist/telemetry/tracer.d.ts +227 -0
  115. package/dist/telemetry/tracer.d.ts.map +1 -0
  116. package/dist/telemetry/tracer.js +355 -0
  117. package/dist/telemetry/tracer.js.map +1 -0
  118. package/dist/web/app.d.ts +2 -0
  119. package/dist/web/app.d.ts.map +1 -0
  120. package/dist/web/app.js +115 -0
  121. package/dist/web/app.js.map +1 -0
  122. package/dist/web/components/ContactList.d.ts +3 -0
  123. package/dist/web/components/ContactList.d.ts.map +1 -0
  124. package/dist/web/components/ContactList.js +262 -0
  125. package/dist/web/components/ContactList.js.map +1 -0
  126. package/dist/web/components/Dashboard.d.ts +3 -0
  127. package/dist/web/components/Dashboard.d.ts.map +1 -0
  128. package/dist/web/components/Dashboard.js +158 -0
  129. package/dist/web/components/Dashboard.js.map +1 -0
  130. package/dist/web/components/DealPipeline.d.ts +3 -0
  131. package/dist/web/components/DealPipeline.d.ts.map +1 -0
  132. package/dist/web/components/DealPipeline.js +306 -0
  133. package/dist/web/components/DealPipeline.js.map +1 -0
  134. package/dist/web/index.d.ts +2 -0
  135. package/dist/web/index.d.ts.map +1 -0
  136. package/dist/web/index.js +269 -0
  137. package/dist/web/index.js.map +1 -0
  138. package/dist/web/types.d.ts +75 -0
  139. package/dist/web/types.d.ts.map +1 -0
  140. package/dist/web/types.js +3 -0
  141. package/dist/web/types.js.map +1 -0
  142. package/native/index.d.ts +571 -0
  143. package/native/index.js +687 -0
  144. package/package.json +105 -0
  145. package/src/cli/commands/activities.ts +543 -0
  146. package/src/cli/commands/contacts.ts +563 -0
  147. package/src/cli/commands/deals.ts +637 -0
  148. package/src/cli/commands/media.ts +521 -0
  149. package/src/cli/commands/search.ts +426 -0
  150. package/src/cli/index.ts +203 -0
  151. package/src/cli/repl.ts +379 -0
  152. package/src/cli/utils/config.ts +299 -0
  153. package/src/cli/utils/output.ts +386 -0
  154. package/src/cli/utils/prompt.ts +444 -0
  155. package/src/cli.ts +11 -0
  156. package/src/core/index.ts +184 -0
  157. package/src/core/schemas.ts +770 -0
  158. package/src/core/types.ts +969 -0
  159. package/src/index.ts +8 -0
  160. package/src/mcp/index.ts +17 -0
  161. package/src/mcp/server.ts +26 -0
  162. package/src/mcp/storage/client.ts +408 -0
  163. package/src/mcp/storage/index.ts +7 -0
  164. package/src/mcp/storage/types.ts +72 -0
  165. package/src/mcp/tools/definitions.ts +961 -0
  166. package/src/mcp/tools/handlers.ts +805 -0
  167. package/src/mcp/tools/index.ts +7 -0
  168. package/src/mcp/tools/types.ts +390 -0
  169. package/src/mcp/transports/stdio.ts +225 -0
  170. package/src/telemetry/index.ts +131 -0
  171. package/src/telemetry/logger.ts +318 -0
  172. package/src/telemetry/metrics.ts +393 -0
  173. package/src/telemetry/tracer.ts +487 -0
  174. package/src/web/api/activities.ts +41 -0
  175. package/src/web/api/contacts.ts +114 -0
  176. package/src/web/api/deals.ts +108 -0
  177. package/src/web/api/media.ts +98 -0
  178. package/src/web/app.tsx +143 -0
  179. package/src/web/components/ActivityFeed.tsx +195 -0
  180. package/src/web/components/ContactList.tsx +340 -0
  181. package/src/web/components/Dashboard.tsx +214 -0
  182. package/src/web/components/DealPipeline.tsx +405 -0
  183. package/src/web/components/MediaGallery.tsx +334 -0
  184. package/src/web/index.html +14 -0
  185. package/src/web/index.ts +326 -0
  186. package/src/web/styles/main.css +180 -0
  187. package/src/web/types.ts +311 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * Tracer module for CRM system
3
+ *
4
+ * Provides distributed tracing support with spans and trace context.
5
+ * Can export traces in OpenTelemetry-compatible format.
6
+ */
7
+
8
+ import { randomUUID } from 'crypto';
9
+
10
+ export interface TraceContext {
11
+ traceId: string;
12
+ spanId: string;
13
+ parentSpanId?: string;
14
+ sampled: boolean;
15
+ }
16
+
17
+ export interface SpanAttribute {
18
+ key: string;
19
+ value: string | number | boolean | string[] | number[];
20
+ }
21
+
22
+ export interface SpanEvent {
23
+ timestamp: number;
24
+ name: string;
25
+ attributes?: SpanAttribute[];
26
+ }
27
+
28
+ export interface SpanLink {
29
+ traceId: string;
30
+ spanId: string;
31
+ attributes?: SpanAttribute[];
32
+ }
33
+
34
+ export interface SpanStatus {
35
+ code: 'ok' | 'error' | 'unset';
36
+ message?: string;
37
+ }
38
+
39
+ export interface Span {
40
+ traceId: string;
41
+ spanId: string;
42
+ parentSpanId?: string;
43
+ name: string;
44
+ kind: 'internal' | 'server' | 'client' | 'producer' | 'consumer';
45
+ startTime: number;
46
+ endTime?: number;
47
+ duration?: number;
48
+ attributes: SpanAttribute[];
49
+ events: SpanEvent[];
50
+ links: SpanLink[];
51
+ status: SpanStatus;
52
+ }
53
+
54
+ export interface TracerConfig {
55
+ serviceName: string;
56
+ serviceVersion: string;
57
+ samplingRate: number; // 0.0 to 1.0
58
+ maxEventsPerSpan: number;
59
+ maxAttributesPerSpan: number;
60
+ }
61
+
62
+ /**
63
+ * Tracer for creating and managing spans
64
+ */
65
+ export class Tracer {
66
+ private readonly config: TracerConfig;
67
+ private readonly spans: Span[] = [];
68
+ private activeSpans: Map<string, Span> = new Map();
69
+
70
+ constructor(config: Partial<TracerConfig> & { serviceName: string }) {
71
+ this.config = {
72
+ serviceVersion: '0.1.0',
73
+ samplingRate: 1.0,
74
+ maxEventsPerSpan: 128,
75
+ maxAttributesPerSpan: 128,
76
+ ...config,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Start a new span
82
+ */
83
+ startSpan(
84
+ name: string,
85
+ options?: {
86
+ kind?: Span['kind'];
87
+ parent?: TraceContext | Span;
88
+ attributes?: SpanAttribute[];
89
+ links?: SpanLink[];
90
+ }
91
+ ): SpanHandle {
92
+ const traceId = options?.parent
93
+ ? ('traceId' in options.parent ? options.parent.traceId : options.parent.traceId)
94
+ : this.generateTraceId();
95
+
96
+ const spanId = this.generateSpanId();
97
+ const parentSpanId = options?.parent
98
+ ? ('spanId' in options.parent ? options.parent.spanId : options.parent.spanId)
99
+ : undefined;
100
+
101
+ const sampled = Math.random() < this.config.samplingRate;
102
+
103
+ const span: Span = {
104
+ traceId,
105
+ spanId,
106
+ parentSpanId,
107
+ name,
108
+ kind: options?.kind ?? 'internal',
109
+ startTime: Date.now(),
110
+ attributes: options?.attributes ?? [],
111
+ events: [],
112
+ links: options?.links ?? [],
113
+ status: { code: 'unset' },
114
+ };
115
+
116
+ this.activeSpans.set(spanId, span);
117
+
118
+ return new SpanHandle(span, this, sampled);
119
+ }
120
+
121
+ /**
122
+ * Start a span from HTTP headers
123
+ */
124
+ startSpanFromHeaders(
125
+ name: string,
126
+ headers: Record<string, string>,
127
+ options?: {
128
+ kind?: Span['kind'];
129
+ attributes?: SpanAttribute[];
130
+ }
131
+ ): SpanHandle {
132
+ const traceparent = headers['traceparent'] || headers['Traceparent'];
133
+ let parentContext: TraceContext | undefined;
134
+
135
+ if (traceparent) {
136
+ const parsed = this.parseTraceParent(traceparent);
137
+ if (parsed) {
138
+ parentContext = parsed;
139
+ }
140
+ }
141
+
142
+ return this.startSpan(name, {
143
+ ...options,
144
+ parent: parentContext,
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Create a child span from a parent span
150
+ */
151
+ startChildSpan(parent: Span | SpanHandle, name: string, options?: {
152
+ kind?: Span['kind'];
153
+ attributes?: SpanAttribute[];
154
+ }): SpanHandle {
155
+ const parentSpan = 'span' in parent ? parent.span : parent;
156
+ return this.startSpan(name, {
157
+ ...options,
158
+ parent: parentSpan,
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Get trace context from a span
164
+ */
165
+ getTraceContext(span: Span | SpanHandle): TraceContext {
166
+ const s = 'span' in span ? span.span : span;
167
+ return {
168
+ traceId: s.traceId,
169
+ spanId: s.spanId,
170
+ parentSpanId: s.parentSpanId,
171
+ sampled: true,
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Get all completed spans
177
+ */
178
+ getSpans(): Span[] {
179
+ return [...this.spans];
180
+ }
181
+
182
+ /**
183
+ * Get spans by trace ID
184
+ */
185
+ getSpansByTraceId(traceId: string): Span[] {
186
+ return this.spans.filter(s => s.traceId === traceId);
187
+ }
188
+
189
+ /**
190
+ * Export spans in OpenTelemetry format
191
+ */
192
+ exportOpenTelemetry(): {
193
+ resourceSpans: {
194
+ resource: {
195
+ attributes: { key: string; value: { stringValue: string } }[];
196
+ };
197
+ scopeSpans: {
198
+ scope: { name: string; version: string };
199
+ spans: {
200
+ traceId: string;
201
+ spanId: string;
202
+ parentSpanId?: string;
203
+ name: string;
204
+ kind: number;
205
+ startTimeUnixNano: string;
206
+ endTimeUnixNano: string;
207
+ attributes: { key: string; value: { stringValue?: string; intValue?: string } }[];
208
+ status: { code: number; message?: string };
209
+ }[];
210
+ }[];
211
+ }[];
212
+ } {
213
+ const spans = this.spans.map(span => ({
214
+ traceId: span.traceId,
215
+ spanId: span.spanId,
216
+ parentSpanId: span.parentSpanId,
217
+ name: span.name,
218
+ kind: this.spanKindToNumber(span.kind),
219
+ startTimeUnixNano: (span.startTime * 1e6).toString(),
220
+ endTimeUnixNano: span.endTime ? (span.endTime * 1e6).toString() : '0',
221
+ attributes: span.attributes.map(attr => ({
222
+ key: attr.key,
223
+ value: typeof attr.value === 'string'
224
+ ? { stringValue: attr.value }
225
+ : { intValue: String(attr.value) },
226
+ })),
227
+ status: {
228
+ code: span.status.code === 'ok' ? 1 : span.status.code === 'error' ? 2 : 0,
229
+ message: span.status.message,
230
+ },
231
+ }));
232
+
233
+ return {
234
+ resourceSpans: [{
235
+ resource: {
236
+ attributes: [
237
+ { key: 'service.name', value: { stringValue: this.config.serviceName } },
238
+ { key: 'service.version', value: { stringValue: this.config.serviceVersion } },
239
+ ],
240
+ },
241
+ scopeSpans: [{
242
+ scope: {
243
+ name: this.config.serviceName,
244
+ version: this.config.serviceVersion,
245
+ },
246
+ spans,
247
+ }],
248
+ }],
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Clear all spans
254
+ */
255
+ clear(): void {
256
+ this.spans.length = 0;
257
+ this.activeSpans.clear();
258
+ }
259
+
260
+ /**
261
+ * Internal: End a span
262
+ */
263
+ endSpan(span: Span): void {
264
+ span.endTime = Date.now();
265
+ span.duration = span.endTime - span.startTime;
266
+ this.activeSpans.delete(span.spanId);
267
+ this.spans.push(span);
268
+ }
269
+
270
+ /**
271
+ * Internal: Add event to span
272
+ */
273
+ addEvent(span: Span, name: string, attributes?: SpanAttribute[]): void {
274
+ if (span.events.length < this.config.maxEventsPerSpan) {
275
+ span.events.push({
276
+ timestamp: Date.now(),
277
+ name,
278
+ attributes,
279
+ });
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Internal: Set attribute on span
285
+ */
286
+ setAttribute(span: Span, key: string, value: SpanAttribute['value']): void {
287
+ if (span.attributes.length < this.config.maxAttributesPerSpan) {
288
+ const existing = span.attributes.findIndex(a => a.key === key);
289
+ if (existing >= 0) {
290
+ span.attributes[existing].value = value;
291
+ } else {
292
+ span.attributes.push({ key, value });
293
+ }
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Internal: Set span status
299
+ */
300
+ setStatus(span: Span, code: SpanStatus['code'], message?: string): void {
301
+ span.status = { code, message };
302
+ }
303
+
304
+ private generateTraceId(): string {
305
+ return randomUUID().replace(/-/g, '');
306
+ }
307
+
308
+ private generateSpanId(): string {
309
+ return randomUUID().replace(/-/g, '').slice(0, 16);
310
+ }
311
+
312
+ private parseTraceParent(header: string): TraceContext | null {
313
+ // Parse W3C Trace Context format: version-traceid-spanid-flags
314
+ const match = header.match(/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i);
315
+ if (!match) return null;
316
+
317
+ return {
318
+ traceId: match[2],
319
+ spanId: match[3],
320
+ sampled: match[4] === '01',
321
+ };
322
+ }
323
+
324
+ private spanKindToNumber(kind: Span['kind']): number {
325
+ switch (kind) {
326
+ case 'internal': return 1;
327
+ case 'server': return 2;
328
+ case 'client': return 3;
329
+ case 'producer': return 4;
330
+ case 'consumer': return 5;
331
+ default: return 1;
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Handle to an active span
338
+ */
339
+ export class SpanHandle {
340
+ private ended = false;
341
+
342
+ constructor(
343
+ public readonly span: Span,
344
+ private readonly tracer: Tracer,
345
+ public readonly sampled: boolean
346
+ ) {}
347
+
348
+ /**
349
+ * Set an attribute on the span
350
+ */
351
+ setAttribute(key: string, value: SpanAttribute['value']): this {
352
+ if (!this.ended) {
353
+ this.tracer.setAttribute(this.span, key, value);
354
+ }
355
+ return this;
356
+ }
357
+
358
+ /**
359
+ * Set multiple attributes
360
+ */
361
+ setAttributes(attributes: Record<string, SpanAttribute['value']>): this {
362
+ for (const [key, value] of Object.entries(attributes)) {
363
+ this.setAttribute(key, value);
364
+ }
365
+ return this;
366
+ }
367
+
368
+ /**
369
+ * Add an event to the span
370
+ */
371
+ addEvent(name: string, attributes?: Record<string, SpanAttribute['value']>): this {
372
+ if (!this.ended) {
373
+ const attrs = attributes
374
+ ? Object.entries(attributes).map(([key, value]) => ({ key, value }))
375
+ : undefined;
376
+ this.tracer.addEvent(this.span, name, attrs);
377
+ }
378
+ return this;
379
+ }
380
+
381
+ /**
382
+ * Set span status to OK
383
+ */
384
+ ok(): this {
385
+ if (!this.ended) {
386
+ this.tracer.setStatus(this.span, 'ok');
387
+ }
388
+ return this;
389
+ }
390
+
391
+ /**
392
+ * Set span status to error
393
+ */
394
+ error(message?: string): this {
395
+ if (!this.ended) {
396
+ this.tracer.setStatus(this.span, 'error', message);
397
+ }
398
+ return this;
399
+ }
400
+
401
+ /**
402
+ * Record an exception
403
+ */
404
+ recordException(error: Error): this {
405
+ if (!this.ended) {
406
+ this.addEvent('exception', {
407
+ 'exception.type': error.name,
408
+ 'exception.message': error.message,
409
+ 'exception.stacktrace': error.stack ?? '',
410
+ });
411
+ this.error(error.message);
412
+ }
413
+ return this;
414
+ }
415
+
416
+ /**
417
+ * End the span
418
+ */
419
+ end(): void {
420
+ if (!this.ended) {
421
+ this.tracer.endSpan(this.span);
422
+ this.ended = true;
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Create traceparent header value for propagation
428
+ */
429
+ toTraceParent(): string {
430
+ const flags = this.sampled ? '01' : '00';
431
+ return `00-${this.span.traceId}-${this.span.spanId}-${flags}`;
432
+ }
433
+
434
+ /**
435
+ * Get trace context for propagation
436
+ */
437
+ getContext(): TraceContext {
438
+ return {
439
+ traceId: this.span.traceId,
440
+ spanId: this.span.spanId,
441
+ parentSpanId: this.span.parentSpanId,
442
+ sampled: this.sampled,
443
+ };
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Default tracer instance
449
+ */
450
+ export const tracer = new Tracer({
451
+ serviceName: 'crm',
452
+ serviceVersion: '0.1.0',
453
+ });
454
+
455
+ /**
456
+ * Create a new tracer instance
457
+ */
458
+ export function createTracer(config: Partial<TracerConfig> & { serviceName: string }): Tracer {
459
+ return new Tracer(config);
460
+ }
461
+
462
+ /**
463
+ * Helper: Run a function within a span
464
+ */
465
+ export async function withSpan<T>(
466
+ name: string,
467
+ fn: (span: SpanHandle) => Promise<T>,
468
+ options?: {
469
+ kind?: Span['kind'];
470
+ parent?: Span | SpanHandle;
471
+ attributes?: SpanAttribute[];
472
+ }
473
+ ): Promise<T> {
474
+ const span = tracer.startSpan(name, options);
475
+ try {
476
+ const result = await fn(span);
477
+ span.ok();
478
+ return result;
479
+ } catch (error) {
480
+ if (error instanceof Error) {
481
+ span.recordException(error);
482
+ }
483
+ throw error;
484
+ } finally {
485
+ span.end();
486
+ }
487
+ }
@@ -0,0 +1,41 @@
1
+ import type { Activity } from '../types';
2
+ import { generateId } from './contacts';
3
+ import { contactsStore } from './contacts';
4
+ import { dealsStore } from './deals';
5
+
6
+ // In-memory storage (would be replaced with database in production)
7
+ export const activitiesStore: Activity[] = [];
8
+
9
+ // List activities
10
+ export async function listActivities(request: Request): Promise<Response> {
11
+ const url = new URL(request.url);
12
+ const limit = parseInt(url.searchParams.get('limit') || '100');
13
+ const type = url.searchParams.get('type') || '';
14
+
15
+ let results = activitiesStore.slice(0, limit);
16
+
17
+ if (type) {
18
+ results = results.filter(a => a.type === type);
19
+ }
20
+
21
+ return Response.json({ success: true, data: results });
22
+ }
23
+
24
+ // Create activity
25
+ export async function createActivity(data: Omit<Activity['id']>): Promise<Activity> {
26
+ const id = generateId();
27
+ const activity: Activity = {
28
+ id,
29
+ type: data.type || 'note',
30
+ description: data.description,
31
+ contactId: data.contactId,
32
+ dealId: data.dealId,
33
+ userId: data.userId || 'system',
34
+ timestamp: new Date().toISOString(),
35
+ metadata: data.metadata,
36
+ };
37
+
38
+ activitiesStore.unshift(activity);
39
+
40
+ return activity;
41
+ }
@@ -0,0 +1,114 @@
1
+ import type { Contact } from '../types';
2
+
3
+ // In-memory storage (would be replaced with database in production)
4
+ export const contactsStore = new Map<string, Contact>();
5
+
6
+ // Helper to generate IDs
7
+ export function generateId(): string {
8
+ return crypto.randomUUID();
9
+ }
10
+
11
+ // List contacts with optional filters
12
+ export async function listContacts(request: Request): Promise<Response> {
13
+ const url = new URL(request.url);
14
+ const search = url.searchParams.get('search') || '';
15
+ const status = url.searchParams.get('status') || '';
16
+
17
+ let results = Array.from(contactsStore.values());
18
+
19
+ if (search) {
20
+ const searchLower = search.toLowerCase();
21
+ results = results.filter(c =>
22
+ c.name.toLowerCase().includes(searchLower) ||
23
+ c.email.toLowerCase().includes(searchLower) ||
24
+ c.company?.toLowerCase().includes(searchLower)
25
+ );
26
+ }
27
+
28
+ if (status) {
29
+ results = results.filter(c => c.status === status);
30
+ }
31
+
32
+ return Response.json({ success: true, data: results });
33
+ }
34
+
35
+ // Create a new contact
36
+ export async function createContact(request: Request): Promise<Response> {
37
+ try {
38
+ const body = await request.json();
39
+ const id = generateId();
40
+ const now = new Date().toISOString();
41
+
42
+ const contact: Contact = {
43
+ id,
44
+ name: body.name,
45
+ email: body.email,
46
+ phone: body.phone,
47
+ company: body.company,
48
+ title: body.title,
49
+ status: body.status || 'lead',
50
+ tags: body.tags || [],
51
+ notes: body.notes,
52
+ createdAt: now,
53
+ updatedAt: now,
54
+ };
55
+
56
+ contactsStore.set(id, contact);
57
+
58
+ return Response.json({ success: true, data: contact });
59
+ } catch (error) {
60
+ return Response.json(
61
+ { success: false, error: 'Invalid request body' },
62
+ { status: 400 }
63
+ );
64
+ }
65
+ }
66
+
67
+ // Get a single contact
68
+ export async function getContact(request: Request, params: Record<string, string>): Promise<Response> {
69
+ const contact = contactsStore.get(params.id);
70
+ if (!contact) {
71
+ return Response.json({ success: false, error: 'Contact not found' }, { status: 404 });
72
+ }
73
+ return Response.json({ success: true, data: contact });
74
+ }
75
+
76
+ // Update a contact
77
+ export async function updateContact(request: Request, params: Record<string, string>): Promise<Response> {
78
+ const contact = contactsStore.get(params.id);
79
+ if (!contact) {
80
+ return Response.json({ success: false, error: 'Contact not found' }, { status: 404 });
81
+ }
82
+
83
+ try {
84
+ const body = await request.json();
85
+ const updated: Contact = {
86
+ ...contact,
87
+ ...body,
88
+ id: contact.id,
89
+ createdAt: contact.createdAt,
90
+ updatedAt: new Date().toISOString(),
91
+ };
92
+
93
+ contactsStore.set(params.id, updated);
94
+
95
+ return Response.json({ success: true, data: updated });
96
+ } catch (error) {
97
+ return Response.json(
98
+ { success: false, error: 'Invalid request body' },
99
+ { status: 400 }
100
+ );
101
+ }
102
+ }
103
+
104
+ // Delete a contact
105
+ export async function deleteContact(request: Request, params: Record<string, string>): Promise<Response> {
106
+ const contact = contactsStore.get(params.id);
107
+ if (!contact) {
108
+ return Response.json({ success: false, error: 'Contact not found' }, { status: 404 });
109
+ }
110
+
111
+ contactsStore.delete(params.id);
112
+
113
+ return Response.json({ success: true });
114
+ }