@ebowwa/crm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/cli/commands/activities.d.ts +11 -0
- package/dist/cli/commands/activities.d.ts.map +1 -0
- package/dist/cli/commands/activities.js +427 -0
- package/dist/cli/commands/activities.js.map +1 -0
- package/dist/cli/commands/contacts.d.ts +11 -0
- package/dist/cli/commands/contacts.d.ts.map +1 -0
- package/dist/cli/commands/contacts.js +458 -0
- package/dist/cli/commands/contacts.js.map +1 -0
- package/dist/cli/commands/deals.d.ts +11 -0
- package/dist/cli/commands/deals.d.ts.map +1 -0
- package/dist/cli/commands/deals.js +498 -0
- package/dist/cli/commands/deals.js.map +1 -0
- package/dist/cli/commands/media.d.ts +11 -0
- package/dist/cli/commands/media.d.ts.map +1 -0
- package/dist/cli/commands/media.js +417 -0
- package/dist/cli/commands/media.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +346 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl.d.ts +15 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +318 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/utils/config.d.ts +91 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +212 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/output.d.ts +136 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +323 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/cli/utils/prompt.d.ts +81 -0
- package/dist/cli/utils/prompt.d.ts.map +1 -0
- package/dist/cli/utils/prompt.js +341 -0
- package/dist/cli/utils/prompt.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +32 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemas.d.ts +3050 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +667 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/types.d.ts +597 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +18 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/storage/client.d.ts +109 -0
- package/dist/mcp/storage/client.d.ts.map +1 -0
- package/dist/mcp/storage/client.js +355 -0
- package/dist/mcp/storage/client.js.map +1 -0
- package/dist/mcp/storage/index.d.ts +7 -0
- package/dist/mcp/storage/index.d.ts.map +1 -0
- package/dist/mcp/storage/index.js +6 -0
- package/dist/mcp/storage/index.js.map +1 -0
- package/dist/mcp/storage/types.d.ts +44 -0
- package/dist/mcp/storage/types.d.ts.map +1 -0
- package/dist/mcp/storage/types.js +35 -0
- package/dist/mcp/storage/types.js.map +1 -0
- package/dist/mcp/tools/definitions.d.ts +16 -0
- package/dist/mcp/tools/definitions.d.ts.map +1 -0
- package/dist/mcp/tools/definitions.js +914 -0
- package/dist/mcp/tools/definitions.js.map +1 -0
- package/dist/mcp/tools/handlers.d.ts +50 -0
- package/dist/mcp/tools/handlers.d.ts.map +1 -0
- package/dist/mcp/tools/handlers.js +760 -0
- package/dist/mcp/tools/handlers.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +7 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +6 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/types.d.ts +314 -0
- package/dist/mcp/tools/types.d.ts.map +1 -0
- package/dist/mcp/tools/types.js +5 -0
- package/dist/mcp/tools/types.js.map +1 -0
- package/dist/mcp/transports/stdio.d.ts +27 -0
- package/dist/mcp/transports/stdio.d.ts.map +1 -0
- package/dist/mcp/transports/stdio.js +237 -0
- package/dist/mcp/transports/stdio.js.map +1 -0
- package/dist/telemetry/index.d.ts +58 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +109 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/logger.d.ts +116 -0
- package/dist/telemetry/logger.d.ts.map +1 -0
- package/dist/telemetry/logger.js +256 -0
- package/dist/telemetry/logger.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +115 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +292 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tracer.d.ts +227 -0
- package/dist/telemetry/tracer.d.ts.map +1 -0
- package/dist/telemetry/tracer.js +355 -0
- package/dist/telemetry/tracer.js.map +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +115 -0
- package/dist/web/app.js.map +1 -0
- package/dist/web/components/ContactList.d.ts +3 -0
- package/dist/web/components/ContactList.d.ts.map +1 -0
- package/dist/web/components/ContactList.js +262 -0
- package/dist/web/components/ContactList.js.map +1 -0
- package/dist/web/components/Dashboard.d.ts +3 -0
- package/dist/web/components/Dashboard.d.ts.map +1 -0
- package/dist/web/components/Dashboard.js +158 -0
- package/dist/web/components/Dashboard.js.map +1 -0
- package/dist/web/components/DealPipeline.d.ts +3 -0
- package/dist/web/components/DealPipeline.d.ts.map +1 -0
- package/dist/web/components/DealPipeline.js +306 -0
- package/dist/web/components/DealPipeline.js.map +1 -0
- package/dist/web/index.d.ts +2 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +269 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/types.d.ts +75 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +3 -0
- package/dist/web/types.js.map +1 -0
- package/native/index.d.ts +571 -0
- package/native/index.js +687 -0
- package/package.json +105 -0
- package/src/cli/commands/activities.ts +543 -0
- package/src/cli/commands/contacts.ts +563 -0
- package/src/cli/commands/deals.ts +637 -0
- package/src/cli/commands/media.ts +521 -0
- package/src/cli/commands/search.ts +426 -0
- package/src/cli/index.ts +203 -0
- package/src/cli/repl.ts +379 -0
- package/src/cli/utils/config.ts +299 -0
- package/src/cli/utils/output.ts +386 -0
- package/src/cli/utils/prompt.ts +444 -0
- package/src/cli.ts +11 -0
- package/src/core/index.ts +184 -0
- package/src/core/schemas.ts +770 -0
- package/src/core/types.ts +969 -0
- package/src/index.ts +8 -0
- package/src/mcp/index.ts +17 -0
- package/src/mcp/server.ts +26 -0
- package/src/mcp/storage/client.ts +408 -0
- package/src/mcp/storage/index.ts +7 -0
- package/src/mcp/storage/types.ts +72 -0
- package/src/mcp/tools/definitions.ts +961 -0
- package/src/mcp/tools/handlers.ts +805 -0
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/types.ts +390 -0
- package/src/mcp/transports/stdio.ts +225 -0
- package/src/telemetry/index.ts +131 -0
- package/src/telemetry/logger.ts +318 -0
- package/src/telemetry/metrics.ts +393 -0
- package/src/telemetry/tracer.ts +487 -0
- package/src/web/api/activities.ts +41 -0
- package/src/web/api/contacts.ts +114 -0
- package/src/web/api/deals.ts +108 -0
- package/src/web/api/media.ts +98 -0
- package/src/web/app.tsx +143 -0
- package/src/web/components/ActivityFeed.tsx +195 -0
- package/src/web/components/ContactList.tsx +340 -0
- package/src/web/components/Dashboard.tsx +214 -0
- package/src/web/components/DealPipeline.tsx +405 -0
- package/src/web/components/MediaGallery.tsx +334 -0
- package/src/web/index.html +14 -0
- package/src/web/index.ts +326 -0
- package/src/web/styles/main.css +180 -0
- package/src/web/types.ts +311 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for managing media files in the CRM.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import {
|
|
11
|
+
printSuccess,
|
|
12
|
+
printError,
|
|
13
|
+
printWarning,
|
|
14
|
+
printTable,
|
|
15
|
+
printJSON,
|
|
16
|
+
printHeader,
|
|
17
|
+
printDivider,
|
|
18
|
+
formatRelativeTime,
|
|
19
|
+
formatFileSize,
|
|
20
|
+
truncate,
|
|
21
|
+
output,
|
|
22
|
+
} from '../utils/output.js';
|
|
23
|
+
import {
|
|
24
|
+
prompt,
|
|
25
|
+
promptRequired,
|
|
26
|
+
confirm,
|
|
27
|
+
select,
|
|
28
|
+
Spinner,
|
|
29
|
+
} from '../utils/prompt.js';
|
|
30
|
+
import { CRMStorageClient } from '../../mcp/storage/client.js';
|
|
31
|
+
import type { Media, MediaType, MimeType } from '../../core/types.js';
|
|
32
|
+
|
|
33
|
+
// Media type icons
|
|
34
|
+
const MEDIA_ICONS: Record<MediaType, string> = {
|
|
35
|
+
image: '\u{1F4F7}',
|
|
36
|
+
video: '\u{1F3AC}',
|
|
37
|
+
audio: '\u{1F3B5}',
|
|
38
|
+
document: '\u{1F4C4}',
|
|
39
|
+
spreadsheet: '\u{1F4CA}',
|
|
40
|
+
presentation: '\u{1F4BB}',
|
|
41
|
+
pdf: '\u{1F4C4}',
|
|
42
|
+
archive: '\u{1F4E6}',
|
|
43
|
+
other: '\u{1F4CE}',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// MIME type to media type mapping
|
|
47
|
+
const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
|
|
48
|
+
'image/jpeg': 'image',
|
|
49
|
+
'image/png': 'image',
|
|
50
|
+
'image/gif': 'image',
|
|
51
|
+
'image/webp': 'image',
|
|
52
|
+
'image/svg+xml': 'image',
|
|
53
|
+
'video/mp4': 'video',
|
|
54
|
+
'video/webm': 'video',
|
|
55
|
+
'video/quicktime': 'video',
|
|
56
|
+
'audio/mpeg': 'audio',
|
|
57
|
+
'audio/wav': 'audio',
|
|
58
|
+
'audio/ogg': 'audio',
|
|
59
|
+
'application/pdf': 'pdf',
|
|
60
|
+
'application/msword': 'document',
|
|
61
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'document',
|
|
62
|
+
'application/vnd.ms-excel': 'spreadsheet',
|
|
63
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'spreadsheet',
|
|
64
|
+
'application/vnd.ms-powerpoint': 'presentation',
|
|
65
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'presentation',
|
|
66
|
+
'application/zip': 'archive',
|
|
67
|
+
'application/x-rar-compressed': 'archive',
|
|
68
|
+
'application/x-7z-compressed': 'archive',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Storage client singleton
|
|
72
|
+
let _storage: CRMStorageClient | null = null;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get storage client (lazy initialization)
|
|
76
|
+
*/
|
|
77
|
+
async function getStorage(): Promise<CRMStorageClient> {
|
|
78
|
+
if (!_storage) {
|
|
79
|
+
_storage = new CRMStorageClient({
|
|
80
|
+
path: process.env.CRM_DB_PATH || './data/crm-cli',
|
|
81
|
+
mapSize: 256 * 1024 * 1024, // 256MB
|
|
82
|
+
maxDbs: 20,
|
|
83
|
+
});
|
|
84
|
+
await _storage.initialize();
|
|
85
|
+
}
|
|
86
|
+
return _storage;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get media type from MIME type
|
|
91
|
+
*/
|
|
92
|
+
function getMediaType(mimeType: string): MediaType {
|
|
93
|
+
return MIME_TO_MEDIA_TYPE[mimeType] || 'other';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all media
|
|
98
|
+
*/
|
|
99
|
+
async function getMedia(): Promise<Media[]> {
|
|
100
|
+
const storage = await getStorage();
|
|
101
|
+
return storage.list('media');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get media by ID
|
|
106
|
+
*/
|
|
107
|
+
async function getMediaById(id: string): Promise<Media | null> {
|
|
108
|
+
const storage = await getStorage();
|
|
109
|
+
return storage.get('media', id);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create media entry
|
|
114
|
+
*/
|
|
115
|
+
async function createMedia(data: Partial<Media>): Promise<Media> {
|
|
116
|
+
const storage = await getStorage();
|
|
117
|
+
return storage.insert('media', {
|
|
118
|
+
entityType: data.entityType || 'contact',
|
|
119
|
+
entityId: data.entityId || '',
|
|
120
|
+
type: data.type || 'other',
|
|
121
|
+
filename: data.filename || 'unknown',
|
|
122
|
+
mimeType: data.mimeType || 'application/octet-stream',
|
|
123
|
+
size: data.size || 0,
|
|
124
|
+
url: data.url || '',
|
|
125
|
+
thumbnailUrl: data.thumbnailUrl,
|
|
126
|
+
metadata: data.metadata || {},
|
|
127
|
+
altText: data.altText,
|
|
128
|
+
caption: data.caption,
|
|
129
|
+
isPublic: data.isPublic ?? false,
|
|
130
|
+
downloadCount: data.downloadCount || 0,
|
|
131
|
+
uploadedBy: data.uploadedBy,
|
|
132
|
+
expiresAt: data.expiresAt,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Delete media
|
|
138
|
+
*/
|
|
139
|
+
async function deleteMedia(id: string): Promise<boolean> {
|
|
140
|
+
const storage = await getStorage();
|
|
141
|
+
const existing = await storage.get('media', id);
|
|
142
|
+
if (!existing) return false;
|
|
143
|
+
await storage.delete('media', id);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Increment download count
|
|
149
|
+
*/
|
|
150
|
+
async function incrementDownloadCount(id: string): Promise<void> {
|
|
151
|
+
const storage = await getStorage();
|
|
152
|
+
const media = storage.get<Media>('media', id);
|
|
153
|
+
if (media) {
|
|
154
|
+
const updates: Partial<Media> = {
|
|
155
|
+
downloadCount: (media.downloadCount || 0) + 1,
|
|
156
|
+
};
|
|
157
|
+
await storage.update('media', id, updates);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Filter media by contact
|
|
163
|
+
*/
|
|
164
|
+
async function filterByContact(contactId: string): Promise<Media[]> {
|
|
165
|
+
const media = await getMedia();
|
|
166
|
+
return media.filter(
|
|
167
|
+
(m) => m.entityType === 'contact' && m.entityId === contactId
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Filter media by deal
|
|
173
|
+
*/
|
|
174
|
+
async function filterByDeal(dealId: string): Promise<Media[]> {
|
|
175
|
+
const media = await getMedia();
|
|
176
|
+
return media.filter(
|
|
177
|
+
(m) => m.entityType === 'deal' && m.entityId === dealId
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generate UUID
|
|
183
|
+
*/
|
|
184
|
+
function generateId(): string {
|
|
185
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
186
|
+
const r = (Math.random() * 16) | 0;
|
|
187
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
188
|
+
return v.toString(16);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Register media commands
|
|
194
|
+
*/
|
|
195
|
+
export function registerMediaCommands(program: Command): void {
|
|
196
|
+
const media = program.command('media').description('Manage media files');
|
|
197
|
+
|
|
198
|
+
// List media
|
|
199
|
+
media
|
|
200
|
+
.command('list')
|
|
201
|
+
.description('List media files')
|
|
202
|
+
.option('-c, --contact <id>', 'Filter by contact ID')
|
|
203
|
+
.option('-d, --deal <id>', 'Filter by deal ID')
|
|
204
|
+
.option('-t, --type <type>', 'Filter by media type')
|
|
205
|
+
.option('--json', 'Output as JSON')
|
|
206
|
+
.option('--limit <number>', 'Limit results', '20')
|
|
207
|
+
.action(async (options) => {
|
|
208
|
+
let mediaFiles = await getMedia();
|
|
209
|
+
|
|
210
|
+
if (options.contact) {
|
|
211
|
+
mediaFiles = await filterByContact(options.contact);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (options.deal) {
|
|
215
|
+
mediaFiles = await filterByDeal(options.deal);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (options.type) {
|
|
219
|
+
mediaFiles = mediaFiles.filter(
|
|
220
|
+
(m) => m.type.toLowerCase() === options.type.toLowerCase()
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Sort by date descending
|
|
225
|
+
mediaFiles.sort(
|
|
226
|
+
(a, b) =>
|
|
227
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const limit = parseInt(options.limit);
|
|
231
|
+
mediaFiles = mediaFiles.slice(0, limit);
|
|
232
|
+
|
|
233
|
+
if (options.json) {
|
|
234
|
+
printJSON(mediaFiles);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (mediaFiles.length === 0) {
|
|
239
|
+
printWarning('No media files found');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
printHeader(`Media Files (${mediaFiles.length})`);
|
|
244
|
+
|
|
245
|
+
printTable(mediaFiles, [
|
|
246
|
+
{
|
|
247
|
+
key: 'type',
|
|
248
|
+
header: '',
|
|
249
|
+
width: 2,
|
|
250
|
+
format: (v) => MEDIA_ICONS[v as MediaType] || '\u{1F4CE}',
|
|
251
|
+
},
|
|
252
|
+
{ key: 'filename', header: 'Filename', width: 30, format: (v) => truncate(String(v), 30) },
|
|
253
|
+
{
|
|
254
|
+
key: 'size',
|
|
255
|
+
header: 'Size',
|
|
256
|
+
width: 10,
|
|
257
|
+
format: (v) => formatFileSize(Number(v)),
|
|
258
|
+
},
|
|
259
|
+
{ key: 'type', header: 'Type', width: 10 },
|
|
260
|
+
{
|
|
261
|
+
key: 'createdAt',
|
|
262
|
+
header: 'Uploaded',
|
|
263
|
+
format: (v) => formatRelativeTime(String(v)),
|
|
264
|
+
},
|
|
265
|
+
]);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Upload media
|
|
269
|
+
media
|
|
270
|
+
.command('upload <file>')
|
|
271
|
+
.description('Upload a media file')
|
|
272
|
+
.option('-c, --contact <id>', 'Associate with contact')
|
|
273
|
+
.option('-d, --deal <id>', 'Associate with deal')
|
|
274
|
+
.option('--caption <text>', 'Caption for the media')
|
|
275
|
+
.option('--public', 'Make media public')
|
|
276
|
+
.action(async (filePath, options) => {
|
|
277
|
+
// Check file exists
|
|
278
|
+
if (!fs.existsSync(filePath)) {
|
|
279
|
+
printError(`File not found: ${filePath}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Get file stats
|
|
284
|
+
const stats = fs.statSync(filePath);
|
|
285
|
+
const filename = path.basename(filePath);
|
|
286
|
+
|
|
287
|
+
// Determine entity
|
|
288
|
+
let entityType: 'contact' | 'deal' = 'contact';
|
|
289
|
+
let entityId: string;
|
|
290
|
+
|
|
291
|
+
if (options.contact) {
|
|
292
|
+
entityType = 'contact';
|
|
293
|
+
entityId = options.contact;
|
|
294
|
+
} else if (options.deal) {
|
|
295
|
+
entityType = 'deal';
|
|
296
|
+
entityId = options.deal;
|
|
297
|
+
} else {
|
|
298
|
+
entityId = await promptRequired('Contact or Deal ID');
|
|
299
|
+
entityType = (await select('Entity Type', ['contact', 'deal'])) as
|
|
300
|
+
| 'contact'
|
|
301
|
+
| 'deal';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Detect MIME type (simplified)
|
|
305
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
306
|
+
const mimeMap: Record<string, string> = {
|
|
307
|
+
'.jpg': 'image/jpeg',
|
|
308
|
+
'.jpeg': 'image/jpeg',
|
|
309
|
+
'.png': 'image/png',
|
|
310
|
+
'.gif': 'image/gif',
|
|
311
|
+
'.webp': 'image/webp',
|
|
312
|
+
'.svg': 'image/svg+xml',
|
|
313
|
+
'.mp4': 'video/mp4',
|
|
314
|
+
'.webm': 'video/webm',
|
|
315
|
+
'.mov': 'video/quicktime',
|
|
316
|
+
'.mp3': 'audio/mpeg',
|
|
317
|
+
'.wav': 'audio/wav',
|
|
318
|
+
'.pdf': 'application/pdf',
|
|
319
|
+
'.doc': 'application/msword',
|
|
320
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
321
|
+
'.xls': 'application/vnd.ms-excel',
|
|
322
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
323
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
324
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
325
|
+
'.zip': 'application/zip',
|
|
326
|
+
'.txt': 'text/plain',
|
|
327
|
+
'.csv': 'text/csv',
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const mimeType: MimeType = mimeMap[ext] || 'application/octet-stream';
|
|
331
|
+
const mediaType = getMediaType(mimeType);
|
|
332
|
+
|
|
333
|
+
// Caption
|
|
334
|
+
const caption = options.caption || (await prompt('Caption (optional)'));
|
|
335
|
+
|
|
336
|
+
// Preview
|
|
337
|
+
console.log();
|
|
338
|
+
console.log(`${MEDIA_ICONS[mediaType]} ${output.bold(filename)}`);
|
|
339
|
+
console.log(`${output.dim('Size:')} ${formatFileSize(stats.size)}`);
|
|
340
|
+
console.log(`${output.dim('Type:')} ${mediaType}`);
|
|
341
|
+
console.log(`${output.dim('MIME:')} ${mimeType}`);
|
|
342
|
+
console.log();
|
|
343
|
+
|
|
344
|
+
const confirmed = await confirm('Upload this file?');
|
|
345
|
+
if (!confirmed) {
|
|
346
|
+
printWarning('Upload cancelled');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Simulate upload
|
|
351
|
+
const spinner = new Spinner('Uploading...').start();
|
|
352
|
+
await new Promise((r) => setTimeout(r, 1000 + Math.random() * 1000));
|
|
353
|
+
|
|
354
|
+
const mediaEntry = await createMedia({
|
|
355
|
+
entityType,
|
|
356
|
+
entityId,
|
|
357
|
+
type: mediaType,
|
|
358
|
+
filename,
|
|
359
|
+
mimeType,
|
|
360
|
+
size: stats.size,
|
|
361
|
+
url: `/uploads/${generateId()}/${filename}`,
|
|
362
|
+
caption,
|
|
363
|
+
isPublic: options.public || false,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
spinner.succeed('Upload complete');
|
|
367
|
+
printSuccess(`Media ID: ${mediaEntry.id}`);
|
|
368
|
+
console.log();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Get media details
|
|
372
|
+
media
|
|
373
|
+
.command('get <id>')
|
|
374
|
+
.description('Get media details')
|
|
375
|
+
.option('--json', 'Output as JSON')
|
|
376
|
+
.action(async (id, options) => {
|
|
377
|
+
const mediaFile = await getMediaById(id);
|
|
378
|
+
|
|
379
|
+
if (!mediaFile) {
|
|
380
|
+
printError(`Media not found: ${id}`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (options.json) {
|
|
385
|
+
printJSON(mediaFile);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const icon = MEDIA_ICONS[mediaFile.type];
|
|
390
|
+
printHeader(`${icon} ${mediaFile.filename}`);
|
|
391
|
+
|
|
392
|
+
console.log(`${output.dim('ID:')} ${mediaFile.id}`);
|
|
393
|
+
console.log(`${output.dim('Type:')} ${mediaFile.type}`);
|
|
394
|
+
console.log();
|
|
395
|
+
|
|
396
|
+
// File Info
|
|
397
|
+
console.log(output.bold('File Information'));
|
|
398
|
+
printDivider('-');
|
|
399
|
+
console.log(`${output.dim('Filename:')} ${mediaFile.filename}`);
|
|
400
|
+
console.log(`${output.dim('MIME Type:')} ${mediaFile.mimeType}`);
|
|
401
|
+
console.log(`${output.dim('Size:')} ${formatFileSize(mediaFile.size)}`);
|
|
402
|
+
console.log(`${output.dim('URL:')} ${mediaFile.url}`);
|
|
403
|
+
|
|
404
|
+
if (mediaFile.thumbnailUrl) {
|
|
405
|
+
console.log(`${output.dim('Thumbnail:')} ${mediaFile.thumbnailUrl}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
console.log();
|
|
409
|
+
|
|
410
|
+
// Association
|
|
411
|
+
console.log(output.bold('Association'));
|
|
412
|
+
printDivider('-');
|
|
413
|
+
console.log(`${output.dim('Entity Type:')} ${mediaFile.entityType}`);
|
|
414
|
+
console.log(`${output.dim('Entity ID:')} ${mediaFile.entityId}`);
|
|
415
|
+
|
|
416
|
+
console.log();
|
|
417
|
+
|
|
418
|
+
// Additional Info
|
|
419
|
+
console.log(output.bold('Additional Info'));
|
|
420
|
+
printDivider('-');
|
|
421
|
+
console.log(`${output.dim('Public:')} ${mediaFile.isPublic ? 'Yes' : 'No'}`);
|
|
422
|
+
console.log(`${output.dim('Downloads:')} ${mediaFile.downloadCount}`);
|
|
423
|
+
|
|
424
|
+
if (mediaFile.caption) {
|
|
425
|
+
console.log(`${output.dim('Caption:')} ${mediaFile.caption}`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (mediaFile.altText) {
|
|
429
|
+
console.log(`${output.dim('Alt Text:')} ${mediaFile.altText}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
console.log();
|
|
433
|
+
|
|
434
|
+
// Timestamps
|
|
435
|
+
console.log(output.bold('Timestamps'));
|
|
436
|
+
printDivider('-');
|
|
437
|
+
console.log(
|
|
438
|
+
`${output.dim('Uploaded:')} ${new Date(mediaFile.createdAt).toLocaleString()}`
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
if (mediaFile.expiresAt) {
|
|
442
|
+
console.log(
|
|
443
|
+
`${output.dim('Expires:')} ${new Date(mediaFile.expiresAt).toLocaleString()}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Delete media
|
|
451
|
+
media
|
|
452
|
+
.command('delete <id>')
|
|
453
|
+
.description('Delete a media file')
|
|
454
|
+
.option('-f, --force', 'Skip confirmation')
|
|
455
|
+
.action(async (id, options) => {
|
|
456
|
+
const mediaFile = await getMediaById(id);
|
|
457
|
+
|
|
458
|
+
if (!mediaFile) {
|
|
459
|
+
printError(`Media not found: ${id}`);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (!options.force) {
|
|
464
|
+
const icon = MEDIA_ICONS[mediaFile.type];
|
|
465
|
+
console.log(`\n${icon} ${output.bold(mediaFile.filename)}`);
|
|
466
|
+
console.log(`Size: ${formatFileSize(mediaFile.size)}`);
|
|
467
|
+
console.log(`Type: ${mediaFile.type}\n`);
|
|
468
|
+
|
|
469
|
+
const confirmed = await confirm(
|
|
470
|
+
'Are you sure you want to delete this media file?'
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
if (!confirmed) {
|
|
474
|
+
printWarning('Deletion cancelled');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const spinner = new Spinner('Deleting media...').start();
|
|
480
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
481
|
+
|
|
482
|
+
await deleteMedia(id);
|
|
483
|
+
spinner.succeed('Media deleted successfully');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Download media
|
|
487
|
+
media
|
|
488
|
+
.command('download <id>')
|
|
489
|
+
.description('Download a media file')
|
|
490
|
+
.option('-o, --output <path>', 'Output path')
|
|
491
|
+
.action(async (id, options) => {
|
|
492
|
+
const mediaFile = await getMediaById(id);
|
|
493
|
+
|
|
494
|
+
if (!mediaFile) {
|
|
495
|
+
printError(`Media not found: ${id}`);
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const outputPath =
|
|
500
|
+
options.output || path.join(process.cwd(), mediaFile.filename);
|
|
501
|
+
|
|
502
|
+
console.log(`\n${MEDIA_ICONS[mediaFile.type]} ${mediaFile.filename}`);
|
|
503
|
+
console.log(`${output.dim('Source:')} ${mediaFile.url}`);
|
|
504
|
+
console.log(`${output.dim('Target:')} ${outputPath}\n`);
|
|
505
|
+
|
|
506
|
+
const spinner = new Spinner('Downloading...').start();
|
|
507
|
+
|
|
508
|
+
// Simulate download
|
|
509
|
+
await new Promise((r) => setTimeout(r, 500 + Math.random() * 1000));
|
|
510
|
+
|
|
511
|
+
// In real implementation, would actually download the file
|
|
512
|
+
// For now, just create a placeholder
|
|
513
|
+
fs.writeFileSync(outputPath, `[Placeholder for ${mediaFile.filename}]`);
|
|
514
|
+
|
|
515
|
+
// Increment download count
|
|
516
|
+
await incrementDownloadCount(id);
|
|
517
|
+
|
|
518
|
+
spinner.succeed('Download complete');
|
|
519
|
+
printSuccess(`Saved to: ${outputPath}`);
|
|
520
|
+
});
|
|
521
|
+
}
|