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