@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,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
|
+
}
|