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