@startanaicompany/crm 2.14.0 → 2.16.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/index.js +302 -35
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -580,21 +580,39 @@ leadsCmd
|
|
|
580
580
|
});
|
|
581
581
|
|
|
582
582
|
leadsCmd
|
|
583
|
-
.command('score')
|
|
584
|
-
.description('
|
|
585
|
-
.
|
|
586
|
-
.
|
|
587
|
-
.
|
|
588
|
-
.action(async (opts) => {
|
|
583
|
+
.command('score <lead-id>')
|
|
584
|
+
.description('Get or set lead score. Without --set shows current score + history. Score conventions: 0-19 Cold, 20-39 Warm, 40-59 Developing, 60-79 Qualified, 80-100 Hot (Sprint 53 T1)')
|
|
585
|
+
.option('--set <n>', 'Set score to this value (0-100 integer)')
|
|
586
|
+
.option('--reason <text>', 'Score reason/explanation (used with --set)')
|
|
587
|
+
.action(async (leadId, opts) => {
|
|
589
588
|
const globalOpts = program.opts();
|
|
590
589
|
const client = getClient(globalOpts);
|
|
591
|
-
const body = {
|
|
592
|
-
score: parseInt(opts.score),
|
|
593
|
-
...(opts.reason && { score_reason: opts.reason })
|
|
594
|
-
};
|
|
595
590
|
try {
|
|
596
|
-
|
|
597
|
-
|
|
591
|
+
if (opts.set !== undefined) {
|
|
592
|
+
// SET mode: PATCH /leads/:id/score
|
|
593
|
+
const body = { score: parseInt(opts.set) };
|
|
594
|
+
if (opts.reason) body.score_reason = opts.reason;
|
|
595
|
+
const res = await client.patch(`/leads/${leadId}/score`, body);
|
|
596
|
+
const d = res.data.data;
|
|
597
|
+
console.log(`Score set to ${d.score}${d.score_reason ? ' — ' + d.score_reason : ''}`);
|
|
598
|
+
printJSON(res.data);
|
|
599
|
+
} else {
|
|
600
|
+
// GET mode: show current score + history
|
|
601
|
+
const [leadRes, histRes] = await Promise.all([
|
|
602
|
+
client.get(`/leads/${leadId}`),
|
|
603
|
+
client.get(`/leads/${leadId}/score-history`),
|
|
604
|
+
]);
|
|
605
|
+
const lead = leadRes.data.data;
|
|
606
|
+
const history = histRes.data.data || [];
|
|
607
|
+
const label = lead.score >= 80 ? 'Hot' : lead.score >= 60 ? 'Qualified' : lead.score >= 40 ? 'Developing' : lead.score >= 20 ? 'Warm' : 'Cold';
|
|
608
|
+
console.log(`\nLead: ${lead.name} <${lead.email}>`);
|
|
609
|
+
console.log(`Current Score: ${lead.score ?? 'N/A'} (${label})`);
|
|
610
|
+
if (lead.score_reason) console.log(`Reason: ${lead.score_reason}`);
|
|
611
|
+
if (history.length > 0) {
|
|
612
|
+
console.log(`\nScore History (${history.length} entries):`);
|
|
613
|
+
history.slice(0, 10).forEach(h => console.log(` ${new Date(h.changed_at).toLocaleString()} — ${h.previous_score ?? '?'} → ${h.new_score} (${h.changed_by || 'system'})`));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
598
616
|
} catch (err) {
|
|
599
617
|
handleError(err);
|
|
600
618
|
}
|
|
@@ -708,13 +726,16 @@ leadsCmd
|
|
|
708
726
|
});
|
|
709
727
|
|
|
710
728
|
leadsCmd
|
|
711
|
-
.command('assign <id>
|
|
712
|
-
.description('Assign a lead to an agent (fires lead.assigned webhook) — S18-2')
|
|
713
|
-
.
|
|
729
|
+
.command('assign <id> [agent]')
|
|
730
|
+
.description('Assign a lead to an agent (fires lead.assigned webhook) — S18-2. Use positional [agent] or --to <agent>.')
|
|
731
|
+
.option('--to <agent>', 'Agent/user to assign to (alias for positional agent arg)')
|
|
732
|
+
.action(async (id, agent, opts) => {
|
|
714
733
|
const globalOpts = program.opts();
|
|
715
734
|
const client = getClient(globalOpts);
|
|
735
|
+
const assignTo = agent || opts.to;
|
|
736
|
+
if (!assignTo) { console.error('Error: specify agent as positional arg or --to <agent>'); process.exit(1); }
|
|
716
737
|
try {
|
|
717
|
-
const res = await client.patch(`/leads/${id}/assign`, { assigned_to:
|
|
738
|
+
const res = await client.patch(`/leads/${id}/assign`, { assigned_to: assignTo });
|
|
718
739
|
console.log(`Lead assigned to: ${res.data.data.assigned_to}`);
|
|
719
740
|
printJSON(res.data);
|
|
720
741
|
} catch (err) {
|
|
@@ -770,27 +791,67 @@ leadsCmd
|
|
|
770
791
|
}
|
|
771
792
|
});
|
|
772
793
|
|
|
773
|
-
// leads
|
|
794
|
+
// Sprint 53 T1: leads stage — get or set pipeline stage
|
|
774
795
|
leadsCmd
|
|
775
|
-
.command('
|
|
776
|
-
.description('
|
|
777
|
-
.
|
|
796
|
+
.command('stage <lead-id>')
|
|
797
|
+
.description('Get current pipeline stage or move lead to a new stage (Sprint 53 T1)')
|
|
798
|
+
.option('--to <stage>', 'Move lead to this pipeline stage name')
|
|
799
|
+
.action(async (leadId, opts) => {
|
|
778
800
|
const globalOpts = program.opts();
|
|
779
801
|
const client = getClient(globalOpts);
|
|
780
802
|
try {
|
|
781
|
-
|
|
803
|
+
if (opts.to) {
|
|
804
|
+
// SET mode: PATCH /leads/:id with pipeline_stage
|
|
805
|
+
const res = await client.patch(`/leads/${leadId}`, { pipeline_stage: opts.to });
|
|
806
|
+
const d = res.data.data;
|
|
807
|
+
console.log(`Stage set to: ${d.pipeline_stage}`);
|
|
808
|
+
printJSON(res.data);
|
|
809
|
+
} else {
|
|
810
|
+
// GET mode: show current stage
|
|
811
|
+
const leadRes = await client.get(`/leads/${leadId}`);
|
|
812
|
+
const lead = leadRes.data.data;
|
|
813
|
+
console.log(`\nLead: ${lead.name} <${lead.email}>`);
|
|
814
|
+
console.log(`Current Stage: ${lead.pipeline_stage || 'N/A'}`);
|
|
815
|
+
if (lead.pipeline_id) {
|
|
816
|
+
try {
|
|
817
|
+
const pipeRes = await client.get(`/pipelines/${lead.pipeline_id}/stages`);
|
|
818
|
+
const stages = pipeRes.data.data || [];
|
|
819
|
+
if (stages.length > 0) {
|
|
820
|
+
console.log(`Available stages: ${stages.map(s => s.name).join(' → ')}`);
|
|
821
|
+
}
|
|
822
|
+
} catch (_) { /* pipeline stages not critical */ }
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
} catch (err) {
|
|
826
|
+
handleError(err);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// leads search <term> — S19-1 / Sprint 53 T3: added --q flag alias
|
|
831
|
+
leadsCmd
|
|
832
|
+
.command('search [term]')
|
|
833
|
+
.description('Search leads by name, email, company, or notes. Use positional term or --q flag.')
|
|
834
|
+
.option('--q <keyword>', 'Search keyword (alias for positional term)')
|
|
835
|
+
.option('--fields <fields>', 'Comma-separated fields to display (e.g. name,email,company)')
|
|
836
|
+
.option('--limit <n>', 'Max results', '50')
|
|
837
|
+
.action(async (term, opts) => {
|
|
838
|
+
const globalOpts = program.opts();
|
|
839
|
+
const client = getClient(globalOpts);
|
|
840
|
+
const query = term || opts.q;
|
|
841
|
+
if (!query) { console.error('Error: provide a search term (positional or --q)'); process.exit(1); }
|
|
842
|
+
try {
|
|
843
|
+
const res = await client.get('/leads', { params: { q: query, per_page: parseInt(opts.limit) } });
|
|
782
844
|
const leads = res.data.data || [];
|
|
783
845
|
if (leads.length === 0) {
|
|
784
|
-
console.log('No leads found matching: ' +
|
|
846
|
+
console.log('No leads found matching: ' + query);
|
|
785
847
|
return;
|
|
786
848
|
}
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}));
|
|
849
|
+
const fields = opts.fields ? opts.fields.split(',').map(f => f.trim()) : ['id', 'name', 'email', 'company', 'status'];
|
|
850
|
+
const rows = leads.map(l => {
|
|
851
|
+
const row = {};
|
|
852
|
+
fields.forEach(f => { row[f] = l[f] ?? '—'; });
|
|
853
|
+
return row;
|
|
854
|
+
});
|
|
794
855
|
console.table(rows);
|
|
795
856
|
} catch (err) {
|
|
796
857
|
handleError(err);
|
|
@@ -1120,6 +1181,68 @@ leadsCmd
|
|
|
1120
1181
|
} catch (err) { handleError(err); }
|
|
1121
1182
|
});
|
|
1122
1183
|
|
|
1184
|
+
// Sprint 53 T3: leads due — follow-up due/overdue list
|
|
1185
|
+
leadsCmd
|
|
1186
|
+
.command('due')
|
|
1187
|
+
.description('List leads with upcoming or overdue follow-ups (Sprint 53 T3)')
|
|
1188
|
+
.option('--today', 'Leads with follow-up scheduled for today')
|
|
1189
|
+
.option('--overdue', 'Leads with overdue follow-ups (follow_up_at < now)')
|
|
1190
|
+
.option('--assigned-to-me', 'Filter to leads assigned to the current API key name')
|
|
1191
|
+
.option('--limit <n>', 'Max results', '50')
|
|
1192
|
+
.action(async (opts) => {
|
|
1193
|
+
const globalOpts = program.opts();
|
|
1194
|
+
const client = getClient(globalOpts);
|
|
1195
|
+
try {
|
|
1196
|
+
const params = { per_page: parseInt(opts.limit) };
|
|
1197
|
+
if (opts.overdue) params.follow_up_overdue = 'true';
|
|
1198
|
+
if (opts.today) params.follow_up_today = 'true';
|
|
1199
|
+
if (!opts.overdue && !opts.today) {
|
|
1200
|
+
// default: both overdue + today
|
|
1201
|
+
params.follow_up_overdue = 'true';
|
|
1202
|
+
}
|
|
1203
|
+
const res = await client.get('/leads', { params });
|
|
1204
|
+
const leads = res.data.data || [];
|
|
1205
|
+
if (leads.length === 0) { console.log('No leads with due/overdue follow-ups.'); return; }
|
|
1206
|
+
leads.forEach(l => {
|
|
1207
|
+
const fu = l.follow_up_at ? new Date(l.follow_up_at).toLocaleString() : 'N/A';
|
|
1208
|
+
const overdue = l.follow_up_at && new Date(l.follow_up_at) < new Date() ? ' ⚠️ OVERDUE' : '';
|
|
1209
|
+
console.log(`[${l.id}] ${l.name} <${l.email}> — follow_up: ${fu}${overdue} (${l.assigned_to || 'unassigned'})`);
|
|
1210
|
+
});
|
|
1211
|
+
} catch (err) { handleError(err); }
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
// Sprint 52 T2: leads escalate + leads unassign
|
|
1215
|
+
leadsCmd
|
|
1216
|
+
.command('escalate <lead-id>')
|
|
1217
|
+
.description('Manually escalate a lead — sets escalated=true and fires lead.escalated webhook (Sprint 52 T2)')
|
|
1218
|
+
.option('--reason <text>', 'Reason for escalation')
|
|
1219
|
+
.option('--to <agent>', 'Reassign to this agent/user on escalation')
|
|
1220
|
+
.action(async (leadId, opts) => {
|
|
1221
|
+
const globalOpts = program.opts();
|
|
1222
|
+
const client = getClient(globalOpts);
|
|
1223
|
+
try {
|
|
1224
|
+
const body = {};
|
|
1225
|
+
if (opts.reason) body.reason = opts.reason;
|
|
1226
|
+
if (opts.to) body.to = opts.to;
|
|
1227
|
+
const res = await client.post(`/leads/${leadId}/escalate`, body);
|
|
1228
|
+
console.log(`Lead ${leadId} escalated.`);
|
|
1229
|
+
printJSON(res.data);
|
|
1230
|
+
} catch (err) { handleError(err); }
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
leadsCmd
|
|
1234
|
+
.command('unassign <lead-id>')
|
|
1235
|
+
.description('Remove assignment from a lead (sets assigned_to to null) (Sprint 52 T2)')
|
|
1236
|
+
.action(async (leadId) => {
|
|
1237
|
+
const globalOpts = program.opts();
|
|
1238
|
+
const client = getClient(globalOpts);
|
|
1239
|
+
try {
|
|
1240
|
+
const res = await client.patch(`/leads/${leadId}`, { assigned_to: null });
|
|
1241
|
+
console.log(`Lead ${leadId} unassigned.`);
|
|
1242
|
+
printJSON(res.data);
|
|
1243
|
+
} catch (err) { handleError(err); }
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1123
1246
|
// Sprint 51 T3: leads export / leads import
|
|
1124
1247
|
leadsCmd
|
|
1125
1248
|
.command('export')
|
|
@@ -1187,11 +1310,14 @@ const contactsCmd = program
|
|
|
1187
1310
|
contactsCmd
|
|
1188
1311
|
.command('create')
|
|
1189
1312
|
.description('Create a new contact')
|
|
1190
|
-
.
|
|
1313
|
+
.option('--name <name>', 'Full name (alias for --first-name)')
|
|
1314
|
+
.option('--first-name <name>', 'First name')
|
|
1191
1315
|
.option('--last-name <name>', 'Last name')
|
|
1192
|
-
.
|
|
1316
|
+
.requiredOption('--email <email>', 'Email address (unique)')
|
|
1193
1317
|
.option('--phone <phone>', 'Phone number')
|
|
1194
|
-
.option('--company <company>', 'Company name')
|
|
1318
|
+
.option('--company <company>', 'Company name (text)')
|
|
1319
|
+
.option('--company-id <uuid>', 'Company UUID (links to companies table)')
|
|
1320
|
+
.option('--lead-id <uuid>', 'Lead UUID to link to this contact on creation')
|
|
1195
1321
|
.option('--title <title>', 'Job title')
|
|
1196
1322
|
.option('--tags <tags>', 'Comma-separated tags')
|
|
1197
1323
|
.option('--do-not-contact', 'Mark as do not contact')
|
|
@@ -1203,11 +1329,14 @@ contactsCmd
|
|
|
1203
1329
|
const client = getClient(globalOpts, agentName);
|
|
1204
1330
|
try {
|
|
1205
1331
|
const body = {
|
|
1206
|
-
|
|
1332
|
+
name: opts.name || opts.firstName,
|
|
1333
|
+
first_name: opts.firstName || opts.name,
|
|
1207
1334
|
last_name: opts.lastName,
|
|
1208
1335
|
email: opts.email,
|
|
1209
1336
|
phone: opts.phone,
|
|
1210
1337
|
company: opts.company,
|
|
1338
|
+
company_id: opts.companyId,
|
|
1339
|
+
lead_id: opts.leadId,
|
|
1211
1340
|
title: opts.title,
|
|
1212
1341
|
notes: opts.notes,
|
|
1213
1342
|
do_not_contact: opts.doNotContact === true,
|
|
@@ -1224,8 +1353,10 @@ contactsCmd
|
|
|
1224
1353
|
.command('list')
|
|
1225
1354
|
.description('List contacts')
|
|
1226
1355
|
.option('--email <email>', 'Filter by email (partial match)')
|
|
1227
|
-
.option('--company <company>', 'Filter by company (partial match)')
|
|
1356
|
+
.option('--company <company>', 'Filter by company name (partial match)')
|
|
1357
|
+
.option('--company-id <uuid>', 'Filter by company UUID')
|
|
1228
1358
|
.option('--tag <tag>', 'Filter by tag')
|
|
1359
|
+
.option('--limit <n>', 'Max results per page (default 20)')
|
|
1229
1360
|
.option('--page <n>', 'Page number', '1')
|
|
1230
1361
|
.option('--per-page <n>', 'Results per page', '20')
|
|
1231
1362
|
.action(async (opts) => {
|
|
@@ -1233,8 +1364,10 @@ contactsCmd
|
|
|
1233
1364
|
const client = getClient(globalOpts);
|
|
1234
1365
|
try {
|
|
1235
1366
|
const params = { page: opts.page, per_page: opts.perPage };
|
|
1367
|
+
if (opts.limit) params.limit = opts.limit;
|
|
1236
1368
|
if (opts.email) params.email = opts.email;
|
|
1237
1369
|
if (opts.company) params.company = opts.company;
|
|
1370
|
+
if (opts.companyId) params.company_id = opts.companyId;
|
|
1238
1371
|
if (opts.tag) params.tag = opts.tag;
|
|
1239
1372
|
const res = await client.get('/contacts', { params });
|
|
1240
1373
|
printJSON(res.data);
|
|
@@ -1260,11 +1393,13 @@ contactsCmd
|
|
|
1260
1393
|
contactsCmd
|
|
1261
1394
|
.command('update <id>')
|
|
1262
1395
|
.description('Update a contact')
|
|
1396
|
+
.option('--name <name>', 'Full name (alias for --first-name)')
|
|
1263
1397
|
.option('--first-name <name>', 'First name')
|
|
1264
1398
|
.option('--last-name <name>', 'Last name')
|
|
1265
1399
|
.option('--email <email>', 'Email address')
|
|
1266
1400
|
.option('--phone <phone>', 'Phone number')
|
|
1267
|
-
.option('--company <company>', 'Company name')
|
|
1401
|
+
.option('--company <company>', 'Company name (text)')
|
|
1402
|
+
.option('--company-id <uuid>', 'Company UUID (links to companies table)')
|
|
1268
1403
|
.option('--title <title>', 'Job title')
|
|
1269
1404
|
.option('--role <role>', 'Role: champion|economic_buyer|technical_buyer|gatekeeper|influencer|end_user')
|
|
1270
1405
|
.option('--tags <tags>', 'Comma-separated tags')
|
|
@@ -1274,11 +1409,13 @@ contactsCmd
|
|
|
1274
1409
|
const client = getClient(globalOpts);
|
|
1275
1410
|
try {
|
|
1276
1411
|
const body = {};
|
|
1412
|
+
if (opts.name !== undefined) body.name = opts.name;
|
|
1277
1413
|
if (opts.firstName !== undefined) body.first_name = opts.firstName;
|
|
1278
1414
|
if (opts.lastName !== undefined) body.last_name = opts.lastName;
|
|
1279
1415
|
if (opts.email !== undefined) body.email = opts.email;
|
|
1280
1416
|
if (opts.phone !== undefined) body.phone = opts.phone;
|
|
1281
1417
|
if (opts.company !== undefined) body.company = opts.company;
|
|
1418
|
+
if (opts.companyId !== undefined) body.company_id = opts.companyId;
|
|
1282
1419
|
if (opts.title !== undefined) body.title = opts.title;
|
|
1283
1420
|
if (opts.role !== undefined) body.role = opts.role;
|
|
1284
1421
|
if (opts.tags !== undefined) body.tags = opts.tags.split(',').map(t => t.trim());
|
|
@@ -3042,6 +3179,59 @@ webhooksCmd
|
|
|
3042
3179
|
}
|
|
3043
3180
|
});
|
|
3044
3181
|
|
|
3182
|
+
// Sprint 53 T2: webhooks test / logs / retry
|
|
3183
|
+
webhooksCmd
|
|
3184
|
+
.command('test <webhook-id>')
|
|
3185
|
+
.description('Send a test payload to a webhook and check delivery (Sprint 53 T2)')
|
|
3186
|
+
.action(async (webhookId) => {
|
|
3187
|
+
const globalOpts = program.opts();
|
|
3188
|
+
const client = getClient(globalOpts);
|
|
3189
|
+
try {
|
|
3190
|
+
const res = await client.post(`/webhooks/${webhookId}/test`);
|
|
3191
|
+
const d = res.data.data;
|
|
3192
|
+
console.log(`Test queued — event: ${d.event}, webhook_id: ${d.webhook_id}`);
|
|
3193
|
+
printJSON(res.data);
|
|
3194
|
+
} catch (err) { handleError(err); }
|
|
3195
|
+
});
|
|
3196
|
+
|
|
3197
|
+
webhooksCmd
|
|
3198
|
+
.command('logs <webhook-id>')
|
|
3199
|
+
.description('View webhook delivery history (Sprint 53 T2)')
|
|
3200
|
+
.option('--limit <n>', 'Max results (default 20)', '20')
|
|
3201
|
+
.option('--status <status>', 'Filter by status: success or failed')
|
|
3202
|
+
.action(async (webhookId, opts) => {
|
|
3203
|
+
const globalOpts = program.opts();
|
|
3204
|
+
const client = getClient(globalOpts);
|
|
3205
|
+
try {
|
|
3206
|
+
const params = { limit: parseInt(opts.limit) };
|
|
3207
|
+
if (opts.status) params.status = opts.status;
|
|
3208
|
+
const res = await client.get(`/webhooks/${webhookId}/deliveries`, { params });
|
|
3209
|
+
const deliveries = res.data.data || [];
|
|
3210
|
+
if (deliveries.length === 0) { console.log('No delivery logs found.'); return; }
|
|
3211
|
+
deliveries.forEach(d => {
|
|
3212
|
+
const icon = d.success ? '✅' : '❌';
|
|
3213
|
+
const retry = d.next_retry_at ? ` | retry: ${new Date(d.next_retry_at).toLocaleString()}` : '';
|
|
3214
|
+
console.log(`${icon} [${d.id}] ${d.event} — HTTP ${d.status_code || '?'} at ${new Date(d.attempted_at).toLocaleString()}${retry}`);
|
|
3215
|
+
if (!d.success && d.error_message) console.log(` Error: ${d.error_message}`);
|
|
3216
|
+
});
|
|
3217
|
+
const p = res.data.pagination;
|
|
3218
|
+
if (p) console.log(`\nShowing ${deliveries.length} of ${p.total} total deliveries.`);
|
|
3219
|
+
} catch (err) { handleError(err); }
|
|
3220
|
+
});
|
|
3221
|
+
|
|
3222
|
+
webhooksCmd
|
|
3223
|
+
.command('retry <webhook-id> <delivery-id>')
|
|
3224
|
+
.description('Retry a failed webhook delivery (Sprint 53 T2)')
|
|
3225
|
+
.action(async (webhookId, deliveryId) => {
|
|
3226
|
+
const globalOpts = program.opts();
|
|
3227
|
+
const client = getClient(globalOpts);
|
|
3228
|
+
try {
|
|
3229
|
+
const res = await client.post(`/webhooks/${webhookId}/deliveries/${deliveryId}/retry`);
|
|
3230
|
+
console.log(`Retry queued for delivery ${deliveryId}`);
|
|
3231
|
+
printJSON(res.data);
|
|
3232
|
+
} catch (err) { handleError(err); }
|
|
3233
|
+
});
|
|
3234
|
+
|
|
3045
3235
|
// ── stages ──────────────────────────────────────────────────────────
|
|
3046
3236
|
const stagesCmd = program.command('stages').description('Manage pipeline stages (admin)');
|
|
3047
3237
|
|
|
@@ -3442,4 +3632,81 @@ gdprCmd
|
|
|
3442
3632
|
}
|
|
3443
3633
|
});
|
|
3444
3634
|
|
|
3635
|
+
// ============================================================
|
|
3636
|
+
// REPORTS COMMANDS — Sprint 52 T3
|
|
3637
|
+
// ============================================================
|
|
3638
|
+
const reportsCmd = program.command('reports').description('Manage and run saved CRM reports (Sprint 52 T3)');
|
|
3639
|
+
|
|
3640
|
+
reportsCmd
|
|
3641
|
+
.command('list')
|
|
3642
|
+
.description('List all saved reports')
|
|
3643
|
+
.action(async () => {
|
|
3644
|
+
const globalOpts = program.opts();
|
|
3645
|
+
const client = getClient(globalOpts);
|
|
3646
|
+
try {
|
|
3647
|
+
const res = await client.get('/reports');
|
|
3648
|
+
const reports = res.data.data || res.data;
|
|
3649
|
+
if (!reports || reports.length === 0) { console.log('No reports found.'); return; }
|
|
3650
|
+
reports.forEach(r => console.log(`[${r.id}] ${r.name} — type: ${r.dimension}/${r.metric}, period: ${r.period}`));
|
|
3651
|
+
} catch (err) { handleError(err); }
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
reportsCmd
|
|
3655
|
+
.command('create')
|
|
3656
|
+
.description('Create and save a report definition')
|
|
3657
|
+
.requiredOption('--name <name>', 'Report name')
|
|
3658
|
+
.option('--type <type>', 'Report type: leads (count by stage) | pipeline (deal value by pipeline) | revenue (deal value by stage)', 'leads')
|
|
3659
|
+
.option('--period <period>', 'Time period: 7d | 30d | 90d | 365d | all', '30d')
|
|
3660
|
+
.option('--filter-status <status>', 'Filter leads by status')
|
|
3661
|
+
.option('--filter-stage <stage>', 'Filter leads by pipeline stage')
|
|
3662
|
+
.option('--chart-type <type>', 'Chart type: bar | line | pie | table', 'bar')
|
|
3663
|
+
.action(async (opts) => {
|
|
3664
|
+
const globalOpts = program.opts();
|
|
3665
|
+
const client = getClient(globalOpts);
|
|
3666
|
+
try {
|
|
3667
|
+
const body = {
|
|
3668
|
+
name: opts.name,
|
|
3669
|
+
type: opts.type,
|
|
3670
|
+
period: opts.period,
|
|
3671
|
+
chart_type: opts.chartType,
|
|
3672
|
+
};
|
|
3673
|
+
if (opts.filterStatus) body.filter_status = opts.filterStatus;
|
|
3674
|
+
if (opts.filterStage) body.filter_stage = opts.filterStage;
|
|
3675
|
+
const res = await client.post('/reports', body);
|
|
3676
|
+
const r = res.data.data || res.data;
|
|
3677
|
+
console.log(`Report created: [${r.id}] ${r.name}`);
|
|
3678
|
+
printJSON(res.data);
|
|
3679
|
+
} catch (err) { handleError(err); }
|
|
3680
|
+
});
|
|
3681
|
+
|
|
3682
|
+
reportsCmd
|
|
3683
|
+
.command('run <report-id>')
|
|
3684
|
+
.description('Run a saved report and display or save results')
|
|
3685
|
+
.option('--output <file>', 'Save results to this JSON file')
|
|
3686
|
+
.action(async (reportId, opts) => {
|
|
3687
|
+
const globalOpts = program.opts();
|
|
3688
|
+
const client = getClient(globalOpts);
|
|
3689
|
+
try {
|
|
3690
|
+
const res = await client.get(`/reports/${reportId}/run`);
|
|
3691
|
+
if (opts.output) {
|
|
3692
|
+
fs.writeFileSync(opts.output, JSON.stringify(res.data, null, 2));
|
|
3693
|
+
console.log(`Report results saved to ${opts.output}`);
|
|
3694
|
+
} else {
|
|
3695
|
+
printJSON(res.data);
|
|
3696
|
+
}
|
|
3697
|
+
} catch (err) { handleError(err); }
|
|
3698
|
+
});
|
|
3699
|
+
|
|
3700
|
+
reportsCmd
|
|
3701
|
+
.command('delete <report-id>')
|
|
3702
|
+
.description('Delete a saved report')
|
|
3703
|
+
.action(async (reportId) => {
|
|
3704
|
+
const globalOpts = program.opts();
|
|
3705
|
+
const client = getClient(globalOpts);
|
|
3706
|
+
try {
|
|
3707
|
+
await client.delete(`/reports/${reportId}`);
|
|
3708
|
+
console.log(`Report ${reportId} deleted.`);
|
|
3709
|
+
} catch (err) { handleError(err); }
|
|
3710
|
+
});
|
|
3711
|
+
|
|
3445
3712
|
program.parse(process.argv);
|