@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
package/src/cli/repl.ts
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive REPL Mode
|
|
3
|
+
*
|
|
4
|
+
* Provides an interactive shell for CRM operations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as readline from 'readline';
|
|
8
|
+
import {
|
|
9
|
+
printSuccess,
|
|
10
|
+
printError,
|
|
11
|
+
printHeader,
|
|
12
|
+
printDivider,
|
|
13
|
+
output,
|
|
14
|
+
colorize,
|
|
15
|
+
} from './utils/output.js';
|
|
16
|
+
import { displayConfig, getConfigLocation } from './utils/config.js';
|
|
17
|
+
import type { Command } from 'commander';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* REPL history
|
|
21
|
+
*/
|
|
22
|
+
const history: string[] = [];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* REPL context
|
|
26
|
+
*/
|
|
27
|
+
interface REPLContext {
|
|
28
|
+
currentContact: string | null;
|
|
29
|
+
currentDeal: string | null;
|
|
30
|
+
lastResults: unknown[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const context: REPLContext = {
|
|
34
|
+
currentContact: null,
|
|
35
|
+
currentDeal: null,
|
|
36
|
+
lastResults: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Available commands in REPL
|
|
41
|
+
*/
|
|
42
|
+
const REPL_COMMANDS: Record<
|
|
43
|
+
string,
|
|
44
|
+
{ description: string; usage: string; example?: string }
|
|
45
|
+
> = {
|
|
46
|
+
help: {
|
|
47
|
+
description: 'Show available commands',
|
|
48
|
+
usage: 'help [command]',
|
|
49
|
+
},
|
|
50
|
+
contacts: {
|
|
51
|
+
description: 'Manage contacts',
|
|
52
|
+
usage: 'contacts list|get|create|update|delete [args...]',
|
|
53
|
+
example: 'contacts list --search john',
|
|
54
|
+
},
|
|
55
|
+
deals: {
|
|
56
|
+
description: 'Manage deals',
|
|
57
|
+
usage: 'deals list|get|create|update|delete|move [args...]',
|
|
58
|
+
example: 'deals list --stage proposal',
|
|
59
|
+
},
|
|
60
|
+
activities: {
|
|
61
|
+
description: 'Manage activities',
|
|
62
|
+
usage: 'activities list|log|create|delete [args...]',
|
|
63
|
+
example: 'activities log <contact-id> call "Follow up call"',
|
|
64
|
+
},
|
|
65
|
+
media: {
|
|
66
|
+
description: 'Manage media files',
|
|
67
|
+
usage: 'media list|upload|get|delete [args...]',
|
|
68
|
+
example: 'media upload ./doc.pdf --contact <id>',
|
|
69
|
+
},
|
|
70
|
+
search: {
|
|
71
|
+
description: 'Search across entities',
|
|
72
|
+
usage: 'search <query>',
|
|
73
|
+
example: 'search john smith',
|
|
74
|
+
},
|
|
75
|
+
recent: {
|
|
76
|
+
description: 'Show recent items',
|
|
77
|
+
usage: 'recent [contacts|deals|activities]',
|
|
78
|
+
},
|
|
79
|
+
use: {
|
|
80
|
+
description: 'Set context (current contact/deal)',
|
|
81
|
+
usage: 'use contact|deal <id>',
|
|
82
|
+
example: 'use contact abc-123',
|
|
83
|
+
},
|
|
84
|
+
context: {
|
|
85
|
+
description: 'Show current context',
|
|
86
|
+
usage: 'context',
|
|
87
|
+
},
|
|
88
|
+
clear: {
|
|
89
|
+
description: 'Clear the screen',
|
|
90
|
+
usage: 'clear',
|
|
91
|
+
},
|
|
92
|
+
config: {
|
|
93
|
+
description: 'Show or manage configuration',
|
|
94
|
+
usage: 'config [location]',
|
|
95
|
+
},
|
|
96
|
+
history: {
|
|
97
|
+
description: 'Show command history',
|
|
98
|
+
usage: 'history [n]',
|
|
99
|
+
},
|
|
100
|
+
exit: {
|
|
101
|
+
description: 'Exit REPL',
|
|
102
|
+
usage: 'exit',
|
|
103
|
+
},
|
|
104
|
+
quit: {
|
|
105
|
+
description: 'Exit REPL (alias)',
|
|
106
|
+
usage: 'quit',
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create readline interface
|
|
112
|
+
*/
|
|
113
|
+
function createRL(): readline.ReadLine {
|
|
114
|
+
return readline.createInterface({
|
|
115
|
+
input: process.stdin,
|
|
116
|
+
output: process.stdout,
|
|
117
|
+
history: history,
|
|
118
|
+
historySize: 100,
|
|
119
|
+
removeHistoryDuplicates: true,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get prompt string
|
|
125
|
+
*/
|
|
126
|
+
function getPrompt(): string {
|
|
127
|
+
const parts: string[] = [];
|
|
128
|
+
|
|
129
|
+
if (context.currentContact) {
|
|
130
|
+
parts.push(output.info(`c:${context.currentContact.slice(0, 4)}`));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (context.currentDeal) {
|
|
134
|
+
parts.push(output.info(`d:${context.currentDeal.slice(0, 4)}`));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const contextStr = parts.length > 0 ? `(${parts.join(',')})` : '';
|
|
138
|
+
return `${contextStr}crm> `;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Show help
|
|
143
|
+
*/
|
|
144
|
+
function showHelp(command?: string): void {
|
|
145
|
+
if (command && REPL_COMMANDS[command]) {
|
|
146
|
+
const cmd = REPL_COMMANDS[command];
|
|
147
|
+
console.log();
|
|
148
|
+
console.log(output.bold(command));
|
|
149
|
+
console.log(` ${cmd.description}`);
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(` Usage: ${cmd.usage}`);
|
|
152
|
+
if (cmd.example) {
|
|
153
|
+
console.log(` Example: ${cmd.example}`);
|
|
154
|
+
}
|
|
155
|
+
console.log();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(output.bold('CRM REPL Commands'));
|
|
161
|
+
printDivider();
|
|
162
|
+
|
|
163
|
+
const maxCmd = Math.max(...Object.keys(REPL_COMMANDS).map((c) => c.length));
|
|
164
|
+
|
|
165
|
+
for (const [cmd, info] of Object.entries(REPL_COMMANDS)) {
|
|
166
|
+
console.log(` ${colorize(cmd.padEnd(maxCmd + 2), 'cyan')}${info.description}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log();
|
|
170
|
+
console.log(`Type ${output.info('help <command>')} for detailed usage.`);
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Show context
|
|
176
|
+
*/
|
|
177
|
+
function showContext(): void {
|
|
178
|
+
console.log();
|
|
179
|
+
console.log(output.bold('Current Context'));
|
|
180
|
+
printDivider('-');
|
|
181
|
+
|
|
182
|
+
if (context.currentContact) {
|
|
183
|
+
console.log(` Contact: ${context.currentContact}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (context.currentDeal) {
|
|
187
|
+
console.log(` Deal: ${context.currentDeal}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!context.currentContact && !context.currentDeal) {
|
|
191
|
+
console.log(' (no context set)');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Set context
|
|
199
|
+
*/
|
|
200
|
+
function setContext(type: string, id: string): void {
|
|
201
|
+
if (type === 'contact') {
|
|
202
|
+
context.currentContact = id;
|
|
203
|
+
printSuccess(`Context set to contact: ${id}`);
|
|
204
|
+
} else if (type === 'deal') {
|
|
205
|
+
context.currentDeal = id;
|
|
206
|
+
printSuccess(`Context set to deal: ${id}`);
|
|
207
|
+
} else {
|
|
208
|
+
printError(`Unknown context type: ${type}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Show history
|
|
214
|
+
*/
|
|
215
|
+
function showHistory(n = 10): void {
|
|
216
|
+
console.log();
|
|
217
|
+
const count = Math.min(n, history.length);
|
|
218
|
+
const start = Math.max(0, history.length - count);
|
|
219
|
+
|
|
220
|
+
for (let i = start; i < history.length; i++) {
|
|
221
|
+
const num = String(i + 1).padStart(3);
|
|
222
|
+
console.log(` ${output.dim(num)} ${history[i]}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Parse and execute command
|
|
230
|
+
*/
|
|
231
|
+
async function executeCommand(
|
|
232
|
+
input: string,
|
|
233
|
+
program: Command
|
|
234
|
+
): Promise<boolean> {
|
|
235
|
+
const trimmed = input.trim();
|
|
236
|
+
|
|
237
|
+
if (!trimmed) return true;
|
|
238
|
+
|
|
239
|
+
// Add to history
|
|
240
|
+
history.push(trimmed);
|
|
241
|
+
|
|
242
|
+
// Parse command
|
|
243
|
+
const parts = trimmed.split(/\s+/);
|
|
244
|
+
const cmd = parts[0]?.toLowerCase();
|
|
245
|
+
const args = parts.slice(1);
|
|
246
|
+
|
|
247
|
+
switch (cmd) {
|
|
248
|
+
case 'help':
|
|
249
|
+
case '?':
|
|
250
|
+
showHelp(args[0]);
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'exit':
|
|
254
|
+
case 'quit':
|
|
255
|
+
case 'q':
|
|
256
|
+
console.log('\nGoodbye!');
|
|
257
|
+
return false;
|
|
258
|
+
|
|
259
|
+
case 'clear':
|
|
260
|
+
console.clear();
|
|
261
|
+
break;
|
|
262
|
+
|
|
263
|
+
case 'context':
|
|
264
|
+
showContext();
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
case 'use':
|
|
268
|
+
if (args.length < 2) {
|
|
269
|
+
printError('Usage: use contact|deal <id>');
|
|
270
|
+
} else {
|
|
271
|
+
setContext(args[0], args[1]);
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case 'recent':
|
|
276
|
+
// Delegate to search recent
|
|
277
|
+
try {
|
|
278
|
+
await program.parseAsync(['recent', ...args], { from: 'user' });
|
|
279
|
+
} catch (e) {
|
|
280
|
+
printError(String(e));
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'history':
|
|
285
|
+
showHistory(args[0] ? parseInt(args[0]) : 10);
|
|
286
|
+
break;
|
|
287
|
+
|
|
288
|
+
case 'config':
|
|
289
|
+
if (args[0] === 'location') {
|
|
290
|
+
console.log(`\nConfig file: ${getConfigLocation()}\n`);
|
|
291
|
+
} else {
|
|
292
|
+
displayConfig();
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case 'contacts':
|
|
297
|
+
case 'deals':
|
|
298
|
+
case 'activities':
|
|
299
|
+
case 'media':
|
|
300
|
+
case 'search':
|
|
301
|
+
// Delegate to main program
|
|
302
|
+
try {
|
|
303
|
+
// Add context if not specified
|
|
304
|
+
const fullArgs = [...args];
|
|
305
|
+
if (cmd === 'activities' && !args.includes('-c') && !args.includes('--contact') && context.currentContact) {
|
|
306
|
+
if (args[0] === 'list') {
|
|
307
|
+
fullArgs.push('-c', context.currentContact);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (cmd === 'media' && !args.includes('-c') && !args.includes('--contact') && context.currentContact) {
|
|
311
|
+
if (args[0] === 'list') {
|
|
312
|
+
fullArgs.push('-c', context.currentContact);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
await program.parseAsync([cmd, ...fullArgs], { from: 'user' });
|
|
317
|
+
} catch (e) {
|
|
318
|
+
printError(String(e));
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
|
|
322
|
+
default:
|
|
323
|
+
printError(`Unknown command: ${cmd}`);
|
|
324
|
+
console.log(`Type ${output.info('help')} for available commands.`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Start REPL
|
|
332
|
+
*/
|
|
333
|
+
export async function startREPL(program: Command): Promise<void> {
|
|
334
|
+
printHeader('CRM Interactive Mode');
|
|
335
|
+
console.log(`Type ${output.info('help')} for commands, ${output.info('exit')} to quit.`);
|
|
336
|
+
console.log();
|
|
337
|
+
|
|
338
|
+
const rl = createRL();
|
|
339
|
+
|
|
340
|
+
const promptForInput = () => {
|
|
341
|
+
rl.question(getPrompt(), async (input) => {
|
|
342
|
+
const shouldContinue = await executeCommand(input, program);
|
|
343
|
+
|
|
344
|
+
if (shouldContinue) {
|
|
345
|
+
promptForInput();
|
|
346
|
+
} else {
|
|
347
|
+
rl.close();
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Handle Ctrl+C
|
|
354
|
+
rl.on('SIGINT', () => {
|
|
355
|
+
console.log('\n\nGoodbye!');
|
|
356
|
+
rl.close();
|
|
357
|
+
process.exit(0);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Handle Ctrl+D
|
|
361
|
+
rl.on('close', () => {
|
|
362
|
+
console.log('\nGoodbye!');
|
|
363
|
+
process.exit(0);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
promptForInput();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Register REPL command
|
|
371
|
+
*/
|
|
372
|
+
export function registerREPLCommand(program: Command): void {
|
|
373
|
+
program
|
|
374
|
+
.command('repl')
|
|
375
|
+
.description('Start interactive REPL mode')
|
|
376
|
+
.action(async () => {
|
|
377
|
+
await startREPL(program);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Configuration Management
|
|
3
|
+
*
|
|
4
|
+
* Handles loading, saving, and managing CLI configuration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration interface
|
|
13
|
+
*/
|
|
14
|
+
export interface CLIConfig {
|
|
15
|
+
/** API endpoint for CRM backend */
|
|
16
|
+
apiEndpoint: string;
|
|
17
|
+
|
|
18
|
+
/** Default output format */
|
|
19
|
+
outputFormat: 'table' | 'json' | 'plain';
|
|
20
|
+
|
|
21
|
+
/** Default page size for lists */
|
|
22
|
+
pageSize: number;
|
|
23
|
+
|
|
24
|
+
/** Default currency */
|
|
25
|
+
currency: string;
|
|
26
|
+
|
|
27
|
+
/** Color output enabled */
|
|
28
|
+
colorEnabled: boolean;
|
|
29
|
+
|
|
30
|
+
/** Editor for multiline input */
|
|
31
|
+
editor?: string;
|
|
32
|
+
|
|
33
|
+
/** Recent contacts for quick access */
|
|
34
|
+
recentContacts: string[];
|
|
35
|
+
|
|
36
|
+
/** Recent deals for quick access */
|
|
37
|
+
recentDeals: string[];
|
|
38
|
+
|
|
39
|
+
/** Custom aliases */
|
|
40
|
+
aliases: Record<string, string>;
|
|
41
|
+
|
|
42
|
+
/** User preferences */
|
|
43
|
+
preferences: {
|
|
44
|
+
defaultStatus?: string;
|
|
45
|
+
defaultDealStage?: string;
|
|
46
|
+
defaultPriority?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Default configuration
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_CONFIG: CLIConfig = {
|
|
54
|
+
apiEndpoint: 'http://localhost:3000/api',
|
|
55
|
+
outputFormat: 'table',
|
|
56
|
+
pageSize: 20,
|
|
57
|
+
currency: 'USD',
|
|
58
|
+
colorEnabled: true,
|
|
59
|
+
recentContacts: [],
|
|
60
|
+
recentDeals: [],
|
|
61
|
+
aliases: {},
|
|
62
|
+
preferences: {},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get config file path
|
|
67
|
+
*/
|
|
68
|
+
function getConfigPath(): string {
|
|
69
|
+
const configDir = path.join(os.homedir(), '.crm');
|
|
70
|
+
if (!fs.existsSync(configDir)) {
|
|
71
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
return path.join(configDir, 'config.json');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load configuration from file
|
|
78
|
+
*/
|
|
79
|
+
export function loadConfig(): CLIConfig {
|
|
80
|
+
const configPath = getConfigPath();
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(configPath)) {
|
|
83
|
+
return { ...DEFAULT_CONFIG };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
88
|
+
const userConfig = JSON.parse(content);
|
|
89
|
+
return { ...DEFAULT_CONFIG, ...userConfig };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Warning: Failed to load config, using defaults');
|
|
92
|
+
return { ...DEFAULT_CONFIG };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Save configuration to file
|
|
98
|
+
*/
|
|
99
|
+
export function saveConfig(config: CLIConfig): void {
|
|
100
|
+
const configPath = getConfigPath();
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Failed to save config:', error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Update specific config value
|
|
111
|
+
*/
|
|
112
|
+
export function updateConfig<K extends keyof CLIConfig>(
|
|
113
|
+
key: K,
|
|
114
|
+
value: CLIConfig[K]
|
|
115
|
+
): void {
|
|
116
|
+
const config = loadConfig();
|
|
117
|
+
config[key] = value;
|
|
118
|
+
saveConfig(config);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get a config value
|
|
123
|
+
*/
|
|
124
|
+
export function getConfig<K extends keyof CLIConfig>(
|
|
125
|
+
key: K
|
|
126
|
+
): CLIConfig[K] {
|
|
127
|
+
const config = loadConfig();
|
|
128
|
+
return config[key];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Add to recent contacts
|
|
133
|
+
*/
|
|
134
|
+
export function addRecentContact(contactId: string): void {
|
|
135
|
+
const config = loadConfig();
|
|
136
|
+
const recent = config.recentContacts.filter((id) => id !== contactId);
|
|
137
|
+
recent.unshift(contactId);
|
|
138
|
+
config.recentContacts = recent.slice(0, 10); // Keep last 10
|
|
139
|
+
saveConfig(config);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Add to recent deals
|
|
144
|
+
*/
|
|
145
|
+
export function addRecentDeal(dealId: string): void {
|
|
146
|
+
const config = loadConfig();
|
|
147
|
+
const recent = config.recentDeals.filter((id) => id !== dealId);
|
|
148
|
+
recent.unshift(dealId);
|
|
149
|
+
config.recentDeals = recent.slice(0, 10); // Keep last 10
|
|
150
|
+
saveConfig(config);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set an alias
|
|
155
|
+
*/
|
|
156
|
+
export function setAlias(name: string, command: string): void {
|
|
157
|
+
const config = loadConfig();
|
|
158
|
+
config.aliases[name] = command;
|
|
159
|
+
saveConfig(config);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Remove an alias
|
|
164
|
+
*/
|
|
165
|
+
export function removeAlias(name: string): boolean {
|
|
166
|
+
const config = loadConfig();
|
|
167
|
+
if (config.aliases[name]) {
|
|
168
|
+
delete config.aliases[name];
|
|
169
|
+
saveConfig(config);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve alias to command
|
|
177
|
+
*/
|
|
178
|
+
export function resolveAlias(name: string): string | null {
|
|
179
|
+
const config = loadConfig();
|
|
180
|
+
return config.aliases[name] || null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Reset configuration to defaults
|
|
185
|
+
*/
|
|
186
|
+
export function resetConfig(): void {
|
|
187
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Display current configuration
|
|
192
|
+
*/
|
|
193
|
+
export function displayConfig(): void {
|
|
194
|
+
const config = loadConfig();
|
|
195
|
+
console.log('\nCurrent Configuration:\n');
|
|
196
|
+
|
|
197
|
+
const entries: [string, unknown][] = [
|
|
198
|
+
['API Endpoint', config.apiEndpoint],
|
|
199
|
+
['Output Format', config.outputFormat],
|
|
200
|
+
['Page Size', config.pageSize],
|
|
201
|
+
['Currency', config.currency],
|
|
202
|
+
['Color Enabled', config.colorEnabled],
|
|
203
|
+
['Editor', config.editor || '(system default)'],
|
|
204
|
+
['Recent Contacts', config.recentContacts.length],
|
|
205
|
+
['Recent Deals', config.recentDeals.length],
|
|
206
|
+
['Aliases', Object.keys(config.aliases).length],
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
for (const [key, value] of entries) {
|
|
210
|
+
console.log(` \x1b[36m${key}:\x1b[0m ${value}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Config file location
|
|
218
|
+
*/
|
|
219
|
+
export function getConfigLocation(): string {
|
|
220
|
+
return getConfigPath();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Environment variable overrides
|
|
225
|
+
*/
|
|
226
|
+
export function applyEnvOverrides(config: CLIConfig): CLIConfig {
|
|
227
|
+
const envConfig = { ...config };
|
|
228
|
+
|
|
229
|
+
if (process.env.CRM_API_ENDPOINT) {
|
|
230
|
+
envConfig.apiEndpoint = process.env.CRM_API_ENDPOINT;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (process.env.CRM_OUTPUT_FORMAT) {
|
|
234
|
+
envConfig.outputFormat = process.env.CRM_OUTPUT_FORMAT as CLIConfig['outputFormat'];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (process.env.CRM_CURRENCY) {
|
|
238
|
+
envConfig.currency = process.env.CRM_CURRENCY;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (process.env.NO_COLOR) {
|
|
242
|
+
envConfig.colorEnabled = false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return envConfig;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Initialize config with interactive prompts
|
|
250
|
+
*/
|
|
251
|
+
export async function initConfig(): Promise<void> {
|
|
252
|
+
const { prompt, confirm, select } = await import('./prompt.js');
|
|
253
|
+
|
|
254
|
+
console.log('\n\x1b[1mCRM CLI Configuration\x1b[0m\n');
|
|
255
|
+
console.log('Let\'s set up your CRM CLI configuration.\n');
|
|
256
|
+
|
|
257
|
+
const config = loadConfig();
|
|
258
|
+
|
|
259
|
+
// API Endpoint
|
|
260
|
+
const apiEndpoint = await prompt('API Endpoint', config.apiEndpoint);
|
|
261
|
+
if (apiEndpoint) {
|
|
262
|
+
config.apiEndpoint = apiEndpoint;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Output format
|
|
266
|
+
config.outputFormat = await select(
|
|
267
|
+
'Default output format',
|
|
268
|
+
['table', 'json', 'plain'] as const,
|
|
269
|
+
config.outputFormat
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Page size
|
|
273
|
+
const pageSizeStr = await prompt('Default page size', String(config.pageSize));
|
|
274
|
+
const pageSize = parseInt(pageSizeStr);
|
|
275
|
+
if (!isNaN(pageSize) && pageSize > 0) {
|
|
276
|
+
config.pageSize = pageSize;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Currency
|
|
280
|
+
config.currency = await select(
|
|
281
|
+
'Default currency',
|
|
282
|
+
['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY'],
|
|
283
|
+
config.currency
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Color
|
|
287
|
+
config.colorEnabled = await confirm('Enable color output?', config.colorEnabled);
|
|
288
|
+
|
|
289
|
+
// Editor
|
|
290
|
+
const editor = await prompt('Editor for multiline input (e.g., vim, code)', config.editor);
|
|
291
|
+
if (editor) {
|
|
292
|
+
config.editor = editor;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
saveConfig(config);
|
|
296
|
+
|
|
297
|
+
console.log('\n\x1b[32m\u2713 Configuration saved!\x1b[0m');
|
|
298
|
+
console.log(`\nConfig file: ${getConfigPath()}\n`);
|
|
299
|
+
}
|