@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,637 @@
1
+ /**
2
+ * Deal Commands
3
+ *
4
+ * CLI commands for managing deals in the CRM.
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
+ formatDate,
20
+ truncate,
21
+ output,
22
+ } from '../utils/output.js';
23
+ import {
24
+ prompt,
25
+ promptRequired,
26
+ promptNumber,
27
+ promptTags,
28
+ confirm,
29
+ select,
30
+ previewAndConfirm,
31
+ Spinner,
32
+ } from '../utils/prompt.js';
33
+ import { loadConfig, addRecentDeal, getConfig } from '../utils/config.js';
34
+ import { CRMStorageClient } from '../../mcp/storage/client.js';
35
+ import type { Deal, DealStage, DealPriority, Currency } from '../../core/types.js';
36
+
37
+ // Deal stage options
38
+ const DEAL_STAGES: DealStage[] = [
39
+ 'prospecting',
40
+ 'qualification',
41
+ 'needs_analysis',
42
+ 'proposal',
43
+ 'negotiation',
44
+ 'closed_won',
45
+ 'closed_lost',
46
+ ];
47
+
48
+ // Deal priority options
49
+ const DEAL_PRIORITIES: DealPriority[] = ['low', 'medium', 'high', 'urgent'];
50
+
51
+ // Currency options
52
+ const CURRENCIES: Currency[] = [
53
+ 'USD',
54
+ 'EUR',
55
+ 'GBP',
56
+ 'CAD',
57
+ 'AUD',
58
+ 'JPY',
59
+ 'CNY',
60
+ 'INR',
61
+ 'BRL',
62
+ 'MXN',
63
+ ];
64
+
65
+ // Stage display names
66
+ const STAGE_LABELS: Record<DealStage, string> = {
67
+ prospecting: 'Prospecting',
68
+ qualification: 'Qualification',
69
+ needs_analysis: 'Needs Analysis',
70
+ proposal: 'Proposal',
71
+ negotiation: 'Negotiation',
72
+ closed_won: 'Closed Won',
73
+ closed_lost: 'Closed Lost',
74
+ };
75
+
76
+ // Storage client singleton
77
+ let _storage: CRMStorageClient | null = null;
78
+
79
+ /**
80
+ * Get storage client (lazy initialization)
81
+ */
82
+ async function getStorage(): Promise<CRMStorageClient> {
83
+ if (!_storage) {
84
+ _storage = new CRMStorageClient({
85
+ path: process.env.CRM_DB_PATH || './data/crm-cli',
86
+ mapSize: 256 * 1024 * 1024, // 256MB
87
+ maxDbs: 20,
88
+ });
89
+ await _storage.initialize();
90
+ }
91
+ return _storage;
92
+ }
93
+
94
+ /**
95
+ * Get all deals
96
+ */
97
+ async function getDeals(): Promise<Deal[]> {
98
+ const storage = await getStorage();
99
+ return storage.list('deals');
100
+ }
101
+
102
+ /**
103
+ * Get deal by ID
104
+ */
105
+ async function getDeal(id: string): Promise<Deal | null> {
106
+ const storage = await getStorage();
107
+ return storage.get('deals', id);
108
+ }
109
+
110
+ /**
111
+ * Create deal
112
+ */
113
+ async function createDeal(data: Partial<Deal>): Promise<Deal> {
114
+ const storage = await getStorage();
115
+ return storage.insert('deals', {
116
+ title: data.title || '',
117
+ contactId: data.contactId || '',
118
+ companyId: data.companyId,
119
+ value: data.value || 0,
120
+ currency: data.currency || 'USD',
121
+ stage: data.stage || 'prospecting',
122
+ probability: data.probability || 0,
123
+ expectedClose: data.expectedClose || new Date().toISOString(),
124
+ actualClose: data.actualClose,
125
+ priority: data.priority || 'medium',
126
+ lineItems: data.lineItems || [],
127
+ discount: data.discount,
128
+ discountType: data.discountType,
129
+ totalValue: data.totalValue || data.value || 0,
130
+ notes: data.notes || '',
131
+ tags: data.tags || [],
132
+ competitors: data.competitors || [],
133
+ lossReason: data.lossReason,
134
+ nextSteps: data.nextSteps,
135
+ ownerId: data.ownerId,
136
+ source: data.source,
137
+ customFields: data.customFields || [],
138
+ lastActivityAt: data.lastActivityAt,
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Update deal
144
+ */
145
+ async function updateDeal(id: string, data: Partial<Deal>): Promise<Deal | null> {
146
+ const storage = await getStorage();
147
+ const existing = await storage.get('deals', id);
148
+ if (!existing) return null;
149
+ return storage.update('deals', id, data);
150
+ }
151
+
152
+ /**
153
+ * Delete deal
154
+ */
155
+ async function deleteDeal(id: string): Promise<boolean> {
156
+ const storage = await getStorage();
157
+ const existing = await storage.get('deals', id);
158
+ if (!existing) return false;
159
+ await storage.delete('deals', id);
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * Filter deals by stage
165
+ */
166
+ async function filterByStage(stage: DealStage): Promise<Deal[]> {
167
+ const deals = await getDeals();
168
+ return deals.filter((d) => d.stage === stage);
169
+ }
170
+
171
+ /**
172
+ * Get probability for stage
173
+ */
174
+ function getStageProbability(stage: DealStage): number {
175
+ const probabilities: Record<DealStage, number> = {
176
+ prospecting: 10,
177
+ qualification: 20,
178
+ needs_analysis: 40,
179
+ proposal: 60,
180
+ negotiation: 80,
181
+ closed_won: 100,
182
+ closed_lost: 0,
183
+ };
184
+ return probabilities[stage];
185
+ }
186
+
187
+ /**
188
+ * Register deal commands
189
+ */
190
+ export function registerDealCommands(program: Command): void {
191
+ const deals = program.command('deals').description('Manage deals');
192
+
193
+ // List deals
194
+ deals
195
+ .command('list')
196
+ .description('List all deals')
197
+ .option('-s, --stage <stage>', 'Filter by stage')
198
+ .option('--priority <priority>', 'Filter by priority')
199
+ .option('--json', 'Output as JSON')
200
+ .option('--limit <number>', 'Limit results', '20')
201
+ .action(async (options) => {
202
+ let dealsList = await getDeals();
203
+
204
+ if (options.stage) {
205
+ dealsList = dealsList.filter(
206
+ (d) => d.stage.toLowerCase() === options.stage.toLowerCase()
207
+ );
208
+ }
209
+
210
+ if (options.priority) {
211
+ dealsList = dealsList.filter(
212
+ (d) => d.priority.toLowerCase() === options.priority.toLowerCase()
213
+ );
214
+ }
215
+
216
+ const limit = parseInt(options.limit);
217
+ dealsList = dealsList.slice(0, limit);
218
+
219
+ if (options.json) {
220
+ printJSON(dealsList);
221
+ return;
222
+ }
223
+
224
+ if (dealsList.length === 0) {
225
+ printWarning('No deals found');
226
+ return;
227
+ }
228
+
229
+ // Calculate pipeline value
230
+ const totalValue = dealsList.reduce((sum, d) => sum + d.totalValue, 0);
231
+ const weightedValue = dealsList.reduce(
232
+ (sum, d) => sum + d.totalValue * (d.probability / 100),
233
+ 0
234
+ );
235
+
236
+ printHeader(`Deals (${dealsList.length})`);
237
+ console.log(
238
+ `${output.dim('Pipeline:')} ${formatCurrency(totalValue)} | ` +
239
+ `${output.dim('Weighted:')} ${formatCurrency(weightedValue)}`
240
+ );
241
+ console.log();
242
+
243
+ printTable(dealsList, [
244
+ { key: 'id', header: 'ID', width: 8, format: (v) => truncate(String(v), 8) },
245
+ { key: 'title', header: 'Title', width: 25, format: (v) => truncate(String(v), 25) },
246
+ {
247
+ key: 'value',
248
+ header: 'Value',
249
+ width: 12,
250
+ format: (v, row) => formatCurrency(Number(v), (row as Deal).currency),
251
+ },
252
+ { key: 'stage', header: 'Stage', format: (v) => formatStatus(String(v)) },
253
+ { key: 'probability', header: 'Prob', width: 5, format: (v) => `${v}%` },
254
+ {
255
+ key: 'expectedClose',
256
+ header: 'Close Date',
257
+ format: (v) => formatDate(String(v)),
258
+ },
259
+ ]);
260
+ });
261
+
262
+ // Get deal details
263
+ deals
264
+ .command('get <id>')
265
+ .description('Get deal details')
266
+ .option('--json', 'Output as JSON')
267
+ .action(async (id, options) => {
268
+ const deal = await getDeal(id);
269
+
270
+ if (!deal) {
271
+ printError(`Deal not found: ${id}`);
272
+ process.exit(1);
273
+ }
274
+
275
+ addRecentDeal(id);
276
+
277
+ if (options.json) {
278
+ printJSON(deal);
279
+ return;
280
+ }
281
+
282
+ printHeader(`Deal: ${deal.title}`);
283
+
284
+ console.log(`${output.dim('ID:')} ${deal.id}`);
285
+ console.log(`${output.dim('Stage:')} ${formatStatus(deal.stage)}`);
286
+ console.log(`${output.dim('Priority:')} ${formatStatus(deal.priority)}`);
287
+ console.log();
288
+
289
+ // Deal Info
290
+ console.log(output.bold('Deal Information'));
291
+ printDivider('-');
292
+ console.log(
293
+ `${output.dim('Value:')} ${formatCurrency(deal.value, deal.currency)}`
294
+ );
295
+ console.log(
296
+ `${output.dim('Total Value:')} ${formatCurrency(deal.totalValue, deal.currency)}`
297
+ );
298
+ console.log(`${output.dim('Probability:')} ${deal.probability}%`);
299
+ console.log(
300
+ `${output.dim('Expected Close:')} ${formatDate(deal.expectedClose)}`
301
+ );
302
+
303
+ if (deal.actualClose) {
304
+ console.log(`${output.dim('Actual Close:')} ${formatDate(deal.actualClose)}`);
305
+ }
306
+
307
+ if (deal.discount) {
308
+ const discountStr =
309
+ deal.discountType === 'percentage'
310
+ ? `${deal.discount}%`
311
+ : formatCurrency(deal.discount, deal.currency);
312
+ console.log(`${output.dim('Discount:')} ${discountStr}`);
313
+ }
314
+
315
+ console.log();
316
+
317
+ // Contact Info
318
+ console.log(output.bold('Associated Contact'));
319
+ printDivider('-');
320
+ console.log(`${output.dim('Contact ID:')} ${deal.contactId}`);
321
+
322
+ if (deal.companyId) {
323
+ console.log(`${output.dim('Company ID:')} ${deal.companyId}`);
324
+ }
325
+
326
+ console.log();
327
+
328
+ // Tags
329
+ if (deal.tags.length > 0) {
330
+ console.log(output.bold('Tags'));
331
+ printDivider('-');
332
+ console.log(deal.tags.map((t) => output.info(`#${t}`)).join(' '));
333
+ console.log();
334
+ }
335
+
336
+ // Notes
337
+ if (deal.notes) {
338
+ console.log(output.bold('Notes'));
339
+ printDivider('-');
340
+ console.log(deal.notes);
341
+ console.log();
342
+ }
343
+
344
+ // Metadata
345
+ console.log(output.bold('Metadata'));
346
+ printDivider('-');
347
+ console.log(
348
+ `${output.dim('Created:')} ${new Date(deal.createdAt).toLocaleString()}`
349
+ );
350
+ console.log(
351
+ `${output.dim('Updated:')} ${new Date(deal.updatedAt).toLocaleString()}`
352
+ );
353
+
354
+ if (deal.lastActivityAt) {
355
+ console.log(
356
+ `${output.dim('Last Activity:')} ${formatRelativeTime(deal.lastActivityAt)}`
357
+ );
358
+ }
359
+
360
+ console.log();
361
+ });
362
+
363
+ // Create deal
364
+ deals
365
+ .command('create')
366
+ .description('Create a new deal')
367
+ .option('--title <title>', 'Deal title')
368
+ .option('--contact <id>', 'Contact ID')
369
+ .option('--value <amount>', 'Deal value')
370
+ .option('--currency <currency>', 'Currency')
371
+ .option('--stage <stage>', 'Deal stage')
372
+ .option('--priority <priority>', 'Deal priority')
373
+ .option('--tags <tags>', 'Tags (comma-separated)')
374
+ .option('--notes <notes>', 'Deal notes')
375
+ .action(async (options) => {
376
+ printHeader('Create Deal');
377
+
378
+ // Gather data
379
+ const title = options.title || (await promptRequired('Deal Title'));
380
+ const contactId = options.contact || (await promptRequired('Contact ID'));
381
+
382
+ const value =
383
+ options.value !== undefined
384
+ ? parseFloat(options.value)
385
+ : await promptNumber('Deal Value', 0, 0);
386
+
387
+ const config = loadConfig();
388
+ const currency = options.currency || config.currency;
389
+
390
+ let stage: DealStage = 'prospecting';
391
+ if (options.stage) {
392
+ stage = options.stage as DealStage;
393
+ } else {
394
+ stage = await select('Stage', DEAL_STAGES, 'prospecting');
395
+ }
396
+
397
+ const probability = getStageProbability(stage);
398
+
399
+ let priority: DealPriority = 'medium';
400
+ if (options.priority) {
401
+ priority = options.priority as DealPriority;
402
+ } else {
403
+ priority = await select('Priority', DEAL_PRIORITIES, 'medium');
404
+ }
405
+
406
+ const tags = options.tags
407
+ ? options.tags.split(',').map((t: string) => t.trim())
408
+ : await promptTags('Tags (comma-separated)');
409
+
410
+ const notes = options.notes || (await prompt('Notes'));
411
+
412
+ // Expected close date
413
+ const defaultCloseDate = new Date();
414
+ defaultCloseDate.setMonth(defaultCloseDate.getMonth() + 1);
415
+ const expectedCloseStr = await prompt(
416
+ 'Expected Close Date (YYYY-MM-DD)',
417
+ defaultCloseDate.toISOString().split('T')[0]
418
+ );
419
+ const expectedClose = new Date(expectedCloseStr).toISOString();
420
+
421
+ // Preview
422
+ const confirmed = await previewAndConfirm('Deal Preview', {
423
+ Title: title,
424
+ 'Contact ID': contactId,
425
+ Value: formatCurrency(value, currency),
426
+ Stage: stage,
427
+ Probability: `${probability}%`,
428
+ Priority: priority,
429
+ 'Expected Close': formatDate(expectedClose),
430
+ Tags: tags.length > 0 ? tags.join(', ') : '(none)',
431
+ Notes: notes || '(none)',
432
+ });
433
+
434
+ if (!confirmed) {
435
+ printWarning('Deal creation cancelled');
436
+ return;
437
+ }
438
+
439
+ // Create deal
440
+ const spinner = new Spinner('Creating deal...').start();
441
+
442
+ const deal = await createDeal({
443
+ title,
444
+ contactId,
445
+ value,
446
+ currency,
447
+ stage,
448
+ probability,
449
+ priority,
450
+ tags,
451
+ notes,
452
+ expectedClose,
453
+ totalValue: value,
454
+ });
455
+
456
+ spinner.succeed('Deal created successfully');
457
+ printSuccess(`Deal ID: ${deal.id}`);
458
+ console.log();
459
+ });
460
+
461
+ // Update deal
462
+ deals
463
+ .command('update <id>')
464
+ .description('Update a deal')
465
+ .option('--title <title>', 'Deal title')
466
+ .option('--value <amount>', 'Deal value')
467
+ .option('--stage <stage>', 'Deal stage')
468
+ .option('--priority <priority>', 'Deal priority')
469
+ .option('--probability <percent>', 'Win probability')
470
+ .option('--notes <notes>', 'Deal notes')
471
+ .action(async (id, options) => {
472
+ const deal = await getDeal(id);
473
+
474
+ if (!deal) {
475
+ printError(`Deal not found: ${id}`);
476
+ process.exit(1);
477
+ }
478
+
479
+ printHeader(`Update Deal: ${deal.title}`);
480
+
481
+ const updates: Partial<Deal> = {};
482
+
483
+ if (options.title) updates.title = options.title;
484
+ if (options.value !== undefined) {
485
+ updates.value = parseFloat(options.value);
486
+ updates.totalValue = updates.value;
487
+ }
488
+ if (options.stage) updates.stage = options.stage as DealStage;
489
+ if (options.priority) updates.priority = options.priority as DealPriority;
490
+ if (options.probability !== undefined) {
491
+ updates.probability = parseInt(options.probability);
492
+ }
493
+ if (options.notes) updates.notes = options.notes;
494
+
495
+ if (Object.keys(updates).length === 0) {
496
+ printWarning('No changes to update');
497
+ return;
498
+ }
499
+
500
+ const confirmed = await confirm('Save changes?');
501
+ if (!confirmed) {
502
+ printWarning('Update cancelled');
503
+ return;
504
+ }
505
+
506
+ const spinner = new Spinner('Updating deal...').start();
507
+
508
+ await updateDeal(id, updates);
509
+ spinner.succeed('Deal updated successfully');
510
+ });
511
+
512
+ // Move deal to stage
513
+ deals
514
+ .command('move <id> <stage>')
515
+ .description('Move a deal to a different stage')
516
+ .action(async (id, stageStr) => {
517
+ const deal = await getDeal(id);
518
+
519
+ if (!deal) {
520
+ printError(`Deal not found: ${id}`);
521
+ process.exit(1);
522
+ }
523
+
524
+ const stage = stageStr.toLowerCase() as DealStage;
525
+ if (!DEAL_STAGES.includes(stage)) {
526
+ printError(`Invalid stage: ${stageStr}`);
527
+ console.log(`Valid stages: ${DEAL_STAGES.join(', ')}`);
528
+ process.exit(1);
529
+ }
530
+
531
+ const probability = getStageProbability(stage);
532
+
533
+ console.log(
534
+ `\nMoving "${deal.title}" from ${formatStatus(deal.stage)} to ${formatStatus(stage)}`
535
+ );
536
+ console.log(`Probability: ${deal.probability}% -> ${probability}%\n`);
537
+
538
+ const confirmed = await confirm('Proceed?');
539
+ if (!confirmed) {
540
+ printWarning('Move cancelled');
541
+ return;
542
+ }
543
+
544
+ const spinner = new Spinner('Moving deal...').start();
545
+
546
+ await updateDeal(id, { stage, probability });
547
+
548
+ if (stage === 'closed_won' || stage === 'closed_lost') {
549
+ await updateDeal(id, { actualClose: new Date().toISOString() });
550
+ }
551
+
552
+ spinner.succeed(`Deal moved to ${STAGE_LABELS[stage]}`);
553
+ });
554
+
555
+ // Delete deal
556
+ deals
557
+ .command('delete <id>')
558
+ .description('Delete a deal')
559
+ .option('-f, --force', 'Skip confirmation')
560
+ .action(async (id, options) => {
561
+ const deal = await getDeal(id);
562
+
563
+ if (!deal) {
564
+ printError(`Deal not found: ${id}`);
565
+ process.exit(1);
566
+ }
567
+
568
+ if (!options.force) {
569
+ console.log(`\nDeal: ${output.bold(deal.title)}`);
570
+ console.log(`Value: ${formatCurrency(deal.value, deal.currency)}`);
571
+ console.log(`Stage: ${deal.stage}\n`);
572
+
573
+ const confirmed = await confirm(
574
+ 'Are you sure you want to delete this deal?'
575
+ );
576
+
577
+ if (!confirmed) {
578
+ printWarning('Deletion cancelled');
579
+ return;
580
+ }
581
+ }
582
+
583
+ const spinner = new Spinner('Deleting deal...').start();
584
+
585
+ await deleteDeal(id);
586
+ spinner.succeed('Deal deleted successfully');
587
+ });
588
+
589
+ // Pipeline summary
590
+ deals
591
+ .command('pipeline')
592
+ .description('Show pipeline summary')
593
+ .option('--json', 'Output as JSON')
594
+ .action(async (options) => {
595
+ const dealsList = await getDeals();
596
+
597
+ if (options.json) {
598
+ const summary = await Promise.all(DEAL_STAGES.map(async (stage) => {
599
+ const stageDeals = await filterByStage(stage);
600
+ return {
601
+ stage,
602
+ count: stageDeals.length,
603
+ value: stageDeals.reduce((sum, d) => sum + d.totalValue, 0),
604
+ };
605
+ }));
606
+ printJSON(summary);
607
+ return;
608
+ }
609
+
610
+ printHeader('Pipeline Summary');
611
+
612
+ let totalValue = 0;
613
+ let totalWeighted = 0;
614
+
615
+ for (const stage of DEAL_STAGES) {
616
+ const stageDeals = await filterByStage(stage);
617
+ const value = stageDeals.reduce((sum, d) => sum + d.totalValue, 0);
618
+ const weighted = value * (getStageProbability(stage) / 100);
619
+
620
+ totalValue += value;
621
+ totalWeighted += weighted;
622
+
623
+ const bar = '\u2588'.repeat(Math.min(20, Math.floor(stageDeals.length / 2)));
624
+ console.log(
625
+ `${formatStatus(stage).padEnd(20)} ` +
626
+ `${output.dim(stageDeals.length.toString().padStart(3))} ` +
627
+ `${output.info(bar)}` +
628
+ ` ${formatCurrency(value)}`
629
+ );
630
+ }
631
+
632
+ console.log();
633
+ console.log(`${output.bold('Total Pipeline:')} ${formatCurrency(totalValue)}`);
634
+ console.log(`${output.bold('Weighted Pipeline:')} ${formatCurrency(totalWeighted)}`);
635
+ console.log();
636
+ });
637
+ }