@startanaicompany/crm 2.16.0 → 2.17.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 +250 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -603,7 +603,8 @@ leadsCmd
|
|
|
603
603
|
client.get(`/leads/${leadId}/score-history`),
|
|
604
604
|
]);
|
|
605
605
|
const lead = leadRes.data.data;
|
|
606
|
-
|
|
606
|
+
// Bug 00d0c981: score-history returns { history: [], total: N } not a bare array
|
|
607
|
+
const history = histRes.data.data?.history || histRes.data.data || [];
|
|
607
608
|
const label = lead.score >= 80 ? 'Hot' : lead.score >= 60 ? 'Qualified' : lead.score >= 40 ? 'Developing' : lead.score >= 20 ? 'Warm' : 'Cold';
|
|
608
609
|
console.log(`\nLead: ${lead.name} <${lead.email}>`);
|
|
609
610
|
console.log(`Current Score: ${lead.score ?? 'N/A'} (${label})`);
|
|
@@ -801,8 +802,8 @@ leadsCmd
|
|
|
801
802
|
const client = getClient(globalOpts);
|
|
802
803
|
try {
|
|
803
804
|
if (opts.to) {
|
|
804
|
-
//
|
|
805
|
-
const res = await client.patch(`/leads/${leadId}`, {
|
|
805
|
+
// Bug 6d850c25: use PATCH /leads/:id/stage with { stage } not /leads/:id with { pipeline_stage }
|
|
806
|
+
const res = await client.patch(`/leads/${leadId}/stage`, { stage: opts.to });
|
|
806
807
|
const d = res.data.data;
|
|
807
808
|
console.log(`Stage set to: ${d.pipeline_stage}`);
|
|
808
809
|
printJSON(res.data);
|
|
@@ -1211,6 +1212,119 @@ leadsCmd
|
|
|
1211
1212
|
} catch (err) { handleError(err); }
|
|
1212
1213
|
});
|
|
1213
1214
|
|
|
1215
|
+
// Sprint 54 T1: leads merge — merge secondary lead into primary
|
|
1216
|
+
leadsCmd
|
|
1217
|
+
.command('merge <primary-id> <secondary-id>')
|
|
1218
|
+
.description('Merge secondary lead into primary lead (Sprint 54 T1). Use --dry-run to preview without committing.')
|
|
1219
|
+
.option('--dry-run', 'Preview what would be merged without committing changes', false)
|
|
1220
|
+
.action(async (primaryId, secondaryId, opts) => {
|
|
1221
|
+
const globalOpts = program.opts();
|
|
1222
|
+
const client = getClient(globalOpts);
|
|
1223
|
+
try {
|
|
1224
|
+
if (opts.dryRun) {
|
|
1225
|
+
// Dry-run: fetch both leads and show diff without calling merge
|
|
1226
|
+
const [primaryRes, secondaryRes] = await Promise.all([
|
|
1227
|
+
client.get(`/leads/${primaryId}`),
|
|
1228
|
+
client.get(`/leads/${secondaryId}`),
|
|
1229
|
+
]);
|
|
1230
|
+
const primary = primaryRes.data.data;
|
|
1231
|
+
const secondary = secondaryRes.data.data;
|
|
1232
|
+
console.log('\n[DRY RUN] Merge preview:');
|
|
1233
|
+
console.log(` Primary : [${primary.id}] ${primary.name} <${primary.email}> — ${primary.status}`);
|
|
1234
|
+
console.log(` Secondary: [${secondary.id}] ${secondary.name} <${secondary.email}> — ${secondary.status}`);
|
|
1235
|
+
console.log('\n What would happen:');
|
|
1236
|
+
console.log(` - Notes, communications, and audit history from secondary copied to primary`);
|
|
1237
|
+
console.log(` - Secondary lead soft-deleted (status → merged)`);
|
|
1238
|
+
console.log(` - Primary lead retained with merged data`);
|
|
1239
|
+
if (secondary.tags && secondary.tags.length > 0) {
|
|
1240
|
+
console.log(` - Tags from secondary that will be merged: ${secondary.tags.join(', ')}`);
|
|
1241
|
+
}
|
|
1242
|
+
console.log('\n Run without --dry-run to commit the merge.');
|
|
1243
|
+
} else {
|
|
1244
|
+
const res = await client.post(`/leads/${primaryId}/merge`, { merge_into: secondaryId });
|
|
1245
|
+
console.log(`Merged lead ${secondaryId} into ${primaryId}.`);
|
|
1246
|
+
printJSON(res.data);
|
|
1247
|
+
}
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
handleError(err);
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
// Sprint 54 T1: leads bulk-update --ids — ID-based bulk status update (distinct from filter-based bulk-update)
|
|
1254
|
+
leadsCmd
|
|
1255
|
+
.command('bulk-update-ids')
|
|
1256
|
+
.description('Bulk update status for specific leads by ID list (Sprint 54 T1). Up to 100 leads per call.')
|
|
1257
|
+
.requiredOption('--ids <ids>', 'Comma-separated lead IDs to update')
|
|
1258
|
+
.requiredOption('--status <status>', 'New status: new|contacted|qualified|unresponsive|converted|lost')
|
|
1259
|
+
.option('--close-reason <reason>', 'Close reason (required when --status lost)')
|
|
1260
|
+
.action(async (opts) => {
|
|
1261
|
+
const globalOpts = program.opts();
|
|
1262
|
+
const client = getClient(globalOpts);
|
|
1263
|
+
const ids = opts.ids.split(',').map(s => s.trim()).filter(Boolean);
|
|
1264
|
+
if (ids.length === 0) { console.error('Error: --ids requires at least one ID'); process.exit(1); }
|
|
1265
|
+
const updates = { status: opts.status };
|
|
1266
|
+
if (opts.closeReason) updates.close_reason = opts.closeReason;
|
|
1267
|
+
try {
|
|
1268
|
+
const res = await client.patch('/leads/bulk', { ids, updates });
|
|
1269
|
+
const d = res.data.data || {};
|
|
1270
|
+
console.log(`Updated: ${d.updated !== undefined ? d.updated : ids.length} lead(s).`);
|
|
1271
|
+
if (d.failed && d.failed.length > 0) {
|
|
1272
|
+
console.log(`Failed IDs: ${d.failed.join(', ')}`);
|
|
1273
|
+
}
|
|
1274
|
+
printJSON(res.data);
|
|
1275
|
+
} catch (err) {
|
|
1276
|
+
handleError(err);
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
// Sprint 54 T1: leads bulk-tag — add a tag to multiple leads by ID
|
|
1281
|
+
leadsCmd
|
|
1282
|
+
.command('bulk-tag')
|
|
1283
|
+
.description('Add a tag to multiple leads by ID list (Sprint 54 T1). Up to 100 leads per call.')
|
|
1284
|
+
.requiredOption('--tag <tag>', 'Tag to add to each lead')
|
|
1285
|
+
.requiredOption('--ids <ids>', 'Comma-separated lead IDs')
|
|
1286
|
+
.action(async (opts) => {
|
|
1287
|
+
const globalOpts = program.opts();
|
|
1288
|
+
const client = getClient(globalOpts);
|
|
1289
|
+
const ids = opts.ids.split(',').map(s => s.trim()).filter(Boolean);
|
|
1290
|
+
if (ids.length === 0) { console.error('Error: --ids requires at least one ID'); process.exit(1); }
|
|
1291
|
+
try {
|
|
1292
|
+
const res = await client.patch('/leads/bulk', { ids, updates: { tags: [opts.tag] } });
|
|
1293
|
+
const d = res.data.data || {};
|
|
1294
|
+
console.log(`Tagged ${d.updated !== undefined ? d.updated : ids.length} lead(s) with "${opts.tag}".`);
|
|
1295
|
+
if (d.failed && d.failed.length > 0) {
|
|
1296
|
+
console.log(`Failed IDs: ${d.failed.join(', ')}`);
|
|
1297
|
+
}
|
|
1298
|
+
printJSON(res.data);
|
|
1299
|
+
} catch (err) {
|
|
1300
|
+
handleError(err);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
// Sprint 54 T1: leads bulk-assign — reassign multiple leads to an agent by ID
|
|
1305
|
+
leadsCmd
|
|
1306
|
+
.command('bulk-assign')
|
|
1307
|
+
.description('Bulk reassign leads to an agent by ID list (Sprint 54 T1). Up to 100 leads per call.')
|
|
1308
|
+
.requiredOption('--to <agent>', 'Agent/user to assign leads to')
|
|
1309
|
+
.requiredOption('--ids <ids>', 'Comma-separated lead IDs')
|
|
1310
|
+
.action(async (opts) => {
|
|
1311
|
+
const globalOpts = program.opts();
|
|
1312
|
+
const client = getClient(globalOpts);
|
|
1313
|
+
const ids = opts.ids.split(',').map(s => s.trim()).filter(Boolean);
|
|
1314
|
+
if (ids.length === 0) { console.error('Error: --ids requires at least one ID'); process.exit(1); }
|
|
1315
|
+
try {
|
|
1316
|
+
const res = await client.patch('/leads/bulk', { ids, updates: { assigned_to: opts.to } });
|
|
1317
|
+
const d = res.data.data || {};
|
|
1318
|
+
console.log(`Assigned ${d.updated !== undefined ? d.updated : ids.length} lead(s) to "${opts.to}".`);
|
|
1319
|
+
if (d.failed && d.failed.length > 0) {
|
|
1320
|
+
console.log(`Failed IDs: ${d.failed.join(', ')}`);
|
|
1321
|
+
}
|
|
1322
|
+
printJSON(res.data);
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
handleError(err);
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1214
1328
|
// Sprint 52 T2: leads escalate + leads unassign
|
|
1215
1329
|
leadsCmd
|
|
1216
1330
|
.command('escalate <lead-id>')
|
|
@@ -3571,6 +3685,139 @@ sequencesCmd
|
|
|
3571
3685
|
} catch (err) { handleError(err); }
|
|
3572
3686
|
});
|
|
3573
3687
|
|
|
3688
|
+
// Sprint 54 T3: sequences stats <sequenceId>
|
|
3689
|
+
sequencesCmd
|
|
3690
|
+
.command('stats <sequenceId>')
|
|
3691
|
+
.description('Get enrollment stats for a sequence (Sprint 54 T3)')
|
|
3692
|
+
.action(async (sequenceId) => {
|
|
3693
|
+
const globalOpts = program.opts();
|
|
3694
|
+
const client = getClient(globalOpts);
|
|
3695
|
+
try {
|
|
3696
|
+
const res = await client.get(`/sequences/${sequenceId}/stats`);
|
|
3697
|
+
const d = res.data.data || {};
|
|
3698
|
+
console.log(`\nSequence: ${d.sequence_name} [${d.sequence_id}]`);
|
|
3699
|
+
console.log(`Active: ${d.active ? 'yes' : 'no'} Steps: ${d.step_count}`);
|
|
3700
|
+
console.log(`Total enrolled: ${d.total_enrolled}`);
|
|
3701
|
+
if (d.by_status) {
|
|
3702
|
+
console.log(` active: ${d.by_status.active}`);
|
|
3703
|
+
console.log(` completed: ${d.by_status.completed}`);
|
|
3704
|
+
console.log(` unsubscribed: ${d.by_status.unsubscribed}`);
|
|
3705
|
+
console.log(` paused: ${d.by_status.paused}`);
|
|
3706
|
+
console.log(` failed: ${d.by_status.failed}`);
|
|
3707
|
+
}
|
|
3708
|
+
if (d.first_enrollment) console.log(`First enrollment: ${new Date(d.first_enrollment).toLocaleString()}`);
|
|
3709
|
+
if (d.last_enrollment) console.log(`Last enrollment: ${new Date(d.last_enrollment).toLocaleString()}`);
|
|
3710
|
+
} catch (err) { handleError(err); }
|
|
3711
|
+
});
|
|
3712
|
+
|
|
3713
|
+
// Sprint 54 T3: leads sequences <lead-id>
|
|
3714
|
+
leadsCmd
|
|
3715
|
+
.command('sequences <lead-id>')
|
|
3716
|
+
.description('List sequences a lead is enrolled in (Sprint 54 T3)')
|
|
3717
|
+
.action(async (leadId) => {
|
|
3718
|
+
const globalOpts = program.opts();
|
|
3719
|
+
const client = getClient(globalOpts);
|
|
3720
|
+
try {
|
|
3721
|
+
const res = await client.get(`/leads/${leadId}/sequences`);
|
|
3722
|
+
const enrollments = res.data.data || res.data || [];
|
|
3723
|
+
if (enrollments.length === 0) { console.log('Lead is not enrolled in any sequences.'); return; }
|
|
3724
|
+
enrollments.forEach(e => {
|
|
3725
|
+
const step = e.current_step || 'N/A';
|
|
3726
|
+
const next = e.next_send_at ? new Date(e.next_send_at).toLocaleString() : 'N/A';
|
|
3727
|
+
console.log(`[${e.sequence_id}] ${e.sequence_name} — status: ${e.status}, step: ${step}, next: ${next}`);
|
|
3728
|
+
});
|
|
3729
|
+
} catch (err) { handleError(err); }
|
|
3730
|
+
});
|
|
3731
|
+
|
|
3732
|
+
// ============================================================
|
|
3733
|
+
// FORMS COMMANDS — Sprint 54 T2
|
|
3734
|
+
// ============================================================
|
|
3735
|
+
const formsCmd = program.command('forms').description('Manage lead capture forms (Sprint 54 T2)');
|
|
3736
|
+
|
|
3737
|
+
formsCmd
|
|
3738
|
+
.command('list')
|
|
3739
|
+
.description('List all forms in the workspace')
|
|
3740
|
+
.action(async () => {
|
|
3741
|
+
const globalOpts = program.opts();
|
|
3742
|
+
const client = getClient(globalOpts);
|
|
3743
|
+
try {
|
|
3744
|
+
const res = await client.get('/apikey/forms');
|
|
3745
|
+
const forms = res.data.data || res.data || [];
|
|
3746
|
+
if (forms.length === 0) { console.log('No forms found.'); return; }
|
|
3747
|
+
forms.forEach(f => {
|
|
3748
|
+
console.log(`[${f.id}] ${f.name} — submissions: ${f.submission_count || 0} — created: ${new Date(f.created_at).toLocaleDateString()}`);
|
|
3749
|
+
});
|
|
3750
|
+
} catch (err) { handleError(err); }
|
|
3751
|
+
});
|
|
3752
|
+
|
|
3753
|
+
formsCmd
|
|
3754
|
+
.command('get <form-id>')
|
|
3755
|
+
.description('Get form details by ID')
|
|
3756
|
+
.action(async (formId) => {
|
|
3757
|
+
const globalOpts = program.opts();
|
|
3758
|
+
const client = getClient(globalOpts);
|
|
3759
|
+
try {
|
|
3760
|
+
const res = await client.get(`/apikey/forms/${formId}`);
|
|
3761
|
+
printJSON(res.data);
|
|
3762
|
+
} catch (err) { handleError(err); }
|
|
3763
|
+
});
|
|
3764
|
+
|
|
3765
|
+
formsCmd
|
|
3766
|
+
.command('create')
|
|
3767
|
+
.description('Create a new lead capture form')
|
|
3768
|
+
.requiredOption('--name <name>', 'Form name')
|
|
3769
|
+
.option('--success-message <msg>', 'Message shown after submission', 'Thank you! We will be in touch.')
|
|
3770
|
+
.option('--redirect-url <url>', 'Redirect URL after submission')
|
|
3771
|
+
.option('--fields <json>', 'JSON array of field definitions (e.g. \'[{"key":"email","label":"Email","type":"email","required":true}]\')')
|
|
3772
|
+
.action(async (opts) => {
|
|
3773
|
+
const globalOpts = program.opts();
|
|
3774
|
+
const client = getClient(globalOpts);
|
|
3775
|
+
let fields = [];
|
|
3776
|
+
if (opts.fields) {
|
|
3777
|
+
try { fields = JSON.parse(opts.fields); } catch (e) { console.error('Error: --fields must be valid JSON array'); process.exit(1); }
|
|
3778
|
+
}
|
|
3779
|
+
try {
|
|
3780
|
+
const body = { name: opts.name, fields, success_message: opts.successMessage };
|
|
3781
|
+
if (opts.redirectUrl) body.redirect_url = opts.redirectUrl;
|
|
3782
|
+
const res = await client.post('/apikey/forms', body);
|
|
3783
|
+
console.log(`Form created: ${res.data.data?.id || res.data?.id}`);
|
|
3784
|
+
printJSON(res.data);
|
|
3785
|
+
} catch (err) { handleError(err); }
|
|
3786
|
+
});
|
|
3787
|
+
|
|
3788
|
+
formsCmd
|
|
3789
|
+
.command('submissions <form-id>')
|
|
3790
|
+
.description('List submissions for a form')
|
|
3791
|
+
.option('--limit <n>', 'Max results', '50')
|
|
3792
|
+
.option('--offset <n>', 'Pagination offset', '0')
|
|
3793
|
+
.action(async (formId, opts) => {
|
|
3794
|
+
const globalOpts = program.opts();
|
|
3795
|
+
const client = getClient(globalOpts);
|
|
3796
|
+
try {
|
|
3797
|
+
const res = await client.get(`/apikey/forms/${formId}/submissions`, { params: { limit: opts.limit, offset: opts.offset } });
|
|
3798
|
+
const d = res.data.data || {};
|
|
3799
|
+
const subs = d.data || [];
|
|
3800
|
+
if (subs.length === 0) { console.log('No submissions found.'); return; }
|
|
3801
|
+
console.log(`Total: ${d.total || subs.length}`);
|
|
3802
|
+
subs.forEach(s => {
|
|
3803
|
+
const when = s.submitted_at ? new Date(s.submitted_at).toLocaleString() : 'N/A';
|
|
3804
|
+
console.log(`[${s.id}] ${s.lead_name || 'Unknown'} <${s.lead_email || ''}> — ${when}`);
|
|
3805
|
+
});
|
|
3806
|
+
} catch (err) { handleError(err); }
|
|
3807
|
+
});
|
|
3808
|
+
|
|
3809
|
+
formsCmd
|
|
3810
|
+
.command('delete <form-id>')
|
|
3811
|
+
.description('Delete a form by ID')
|
|
3812
|
+
.action(async (formId) => {
|
|
3813
|
+
const globalOpts = program.opts();
|
|
3814
|
+
const client = getClient(globalOpts);
|
|
3815
|
+
try {
|
|
3816
|
+
await client.delete(`/apikey/forms/${formId}`);
|
|
3817
|
+
console.log(`Form ${formId} deleted.`);
|
|
3818
|
+
} catch (err) { handleError(err); }
|
|
3819
|
+
});
|
|
3820
|
+
|
|
3574
3821
|
// ============================================================
|
|
3575
3822
|
// GDPR COMMANDS — Sprint 50 T2
|
|
3576
3823
|
// ============================================================
|