@startanaicompany/crm 2.15.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 +173 -29
- 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
|
|
795
|
+
leadsCmd
|
|
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) => {
|
|
800
|
+
const globalOpts = program.opts();
|
|
801
|
+
const client = getClient(globalOpts);
|
|
802
|
+
try {
|
|
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
|
|
774
831
|
leadsCmd
|
|
775
|
-
.command('search
|
|
776
|
-
.description('Search leads by name, email, company, or notes')
|
|
777
|
-
.
|
|
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) => {
|
|
778
838
|
const globalOpts = program.opts();
|
|
779
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); }
|
|
780
842
|
try {
|
|
781
|
-
const res = await client.get('/leads', { params: { q:
|
|
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,36 @@ 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
|
+
|
|
1123
1214
|
// Sprint 52 T2: leads escalate + leads unassign
|
|
1124
1215
|
leadsCmd
|
|
1125
1216
|
.command('escalate <lead-id>')
|
|
@@ -3088,6 +3179,59 @@ webhooksCmd
|
|
|
3088
3179
|
}
|
|
3089
3180
|
});
|
|
3090
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
|
+
|
|
3091
3235
|
// ── stages ──────────────────────────────────────────────────────────
|
|
3092
3236
|
const stagesCmd = program.command('stages').description('Manage pipeline stages (admin)');
|
|
3093
3237
|
|