@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
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@ebowwa/crm",
3
+ "version": "0.1.0",
4
+ "description": "MCP server, CRM types, schemas, and native Rust module for high-performance search",
5
+ "type": "module",
6
+ "main": "dist/mcp/index.js",
7
+ "types": "dist/mcp/index.d.ts",
8
+ "bin": {
9
+ "crm-mcp": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/mcp/index.js",
14
+ "types": "./dist/mcp/index.d.ts"
15
+ },
16
+ "./core": {
17
+ "import": "./dist/core/index.js",
18
+ "types": "./dist/core/index.d.ts"
19
+ },
20
+ "./mcp": {
21
+ "import": "./dist/mcp/index.js",
22
+ "types": "./dist/mcp/index.d.ts"
23
+ },
24
+ "./native": {
25
+ "import": "./native/index.js",
26
+ "types": "./native/index.d.ts"
27
+ },
28
+ "./storage": {
29
+ "import": "./dist/mcp/storage/index.js",
30
+ "types": "./dist/mcp/storage/index.d.ts"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src",
36
+ "native"
37
+ ],
38
+ "scripts": {
39
+ "build": "bun run build:ts",
40
+ "build:ts": "tsc",
41
+ "build:native": "cd rust && cargo build --release 2>/dev/null && (cp target/release/libcrm_native.dylib ../native/crm-native.darwin-arm64.node 2>/dev/null || cp target/release/libcrm_native.so ../native/crm-native.linux-arm64.node 2>/dev/null || cp target/release/crm_native.dll ../native/crm-native.win32-x64.node 2>/dev/null) || echo 'Native build skipped - using JS fallbacks'",
42
+ "build:native:debug": "cd rust && cargo build",
43
+ "dev": "bun --hot src/mcp/cli.ts",
44
+ "start": "node dist/mcp/cli.js",
45
+ "test": "bun test",
46
+ "test:native": "cd rust && cargo test",
47
+ "typecheck": "tsc --noEmit",
48
+ "clean": "rm -rf dist && cd rust && cargo clean",
49
+ "prepublishOnly": "bun run build"
50
+ },
51
+ "keywords": [
52
+ "crm",
53
+ "mcp",
54
+ "model-context-protocol",
55
+ "lmdb",
56
+ "storage",
57
+ "typescript",
58
+ "rust",
59
+ "tantivy",
60
+ "search",
61
+ "native"
62
+ ],
63
+ "author": "ebowwa",
64
+ "license": "MIT",
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "https://github.com/ebowwa/codespaces.git",
68
+ "directory": "packages/src/coder/assignments/crm"
69
+ },
70
+ "dependencies": {
71
+ "@ebowwa/crm-primitives": "file:../../../primitives/crm",
72
+ "@modelcontextprotocol/sdk": "^1.0.4",
73
+ "lmdb": "^3.5.1",
74
+ "zod": "^3.24.2"
75
+ },
76
+ "devDependencies": {
77
+ "@types/bun": "latest",
78
+ "@types/node": "^25.2.0",
79
+ "typescript": "^5.7.3"
80
+ },
81
+ "peerDependencies": {
82
+ "typescript": ">=5.0.0"
83
+ },
84
+ "engines": {
85
+ "node": ">=18.0.0"
86
+ },
87
+ "napi": {
88
+ "name": "crm-native",
89
+ "triples": {
90
+ "defaults": true,
91
+ "additional": ["aarch64-apple-darwin", "x86_64-unknown-linux-gnu"]
92
+ }
93
+ },
94
+ "ownership": {
95
+ "domain": "crm",
96
+ "responsibilities": [
97
+ "crm-mcp",
98
+ "contact-management",
99
+ "deal-tracking",
100
+ "activity-logging",
101
+ "full-text-search",
102
+ "media-metadata"
103
+ ]
104
+ }
105
+ }
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Activity Commands
3
+ *
4
+ * CLI commands for managing activities 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
+ formatRelativeTime,
18
+ formatDateTime,
19
+ truncate,
20
+ output,
21
+ } from '../utils/output.js';
22
+ import {
23
+ prompt,
24
+ promptRequired,
25
+ promptMultiline,
26
+ confirm,
27
+ select,
28
+ previewAndConfirm,
29
+ Spinner,
30
+ } from '../utils/prompt.js';
31
+ import { CRMStorageClient } from '../../mcp/storage/client.js';
32
+ import type { Activity, ActivityType, CallOutcome, MeetingOutcome, CallMetadata, MeetingMetadata, EmailMetadata, TaskMetadata } from '../../core/types.js';
33
+
34
+ // Activity types
35
+ const ACTIVITY_TYPES: ActivityType[] = [
36
+ 'call',
37
+ 'email',
38
+ 'meeting',
39
+ 'task',
40
+ 'note',
41
+ 'sms',
42
+ 'video_call',
43
+ 'demo',
44
+ 'proposal_sent',
45
+ 'contract_sent',
46
+ 'follow_up',
47
+ 'social_media',
48
+ 'event',
49
+ 'other',
50
+ ];
51
+
52
+ // Call outcomes
53
+ const CALL_OUTCOMES: CallOutcome[] = [
54
+ 'connected',
55
+ 'voicemail',
56
+ 'no_answer',
57
+ 'busy',
58
+ 'wrong_number',
59
+ 'disconnected',
60
+ ];
61
+
62
+ // Meeting outcomes
63
+ const MEETING_OUTCOMES: MeetingOutcome[] = [
64
+ 'completed',
65
+ 'rescheduled',
66
+ 'cancelled',
67
+ 'no_show',
68
+ ];
69
+
70
+ // Activity type icons
71
+ const ACTIVITY_ICONS: Record<ActivityType, string> = {
72
+ call: '\u260E',
73
+ email: '\u2709',
74
+ meeting: '\u{1F3D7}',
75
+ task: '\u2713',
76
+ note: '\u{1F4DD}',
77
+ sms: '\u{1F4AC}',
78
+ video_call: '\u{1F4F7}',
79
+ demo: '\u{1F4BB}',
80
+ proposal_sent: '\u{1F4C4}',
81
+ contract_sent: '\u{1F4DD}',
82
+ follow_up: '\u23F0',
83
+ social_media: '\u{1F310}',
84
+ event: '\u{1F3AD}',
85
+ other: '\u2022',
86
+ };
87
+
88
+ // Storage client singleton
89
+ let _storage: CRMStorageClient | null = null;
90
+
91
+ /**
92
+ * Get storage client (lazy initialization)
93
+ */
94
+ async function getStorage(): Promise<CRMStorageClient> {
95
+ if (!_storage) {
96
+ _storage = new CRMStorageClient({
97
+ path: process.env.CRM_DB_PATH || './data/crm-cli',
98
+ mapSize: 256 * 1024 * 1024, // 256MB
99
+ maxDbs: 20,
100
+ });
101
+ await _storage.initialize();
102
+ }
103
+ return _storage;
104
+ }
105
+
106
+ /**
107
+ * Get all activities
108
+ */
109
+ async function getActivities(): Promise<Activity[]> {
110
+ const storage = await getStorage();
111
+ return storage.list('activities');
112
+ }
113
+
114
+ /**
115
+ * Get activity by ID
116
+ */
117
+ async function getActivity(id: string): Promise<Activity | null> {
118
+ const storage = await getStorage();
119
+ return storage.get('activities', id);
120
+ }
121
+
122
+ /**
123
+ * Create activity
124
+ */
125
+ async function createActivity(data: Partial<Activity>): Promise<Activity> {
126
+ const storage = await getStorage();
127
+ return storage.insert('activities', {
128
+ contactId: data.contactId,
129
+ dealId: data.dealId,
130
+ type: data.type || 'note',
131
+ title: data.title || '',
132
+ description: data.description || '',
133
+ timestamp: data.timestamp || new Date().toISOString(),
134
+ duration: data.duration,
135
+ metadata: data.metadata || {},
136
+ createdBy: data.createdBy,
137
+ tags: data.tags || [],
138
+ customFields: data.customFields || [],
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Delete activity
144
+ */
145
+ async function deleteActivity(id: string): Promise<boolean> {
146
+ const storage = await getStorage();
147
+ const existing = await storage.get('activities', id);
148
+ if (!existing) return false;
149
+ await storage.delete('activities', id);
150
+ return true;
151
+ }
152
+
153
+ /**
154
+ * Filter activities by contact
155
+ */
156
+ async function filterByContact(contactId: string): Promise<Activity[]> {
157
+ const activities = await getActivities();
158
+ return activities.filter((a) => a.contactId === contactId);
159
+ }
160
+
161
+ /**
162
+ * Filter activities by deal
163
+ */
164
+ async function filterByDeal(dealId: string): Promise<Activity[]> {
165
+ const activities = await getActivities();
166
+ return activities.filter((a) => a.dealId === dealId);
167
+ }
168
+
169
+ /**
170
+ * Register activity commands
171
+ */
172
+ export function registerActivityCommands(program: Command): void {
173
+ const activities = program.command('activities').alias('activity').description('Manage activities');
174
+
175
+ // List activities
176
+ activities
177
+ .command('list')
178
+ .description('List activities')
179
+ .option('-c, --contact <id>', 'Filter by contact ID')
180
+ .option('-d, --deal <id>', 'Filter by deal ID')
181
+ .option('-t, --type <type>', 'Filter by type')
182
+ .option('--json', 'Output as JSON')
183
+ .option('--limit <number>', 'Limit results', '20')
184
+ .action(async (options) => {
185
+ let activities = await getActivities();
186
+
187
+ if (options.contact) {
188
+ activities = await filterByContact(options.contact);
189
+ }
190
+
191
+ if (options.deal) {
192
+ activities = await filterByDeal(options.deal);
193
+ }
194
+
195
+ if (options.type) {
196
+ activities = activities.filter(
197
+ (a) => a.type.toLowerCase() === options.type.toLowerCase()
198
+ );
199
+ }
200
+
201
+ // Sort by timestamp descending
202
+ activities.sort(
203
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
204
+ );
205
+
206
+ const limit = parseInt(options.limit);
207
+ activities = activities.slice(0, limit);
208
+
209
+ if (options.json) {
210
+ printJSON(activities);
211
+ return;
212
+ }
213
+
214
+ if (activities.length === 0) {
215
+ printWarning('No activities found');
216
+ return;
217
+ }
218
+
219
+ printHeader(`Activities (${activities.length})`);
220
+
221
+ printTable(activities, [
222
+ {
223
+ key: 'type',
224
+ header: 'Type',
225
+ width: 6,
226
+ format: (v) => ACTIVITY_ICONS[v as ActivityType] || '\u2022',
227
+ },
228
+ { key: 'title', header: 'Title', width: 30, format: (v) => truncate(String(v), 30) },
229
+ {
230
+ key: 'type',
231
+ header: 'Activity',
232
+ width: 12,
233
+ format: (v) => String(v).replace('_', ' '),
234
+ },
235
+ {
236
+ key: 'timestamp',
237
+ header: 'When',
238
+ format: (v) => formatRelativeTime(String(v)),
239
+ },
240
+ {
241
+ key: 'contactId',
242
+ header: 'Contact',
243
+ width: 8,
244
+ format: (v) => (v ? truncate(String(v), 8) : '-'),
245
+ },
246
+ ]);
247
+ });
248
+
249
+ // Log activity (quick create)
250
+ activities
251
+ .command('log <contactId> <type> <description>')
252
+ .description('Log a new activity')
253
+ .option('-d, --deal <id>', 'Associated deal ID')
254
+ .option('--duration <minutes>', 'Duration in minutes')
255
+ .action(async (contactId, typeStr, description, options) => {
256
+ const type = typeStr.toLowerCase() as ActivityType;
257
+
258
+ if (!ACTIVITY_TYPES.includes(type)) {
259
+ printError(`Invalid activity type: ${typeStr}`);
260
+ console.log(`Valid types: ${ACTIVITY_TYPES.join(', ')}`);
261
+ process.exit(1);
262
+ }
263
+
264
+ const spinner = new Spinner('Logging activity...').start();
265
+ await new Promise((r) => setTimeout(r, 300));
266
+
267
+ const activity = await createActivity({
268
+ contactId,
269
+ dealId: options.deal,
270
+ type,
271
+ title: description.slice(0, 50),
272
+ description,
273
+ duration: options.duration ? parseInt(options.duration) * 60 : undefined,
274
+ });
275
+
276
+ spinner.succeed('Activity logged');
277
+ printSuccess(`${ACTIVITY_ICONS[type]} ${type}: ${truncate(description, 50)}`);
278
+ console.log(`ID: ${activity.id}`);
279
+ });
280
+
281
+ // Create activity (interactive)
282
+ activities
283
+ .command('create')
284
+ .description('Create a new activity interactively')
285
+ .option('-c, --contact <id>', 'Contact ID')
286
+ .option('-d, --deal <id>', 'Deal ID')
287
+ .action(async (options) => {
288
+ printHeader('Create Activity');
289
+
290
+ // Contact ID
291
+ const contactId =
292
+ options.contact || (await promptRequired('Contact ID'));
293
+
294
+ // Activity type
295
+ const type = await select('Activity Type', ACTIVITY_TYPES, 'note');
296
+
297
+ // Title
298
+ const title = await promptRequired('Title');
299
+
300
+ // Description
301
+ const description = await promptMultiline('Description');
302
+
303
+ // Deal ID
304
+ let dealId = options.deal;
305
+ if (!dealId) {
306
+ const hasDeal = await confirm('Associate with a deal?');
307
+ if (hasDeal) {
308
+ dealId = await promptRequired('Deal ID');
309
+ }
310
+ }
311
+
312
+ // Duration (for calls/meetings)
313
+ let duration: number | undefined;
314
+ if (['call', 'meeting', 'video_call', 'demo'].includes(type)) {
315
+ const durationStr = await prompt('Duration (minutes)');
316
+ if (durationStr) {
317
+ duration = parseInt(durationStr) * 60;
318
+ }
319
+ }
320
+
321
+ // Type-specific metadata
322
+ let metadata: Record<string, unknown> = {};
323
+
324
+ if (type === 'call') {
325
+ const outcome = await select('Call Outcome', CALL_OUTCOMES, 'connected');
326
+ metadata = {
327
+ outcome,
328
+ direction: 'outbound',
329
+ };
330
+ } else if (type === 'meeting') {
331
+ const outcome = await select('Meeting Outcome', MEETING_OUTCOMES, 'completed');
332
+ metadata = {
333
+ outcome,
334
+ };
335
+ }
336
+
337
+ // Preview
338
+ const confirmed = await previewAndConfirm('Activity Preview', {
339
+ Type: type,
340
+ Title: title,
341
+ Description: truncate(description, 50),
342
+ Contact: contactId,
343
+ Deal: dealId || '(none)',
344
+ Duration: duration ? `${Math.floor(duration / 60)} min` : '(none)',
345
+ });
346
+
347
+ if (!confirmed) {
348
+ printWarning('Activity creation cancelled');
349
+ return;
350
+ }
351
+
352
+ const spinner = new Spinner('Creating activity...').start();
353
+ await new Promise((r) => setTimeout(r, 500));
354
+
355
+ const activity = await createActivity({
356
+ contactId,
357
+ dealId,
358
+ type,
359
+ title,
360
+ description,
361
+ duration,
362
+ metadata: metadata as unknown as CallMetadata | MeetingMetadata | EmailMetadata | TaskMetadata | undefined,
363
+ });
364
+
365
+ spinner.succeed('Activity created successfully');
366
+ printSuccess(`Activity ID: ${activity.id}`);
367
+ });
368
+
369
+ // Get activity details
370
+ activities
371
+ .command('get <id>')
372
+ .description('Get activity details')
373
+ .option('--json', 'Output as JSON')
374
+ .action(async (id, options) => {
375
+ const activity = await getActivity(id);
376
+
377
+ if (!activity) {
378
+ printError(`Activity not found: ${id}`);
379
+ process.exit(1);
380
+ }
381
+
382
+ if (options.json) {
383
+ printJSON(activity);
384
+ return;
385
+ }
386
+
387
+ printHeader(`${ACTIVITY_ICONS[activity.type]} ${activity.title}`);
388
+
389
+ console.log(`${output.dim('ID:')} ${activity.id}`);
390
+ console.log(`${output.dim('Type:')} ${formatStatus(activity.type)}`);
391
+ console.log();
392
+
393
+ // Details
394
+ console.log(output.bold('Details'));
395
+ printDivider('-');
396
+ console.log(`${output.dim('Contact ID:')} ${activity.contactId || '(none)'}`);
397
+ console.log(`${output.dim('Deal ID:')} ${activity.dealId || '(none)'}`);
398
+ console.log(
399
+ `${output.dim('Timestamp:')} ${formatDateTime(activity.timestamp)}`
400
+ );
401
+
402
+ if (activity.duration) {
403
+ console.log(
404
+ `${output.dim('Duration:')} ${Math.floor(activity.duration / 60)} min`
405
+ );
406
+ }
407
+
408
+ console.log();
409
+
410
+ // Description
411
+ if (activity.description) {
412
+ console.log(output.bold('Description'));
413
+ printDivider('-');
414
+ console.log(activity.description);
415
+ console.log();
416
+ }
417
+
418
+ // Metadata
419
+ if (Object.keys(activity.metadata).length > 0) {
420
+ console.log(output.bold('Metadata'));
421
+ printDivider('-');
422
+ for (const [key, value] of Object.entries(activity.metadata)) {
423
+ console.log(`${output.dim(key)}: ${value}`);
424
+ }
425
+ console.log();
426
+ }
427
+
428
+ // Timestamps
429
+ console.log(output.bold('Timestamps'));
430
+ printDivider('-');
431
+ console.log(
432
+ `${output.dim('Created:')} ${new Date(activity.createdAt).toLocaleString()}`
433
+ );
434
+ console.log(
435
+ `${output.dim('Updated:')} ${new Date(activity.updatedAt).toLocaleString()}`
436
+ );
437
+ console.log();
438
+ });
439
+
440
+ // Delete activity
441
+ activities
442
+ .command('delete <id>')
443
+ .description('Delete an activity')
444
+ .option('-f, --force', 'Skip confirmation')
445
+ .action(async (id, options) => {
446
+ const activity = await getActivity(id);
447
+
448
+ if (!activity) {
449
+ printError(`Activity not found: ${id}`);
450
+ process.exit(1);
451
+ }
452
+
453
+ if (!options.force) {
454
+ console.log(`\nActivity: ${output.bold(activity.title)}`);
455
+ console.log(`Type: ${activity.type}`);
456
+ console.log(`Date: ${formatDateTime(activity.timestamp)}\n`);
457
+
458
+ const confirmed = await confirm(
459
+ 'Are you sure you want to delete this activity?'
460
+ );
461
+
462
+ if (!confirmed) {
463
+ printWarning('Deletion cancelled');
464
+ return;
465
+ }
466
+ }
467
+
468
+ const spinner = new Spinner('Deleting activity...').start();
469
+ await new Promise((r) => setTimeout(r, 300));
470
+
471
+ await deleteActivity(id);
472
+ spinner.succeed('Activity deleted successfully');
473
+ });
474
+
475
+ // Timeline (show activities in timeline view)
476
+ activities
477
+ .command('timeline')
478
+ .description('Show activity timeline')
479
+ .option('-c, --contact <id>', 'Filter by contact ID')
480
+ .option('-d, --deal <id>', 'Filter by deal ID')
481
+ .option('--days <number>', 'Days to show', '7')
482
+ .action(async (options) => {
483
+ let activities = await getActivities();
484
+
485
+ if (options.contact) {
486
+ activities = await filterByContact(options.contact);
487
+ }
488
+
489
+ if (options.deal) {
490
+ activities = await filterByDeal(options.deal);
491
+ }
492
+
493
+ // Filter by date range
494
+ const days = parseInt(options.days);
495
+ const cutoff = new Date();
496
+ cutoff.setDate(cutoff.getDate() - days);
497
+
498
+ activities = activities.filter(
499
+ (a) => new Date(a.timestamp) >= cutoff
500
+ );
501
+
502
+ // Sort by timestamp descending
503
+ activities.sort(
504
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
505
+ );
506
+
507
+ if (activities.length === 0) {
508
+ printWarning('No activities in the specified time range');
509
+ return;
510
+ }
511
+
512
+ printHeader(`Activity Timeline (Last ${days} Days)`);
513
+
514
+ let currentDate = '';
515
+
516
+ for (const activity of activities) {
517
+ const date = new Date(activity.timestamp).toLocaleDateString('en-US', {
518
+ weekday: 'long',
519
+ month: 'long',
520
+ day: 'numeric',
521
+ });
522
+
523
+ if (date !== currentDate) {
524
+ console.log();
525
+ console.log(output.bold(date));
526
+ printDivider('\u2500');
527
+ currentDate = date;
528
+ }
529
+
530
+ const time = new Date(activity.timestamp).toLocaleTimeString('en-US', {
531
+ hour: '2-digit',
532
+ minute: '2-digit',
533
+ });
534
+
535
+ const icon = ACTIVITY_ICONS[activity.type];
536
+ console.log(
537
+ ` ${output.dim(time)} ${icon} ${activity.title} ${output.dim(`(${activity.type})`)}`
538
+ );
539
+ }
540
+
541
+ console.log();
542
+ });
543
+ }