@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,426 @@
1
+ /**
2
+ * Search Commands
3
+ *
4
+ * CLI commands for searching across CRM entities.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import {
9
+ printSuccess,
10
+ printError,
11
+ printWarning,
12
+ printTable,
13
+ printJSON,
14
+ printHeader,
15
+ printDivider,
16
+ formatStatus,
17
+ formatCurrency,
18
+ formatRelativeTime,
19
+ truncate,
20
+ output,
21
+ } from '../utils/output.js';
22
+ import { Spinner } from '../utils/prompt.js';
23
+ import { CRMStorageClient } from '../../mcp/storage/client.js';
24
+ import type { Contact, Deal, Activity } from '../../core/types.js';
25
+
26
+ /**
27
+ * Search result type
28
+ */
29
+ interface SearchResult {
30
+ type: 'contact' | 'deal' | 'activity';
31
+ id: string;
32
+ title: string;
33
+ subtitle: string;
34
+ matchField?: string;
35
+ matchValue?: string;
36
+ }
37
+
38
+ // Storage client singleton
39
+ let _storage: CRMStorageClient | null = null;
40
+
41
+ /**
42
+ * Get storage client (lazy initialization)
43
+ */
44
+ async function getStorage(): Promise<CRMStorageClient> {
45
+ if (!_storage) {
46
+ _storage = new CRMStorageClient({
47
+ path: process.env.CRM_DB_PATH || './data/crm-cli',
48
+ mapSize: 256 * 1024 * 1024, // 256MB
49
+ maxDbs: 20,
50
+ });
51
+ await _storage.initialize();
52
+ }
53
+ return _storage;
54
+ }
55
+
56
+ /**
57
+ * Get all contacts
58
+ */
59
+ async function getContacts(): Promise<Contact[]> {
60
+ const storage = await getStorage();
61
+ return storage.list('contacts');
62
+ }
63
+
64
+ /**
65
+ * Get all deals
66
+ */
67
+ async function getDeals(): Promise<Deal[]> {
68
+ const storage = await getStorage();
69
+ return storage.list('deals');
70
+ }
71
+
72
+ /**
73
+ * Get all activities
74
+ */
75
+ async function getActivities(): Promise<Activity[]> {
76
+ const storage = await getStorage();
77
+ return storage.list('activities');
78
+ }
79
+
80
+ /**
81
+ * Register search commands
82
+ */
83
+ export function registerSearchCommands(program: Command): void {
84
+ const search = program.command('search').description('Search across all entities');
85
+
86
+ // Global search
87
+ search
88
+ .command('<query>')
89
+ .description('Search contacts, deals, and activities')
90
+ .option('-t, --type <type>', 'Filter by entity type (contact, deal, activity)')
91
+ .option('--json', 'Output as JSON')
92
+ .option('--limit <number>', 'Limit results per type', '10')
93
+ .action(async (query, options) => {
94
+ if (!query || query.length < 2) {
95
+ printError('Search query must be at least 2 characters');
96
+ process.exit(1);
97
+ }
98
+
99
+ const spinner = new Spinner(`Searching for "${query}"...`).start();
100
+ await new Promise((r) => setTimeout(r, 300));
101
+
102
+ const results: SearchResult[] = [];
103
+ const q = query.toLowerCase();
104
+ const limit = parseInt(options.limit);
105
+
106
+ // Search contacts
107
+ if (!options.type || options.type === 'contact') {
108
+ const contacts = await getContacts();
109
+ for (const contact of contacts) {
110
+ const matches: { field: string; value: string }[] = [];
111
+
112
+ // Check name
113
+ if (contact.name.toLowerCase().includes(q)) {
114
+ matches.push({ field: 'name', value: contact.name });
115
+ }
116
+
117
+ // Check emails
118
+ for (const email of contact.emails) {
119
+ if (email.email.toLowerCase().includes(q)) {
120
+ matches.push({ field: 'email', value: email.email });
121
+ }
122
+ }
123
+
124
+ // Check company
125
+ if (contact.company?.toLowerCase().includes(q)) {
126
+ matches.push({ field: 'company', value: contact.company });
127
+ }
128
+
129
+ // Check tags
130
+ for (const tag of contact.tags) {
131
+ if (tag.toLowerCase().includes(q)) {
132
+ matches.push({ field: 'tag', value: tag });
133
+ }
134
+ }
135
+
136
+ if (matches.length > 0) {
137
+ results.push({
138
+ type: 'contact',
139
+ id: contact.id,
140
+ title: contact.name,
141
+ subtitle: contact.company || contact.emails[0]?.email || 'No details',
142
+ matchField: matches[0].field,
143
+ matchValue: matches[0].value,
144
+ });
145
+ }
146
+
147
+ if (results.filter((r) => r.type === 'contact').length >= limit) break;
148
+ }
149
+ }
150
+
151
+ // Search deals
152
+ if (!options.type || options.type === 'deal') {
153
+ const deals = await getDeals();
154
+ for (const deal of deals) {
155
+ const matches: { field: string; value: string }[] = [];
156
+
157
+ // Check title
158
+ if (deal.title.toLowerCase().includes(q)) {
159
+ matches.push({ field: 'title', value: deal.title });
160
+ }
161
+
162
+ // Check notes
163
+ if (deal.notes?.toLowerCase().includes(q)) {
164
+ matches.push({ field: 'notes', value: truncate(deal.notes, 50) });
165
+ }
166
+
167
+ // Check tags
168
+ for (const tag of deal.tags) {
169
+ if (tag.toLowerCase().includes(q)) {
170
+ matches.push({ field: 'tag', value: tag });
171
+ }
172
+ }
173
+
174
+ if (matches.length > 0) {
175
+ results.push({
176
+ type: 'deal',
177
+ id: deal.id,
178
+ title: deal.title,
179
+ subtitle: `${formatCurrency(deal.value, deal.currency)} - ${deal.stage}`,
180
+ matchField: matches[0].field,
181
+ matchValue: matches[0].value,
182
+ });
183
+ }
184
+
185
+ if (results.filter((r) => r.type === 'deal').length >= limit) break;
186
+ }
187
+ }
188
+
189
+ // Search activities
190
+ if (!options.type || options.type === 'activity') {
191
+ const activities = await getActivities();
192
+ for (const activity of activities) {
193
+ const matches: { field: string; value: string }[] = [];
194
+
195
+ // Check title
196
+ if (activity.title.toLowerCase().includes(q)) {
197
+ matches.push({ field: 'title', value: activity.title });
198
+ }
199
+
200
+ // Check description
201
+ if (activity.description?.toLowerCase().includes(q)) {
202
+ matches.push({ field: 'description', value: truncate(activity.description, 50) });
203
+ }
204
+
205
+ if (matches.length > 0) {
206
+ results.push({
207
+ type: 'activity',
208
+ id: activity.id,
209
+ title: activity.title,
210
+ subtitle: `${activity.type} - ${formatRelativeTime(activity.timestamp)}`,
211
+ matchField: matches[0].field,
212
+ matchValue: matches[0].value,
213
+ });
214
+ }
215
+
216
+ if (results.filter((r) => r.type === 'activity').length >= limit) break;
217
+ }
218
+ }
219
+
220
+ spinner.succeed(`Found ${results.length} results`);
221
+
222
+ if (options.json) {
223
+ printJSON(results);
224
+ return;
225
+ }
226
+
227
+ if (results.length === 0) {
228
+ printWarning(`No results found for "${query}"`);
229
+ return;
230
+ }
231
+
232
+ // Group by type
233
+ const byType = results.reduce(
234
+ (acc, r) => {
235
+ if (!acc[r.type]) acc[r.type] = [];
236
+ acc[r.type].push(r);
237
+ return acc;
238
+ },
239
+ {} as Record<string, SearchResult[]>
240
+ );
241
+
242
+ // Display contacts
243
+ if (byType.contact?.length) {
244
+ console.log();
245
+ console.log(output.bold(`\u{1F464} Contacts (${byType.contact.length})`));
246
+ printDivider('-');
247
+
248
+ printTable(byType.contact, [
249
+ { key: 'id', header: 'ID', width: 8, format: (v) => truncate(String(v), 8) },
250
+ { key: 'title', header: 'Name', width: 25 },
251
+ { key: 'subtitle', header: 'Details', width: 30, format: (v) => truncate(String(v), 30) },
252
+ {
253
+ key: 'matchField',
254
+ header: 'Match',
255
+ format: (v, row) => `${v}: ${(row as SearchResult).matchValue}`,
256
+ },
257
+ ]);
258
+ }
259
+
260
+ // Display deals
261
+ if (byType.deal?.length) {
262
+ console.log();
263
+ console.log(output.bold(`\u{1F4B0} Deals (${byType.deal.length})`));
264
+ printDivider('-');
265
+
266
+ printTable(byType.deal, [
267
+ { key: 'id', header: 'ID', width: 8, format: (v) => truncate(String(v), 8) },
268
+ { key: 'title', header: 'Title', width: 25, format: (v) => truncate(String(v), 25) },
269
+ { key: 'subtitle', header: 'Value/Stage', width: 30, format: (v) => truncate(String(v), 30) },
270
+ {
271
+ key: 'matchField',
272
+ header: 'Match',
273
+ format: (v, row) => `${v}: ${(row as SearchResult).matchValue}`,
274
+ },
275
+ ]);
276
+ }
277
+
278
+ // Display activities
279
+ if (byType.activity?.length) {
280
+ console.log();
281
+ console.log(output.bold(`\u{1F4DD} Activities (${byType.activity.length})`));
282
+ printDivider('-');
283
+
284
+ printTable(byType.activity, [
285
+ { key: 'id', header: 'ID', width: 8, format: (v) => truncate(String(v), 8) },
286
+ { key: 'title', header: 'Title', width: 30, format: (v) => truncate(String(v), 30) },
287
+ { key: 'subtitle', header: 'When', width: 20 },
288
+ {
289
+ key: 'matchField',
290
+ header: 'Match',
291
+ format: (v, row) => `${v}: ${(row as SearchResult).matchValue}`,
292
+ },
293
+ ]);
294
+ }
295
+
296
+ console.log();
297
+ });
298
+
299
+ // Quick search (alias)
300
+ search
301
+ .command('quick <query>')
302
+ .description('Quick search with minimal output')
303
+ .action(async (query) => {
304
+ const results: SearchResult[] = [];
305
+ const q = query.toLowerCase();
306
+
307
+ // Quick contact search
308
+ const contacts = await getContacts();
309
+ for (const contact of contacts) {
310
+ if (contact.name.toLowerCase().includes(q)) {
311
+ results.push({
312
+ type: 'contact',
313
+ id: contact.id,
314
+ title: contact.name,
315
+ subtitle: contact.emails[0]?.email || '',
316
+ });
317
+ if (results.length >= 5) break;
318
+ }
319
+ }
320
+
321
+ // Quick deal search
322
+ const deals = await getDeals();
323
+ for (const deal of deals) {
324
+ if (deal.title.toLowerCase().includes(q)) {
325
+ results.push({
326
+ type: 'deal',
327
+ id: deal.id,
328
+ title: deal.title,
329
+ subtitle: formatCurrency(deal.value, deal.currency),
330
+ });
331
+ if (results.length >= 10) break;
332
+ }
333
+ }
334
+
335
+ if (results.length === 0) {
336
+ printWarning('No quick results');
337
+ return;
338
+ }
339
+
340
+ for (const r of results) {
341
+ const icon = r.type === 'contact' ? '\u{1F464}' : '\u{1F4B0}';
342
+ console.log(`${icon} ${output.bold(r.id.slice(0, 8))} ${r.title} ${output.dim(r.subtitle)}`);
343
+ }
344
+ });
345
+
346
+ // Recent items
347
+ search
348
+ .command('recent')
349
+ .description('Show recent items')
350
+ .option('-t, --type <type>', 'Filter by type (contact, deal, activity)')
351
+ .option('--limit <number>', 'Limit results', '10')
352
+ .action(async (options) => {
353
+ const limit = parseInt(options.limit);
354
+ const results: SearchResult[] = [];
355
+
356
+ // Get recent contacts
357
+ if (!options.type || options.type === 'contact') {
358
+ const contacts = (await getContacts())
359
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
360
+ .slice(0, limit);
361
+
362
+ for (const c of contacts) {
363
+ results.push({
364
+ type: 'contact',
365
+ id: c.id,
366
+ title: c.name,
367
+ subtitle: `Updated ${formatRelativeTime(c.updatedAt)}`,
368
+ });
369
+ }
370
+ }
371
+
372
+ // Get recent deals
373
+ if (!options.type || options.type === 'deal') {
374
+ const deals = (await getDeals())
375
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
376
+ .slice(0, limit);
377
+
378
+ for (const d of deals) {
379
+ results.push({
380
+ type: 'deal',
381
+ id: d.id,
382
+ title: d.title,
383
+ subtitle: `${formatStatus(d.stage)} - Updated ${formatRelativeTime(d.updatedAt)}`,
384
+ });
385
+ }
386
+ }
387
+
388
+ // Get recent activities
389
+ if (!options.type || options.type === 'activity') {
390
+ const activities = (await getActivities())
391
+ .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
392
+ .slice(0, limit);
393
+
394
+ for (const a of activities) {
395
+ results.push({
396
+ type: 'activity',
397
+ id: a.id,
398
+ title: a.title,
399
+ subtitle: `${a.type} - ${formatRelativeTime(a.timestamp)}`,
400
+ });
401
+ }
402
+ }
403
+
404
+ if (results.length === 0) {
405
+ printWarning('No recent items');
406
+ return;
407
+ }
408
+
409
+ printHeader('Recent Items');
410
+
411
+ for (const r of results) {
412
+ const icons: Record<string, string> = {
413
+ contact: '\u{1F464}',
414
+ deal: '\u{1F4B0}',
415
+ activity: '\u{1F4DD}',
416
+ };
417
+ const icon = icons[r.type];
418
+ console.log(
419
+ `${icon} ${output.dim(r.type.padEnd(8))} ${output.bold(truncate(r.id, 8))} ${r.title}`
420
+ );
421
+ console.log(` ${output.dim(r.subtitle)}`);
422
+ }
423
+
424
+ console.log();
425
+ });
426
+ }
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CRM CLI Entry Point
4
+ *
5
+ * Terminal-based CLI for CRM operations.
6
+ */
7
+
8
+ import { Command } from 'commander';
9
+ import {
10
+ printError,
11
+ printHeader,
12
+ output,
13
+ } from './utils/output.js';
14
+ import { loadConfig, applyEnvOverrides, initConfig } from './utils/config.js';
15
+ import { registerContactCommands } from './commands/contacts.js';
16
+ import { registerDealCommands } from './commands/deals.js';
17
+ import { registerActivityCommands } from './commands/activities.js';
18
+ import { registerMediaCommands } from './commands/media.js';
19
+ import { registerSearchCommands } from './commands/search.js';
20
+ import { registerREPLCommand } from './repl.js';
21
+
22
+ // Package version (would be read from package.json in production)
23
+ const VERSION = '1.0.0';
24
+
25
+ /**
26
+ * Create and configure the CLI program
27
+ */
28
+ function createProgram(): Command {
29
+ const program = new Command();
30
+
31
+ // Program metadata
32
+ program
33
+ .name('crm')
34
+ .description('Terminal-based CRM CLI')
35
+ .version(VERSION, '-v, --version', 'Show version')
36
+ .helpOption('-h, --help', 'Show help');
37
+
38
+ // Global options
39
+ program
40
+ .option('--json', 'Output as JSON')
41
+ .option('--no-color', 'Disable colored output')
42
+ .option('--api <url>', 'API endpoint URL');
43
+
44
+ // Register command modules
45
+ registerContactCommands(program);
46
+ registerDealCommands(program);
47
+ registerActivityCommands(program);
48
+ registerMediaCommands(program);
49
+ registerSearchCommands(program);
50
+ registerREPLCommand(program);
51
+
52
+ // Config command
53
+ program
54
+ .command('config')
55
+ .description('Manage CLI configuration')
56
+ .argument('[action]', 'Action (show, init, location)')
57
+ .action(async (action = 'show') => {
58
+ const { displayConfig, initConfig, getConfigLocation } = await import(
59
+ './utils/config.js'
60
+ );
61
+
62
+ switch (action) {
63
+ case 'init':
64
+ await initConfig();
65
+ break;
66
+ case 'location':
67
+ console.log(`\nConfig file: ${getConfigLocation()}\n`);
68
+ break;
69
+ case 'show':
70
+ default:
71
+ displayConfig();
72
+ }
73
+ });
74
+
75
+ // Init command (alias)
76
+ program
77
+ .command('init')
78
+ .description('Initialize CLI configuration')
79
+ .action(async () => {
80
+ const { initConfig } = await import('./utils/config.js');
81
+ await initConfig();
82
+ });
83
+
84
+ // Status command
85
+ program
86
+ .command('status')
87
+ .description('Show CRM status overview')
88
+ .option('--json', 'Output as JSON')
89
+ .action(async (options) => {
90
+ const { printTable, printJSON, formatCurrency } = await import(
91
+ './utils/output.js'
92
+ );
93
+
94
+ // Mock data (would come from API)
95
+ const status = {
96
+ contacts: { total: 156, leads: 45, customers: 89 },
97
+ deals: {
98
+ total: 34,
99
+ pipeline: 245000,
100
+ won: 89000,
101
+ weighted: 156000,
102
+ },
103
+ activities: { today: 12, thisWeek: 67 },
104
+ };
105
+
106
+ if (options.json) {
107
+ printJSON(status);
108
+ return;
109
+ }
110
+
111
+ printHeader('CRM Status');
112
+
113
+ console.log(output.bold('\u{1F464} Contacts'));
114
+ console.log(` Total: ${status.contacts.total}`);
115
+ console.log(` Leads: ${status.contacts.leads}`);
116
+ console.log(` Customers: ${status.contacts.customers}`);
117
+ console.log();
118
+
119
+ console.log(output.bold('\u{1F4B0} Deals'));
120
+ console.log(` Total: ${status.deals.total}`);
121
+ console.log(` Pipeline Value: ${formatCurrency(status.deals.pipeline)}`);
122
+ console.log(` Won This Month: ${formatCurrency(status.deals.won)}`);
123
+ console.log(` Weighted Pipeline: ${formatCurrency(status.deals.weighted)}`);
124
+ console.log();
125
+
126
+ console.log(output.bold('\u{1F4DD} Activities'));
127
+ console.log(` Today: ${status.activities.today}`);
128
+ console.log(` This Week: ${status.activities.thisWeek}`);
129
+ console.log();
130
+ });
131
+
132
+ // Quick commands
133
+ program
134
+ .command('ls')
135
+ .description('List contacts (alias for contacts list)')
136
+ .allowUnknownOption(true)
137
+ .action(async () => {
138
+ const args = process.argv.slice(process.argv.indexOf('ls') + 1);
139
+ await program.parseAsync(['contacts', 'list', ...args], { from: 'user' });
140
+ });
141
+
142
+ program
143
+ .command('new')
144
+ .description('Create new entity')
145
+ .argument('<type>', 'Entity type (contact, deal, activity)')
146
+ .action(async (type) => {
147
+ switch (type) {
148
+ case 'contact':
149
+ await program.parseAsync(['contacts', 'create'], { from: 'user' });
150
+ break;
151
+ case 'deal':
152
+ await program.parseAsync(['deals', 'create'], { from: 'user' });
153
+ break;
154
+ case 'activity':
155
+ await program.parseAsync(['activities', 'create'], { from: 'user' });
156
+ break;
157
+ default:
158
+ printError(`Unknown entity type: ${type}`);
159
+ console.log('Valid types: contact, deal, activity');
160
+ }
161
+ });
162
+
163
+ return program;
164
+ }
165
+
166
+ /**
167
+ * Main entry point
168
+ */
169
+ async function main(): Promise<void> {
170
+ // Load and apply configuration
171
+ let config = loadConfig();
172
+ config = applyEnvOverrides(config);
173
+
174
+ // Create program
175
+ const program = createProgram();
176
+
177
+ // Handle unknown commands
178
+ program.on('command:*', (operands) => {
179
+ printError(`Unknown command: ${operands[0]}`);
180
+ console.log(`Run ${output.info('crm --help')} for available commands.`);
181
+ process.exit(1);
182
+ });
183
+
184
+ // Parse arguments
185
+ try {
186
+ await program.parseAsync(process.argv);
187
+ } catch (error) {
188
+ if (error instanceof Error) {
189
+ printError(error.message);
190
+ } else {
191
+ printError('An unexpected error occurred');
192
+ }
193
+ process.exit(1);
194
+ }
195
+ }
196
+
197
+ // Run main
198
+ main().catch((error) => {
199
+ console.error('Fatal error:', error);
200
+ process.exit(1);
201
+ });
202
+
203
+ export { createProgram };