@startanaicompany/crm 2.13.1 → 2.15.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 +285 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1020,6 +1020,194 @@ program
|
|
|
1020
1020
|
}
|
|
1021
1021
|
});
|
|
1022
1022
|
|
|
1023
|
+
// Sprint 51 T1: leads notes subcommands
|
|
1024
|
+
const leadsNotesCmd = leadsCmd.command('notes').description('Manage notes on a lead (Sprint 51 T1)');
|
|
1025
|
+
|
|
1026
|
+
leadsNotesCmd
|
|
1027
|
+
.command('add <lead-id>')
|
|
1028
|
+
.description('Add a note to a lead')
|
|
1029
|
+
.requiredOption('--body <text>', 'Note body text')
|
|
1030
|
+
.option('--mention <agent>', 'Agent name to @mention (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
1031
|
+
.option('--type <type>', 'Note type: general|call_summary|email_summary|qualification_note|action_taken|handoff', 'general')
|
|
1032
|
+
.option('--pin', 'Pin note to top', false)
|
|
1033
|
+
.action(async (leadId, opts) => {
|
|
1034
|
+
const globalOpts = program.opts();
|
|
1035
|
+
const client = getClient(globalOpts);
|
|
1036
|
+
try {
|
|
1037
|
+
const res = await client.post(`/leads/${leadId}/notes`, {
|
|
1038
|
+
content: opts.body,
|
|
1039
|
+
mentions: opts.mention.length > 0 ? opts.mention : undefined,
|
|
1040
|
+
note_type: opts.type,
|
|
1041
|
+
is_pinned: opts.pin || undefined,
|
|
1042
|
+
});
|
|
1043
|
+
printJSON(res.data);
|
|
1044
|
+
} catch (err) { handleError(err); }
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
leadsNotesCmd
|
|
1048
|
+
.command('list <lead-id>')
|
|
1049
|
+
.description('List all notes for a lead (pinned first, then reverse-chron)')
|
|
1050
|
+
.option('--limit <n>', 'Max results (server returns all by default)')
|
|
1051
|
+
.action(async (leadId, opts) => {
|
|
1052
|
+
const globalOpts = program.opts();
|
|
1053
|
+
const client = getClient(globalOpts);
|
|
1054
|
+
try {
|
|
1055
|
+
const params = {};
|
|
1056
|
+
if (opts.limit) params.limit = parseInt(opts.limit);
|
|
1057
|
+
const res = await client.get(`/leads/${leadId}/notes`, { params });
|
|
1058
|
+
const notes = res.data.data || [];
|
|
1059
|
+
if (notes.length === 0) { console.log('No notes found.'); return; }
|
|
1060
|
+
notes.forEach(n => {
|
|
1061
|
+
const pin = n.is_pinned ? ' 📌' : '';
|
|
1062
|
+
const mentions = n.mentions && n.mentions.length > 0 ? ` [@${n.mentions.join(', @')}]` : '';
|
|
1063
|
+
console.log(`[${n.id}]${pin} ${n.note_type} — ${new Date(n.created_at).toLocaleString()} by ${n.created_by || 'unknown'}`);
|
|
1064
|
+
console.log(` ${n.content}${mentions}`);
|
|
1065
|
+
});
|
|
1066
|
+
} catch (err) { handleError(err); }
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
leadsNotesCmd
|
|
1070
|
+
.command('get <lead-id> <note-id>')
|
|
1071
|
+
.description('Get a single note by ID')
|
|
1072
|
+
.action(async (leadId, noteId) => {
|
|
1073
|
+
const globalOpts = program.opts();
|
|
1074
|
+
const client = getClient(globalOpts);
|
|
1075
|
+
try {
|
|
1076
|
+
const res = await client.get(`/leads/${leadId}/notes/${noteId}`);
|
|
1077
|
+
printJSON(res.data);
|
|
1078
|
+
} catch (err) { handleError(err); }
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
// Sprint 51 T2: leads watch / unwatch / watched
|
|
1082
|
+
leadsCmd
|
|
1083
|
+
.command('watch <lead-id>')
|
|
1084
|
+
.description('Watch a lead — receive lead.watched_change webhook events (Sprint 51 T2)')
|
|
1085
|
+
.action(async (leadId) => {
|
|
1086
|
+
const globalOpts = program.opts();
|
|
1087
|
+
const client = getClient(globalOpts);
|
|
1088
|
+
try {
|
|
1089
|
+
const res = await client.post(`/leads/${leadId}/watch`);
|
|
1090
|
+
console.log(`Watching lead ${leadId}`);
|
|
1091
|
+
printJSON(res.data);
|
|
1092
|
+
} catch (err) { handleError(err); }
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
leadsCmd
|
|
1096
|
+
.command('unwatch <lead-id>')
|
|
1097
|
+
.description('Stop watching a lead (Sprint 51 T2)')
|
|
1098
|
+
.action(async (leadId) => {
|
|
1099
|
+
const globalOpts = program.opts();
|
|
1100
|
+
const client = getClient(globalOpts);
|
|
1101
|
+
try {
|
|
1102
|
+
const res = await client.delete(`/leads/${leadId}/watch`);
|
|
1103
|
+
console.log(`Unwatched lead ${leadId}`);
|
|
1104
|
+
printJSON(res.data);
|
|
1105
|
+
} catch (err) { handleError(err); }
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
leadsCmd
|
|
1109
|
+
.command('watched')
|
|
1110
|
+
.description('List all leads currently watched by this API key (Sprint 51 T2)')
|
|
1111
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
1112
|
+
.action(async (opts) => {
|
|
1113
|
+
const globalOpts = program.opts();
|
|
1114
|
+
const client = getClient(globalOpts);
|
|
1115
|
+
try {
|
|
1116
|
+
const res = await client.get('/leads/watched', { params: { limit: parseInt(opts.limit) } });
|
|
1117
|
+
const leads = res.data.data || res.data;
|
|
1118
|
+
if (!leads || leads.length === 0) { console.log('No watched leads.'); return; }
|
|
1119
|
+
leads.forEach(l => console.log(`[${l.id}] ${l.name} <${l.email}> — ${l.status}`));
|
|
1120
|
+
} catch (err) { handleError(err); }
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
// Sprint 52 T2: leads escalate + leads unassign
|
|
1124
|
+
leadsCmd
|
|
1125
|
+
.command('escalate <lead-id>')
|
|
1126
|
+
.description('Manually escalate a lead — sets escalated=true and fires lead.escalated webhook (Sprint 52 T2)')
|
|
1127
|
+
.option('--reason <text>', 'Reason for escalation')
|
|
1128
|
+
.option('--to <agent>', 'Reassign to this agent/user on escalation')
|
|
1129
|
+
.action(async (leadId, opts) => {
|
|
1130
|
+
const globalOpts = program.opts();
|
|
1131
|
+
const client = getClient(globalOpts);
|
|
1132
|
+
try {
|
|
1133
|
+
const body = {};
|
|
1134
|
+
if (opts.reason) body.reason = opts.reason;
|
|
1135
|
+
if (opts.to) body.to = opts.to;
|
|
1136
|
+
const res = await client.post(`/leads/${leadId}/escalate`, body);
|
|
1137
|
+
console.log(`Lead ${leadId} escalated.`);
|
|
1138
|
+
printJSON(res.data);
|
|
1139
|
+
} catch (err) { handleError(err); }
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
leadsCmd
|
|
1143
|
+
.command('unassign <lead-id>')
|
|
1144
|
+
.description('Remove assignment from a lead (sets assigned_to to null) (Sprint 52 T2)')
|
|
1145
|
+
.action(async (leadId) => {
|
|
1146
|
+
const globalOpts = program.opts();
|
|
1147
|
+
const client = getClient(globalOpts);
|
|
1148
|
+
try {
|
|
1149
|
+
const res = await client.patch(`/leads/${leadId}`, { assigned_to: null });
|
|
1150
|
+
console.log(`Lead ${leadId} unassigned.`);
|
|
1151
|
+
printJSON(res.data);
|
|
1152
|
+
} catch (err) { handleError(err); }
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// Sprint 51 T3: leads export / leads import
|
|
1156
|
+
leadsCmd
|
|
1157
|
+
.command('export')
|
|
1158
|
+
.description('Export leads to CSV or JSON (Sprint 51 T3)')
|
|
1159
|
+
.option('--format <format>', 'Output format: csv or json', 'csv')
|
|
1160
|
+
.option('--output <file>', 'Save to this file (default: leads-export.<ext>)')
|
|
1161
|
+
.option('--status <status>', 'Filter by status')
|
|
1162
|
+
.option('--pipeline-stage <stage>', 'Filter by pipeline stage')
|
|
1163
|
+
.option('--assigned-to <agent>', 'Filter by assigned agent')
|
|
1164
|
+
.option('--q <term>', 'Search term')
|
|
1165
|
+
.action(async (opts) => {
|
|
1166
|
+
const globalOpts = program.opts();
|
|
1167
|
+
const client = getClient(globalOpts);
|
|
1168
|
+
try {
|
|
1169
|
+
const fmt = opts.format === 'json' ? 'json' : 'csv';
|
|
1170
|
+
const params = { format: fmt };
|
|
1171
|
+
if (opts.status) params.status = opts.status;
|
|
1172
|
+
if (opts.pipelineStage) params.pipeline_stage = opts.pipelineStage;
|
|
1173
|
+
if (opts.assignedTo) params.assigned_to = opts.assignedTo;
|
|
1174
|
+
if (opts.q) params.q = opts.q;
|
|
1175
|
+
const res = await client.get('/leads/export', { params, responseType: fmt === 'json' ? 'text' : 'text' });
|
|
1176
|
+
const outFile = opts.output || `leads-export.${fmt}`;
|
|
1177
|
+
fs.writeFileSync(outFile, res.data);
|
|
1178
|
+
console.log(`Exported ${outFile}`);
|
|
1179
|
+
} catch (err) { handleError(err); }
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
leadsCmd
|
|
1183
|
+
.command('import')
|
|
1184
|
+
.description('Import leads from CSV file (Sprint 51 T3). Required columns: name, email. Optional: phone, company, status, deal_value, tags (semicolon-separated).')
|
|
1185
|
+
.requiredOption('--file <path>', 'Path to CSV file')
|
|
1186
|
+
.option('--dry-run', 'Validate without writing to database', false)
|
|
1187
|
+
.action(async (opts) => {
|
|
1188
|
+
const globalOpts = program.opts();
|
|
1189
|
+
const client = getClient(globalOpts);
|
|
1190
|
+
try {
|
|
1191
|
+
const csvData = fs.readFileSync(opts.file, 'utf8');
|
|
1192
|
+
const params = {};
|
|
1193
|
+
if (opts.dryRun) params.dry_run = 'true';
|
|
1194
|
+
const res = await client.post('/leads/import', csvData, {
|
|
1195
|
+
params,
|
|
1196
|
+
headers: { 'Content-Type': 'text/csv' },
|
|
1197
|
+
});
|
|
1198
|
+
const d = res.data.data;
|
|
1199
|
+
if (opts.dryRun) {
|
|
1200
|
+
console.log(`Dry-run: ${d.imported} rows would import, ${d.failed.length} errors.`);
|
|
1201
|
+
} else {
|
|
1202
|
+
console.log(`Imported: ${d.imported} leads. Errors: ${d.failed.length}`);
|
|
1203
|
+
}
|
|
1204
|
+
if (d.failed.length > 0) {
|
|
1205
|
+
console.log('Errors:');
|
|
1206
|
+
d.failed.forEach(f => console.log(` Row ${f.row}: ${f.reason}`));
|
|
1207
|
+
}
|
|
1208
|
+
} catch (err) { handleError(err); }
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1023
1211
|
// ============================================================
|
|
1024
1212
|
// CONTACTS
|
|
1025
1213
|
// ============================================================
|
|
@@ -1031,11 +1219,14 @@ const contactsCmd = program
|
|
|
1031
1219
|
contactsCmd
|
|
1032
1220
|
.command('create')
|
|
1033
1221
|
.description('Create a new contact')
|
|
1034
|
-
.
|
|
1222
|
+
.option('--name <name>', 'Full name (alias for --first-name)')
|
|
1223
|
+
.option('--first-name <name>', 'First name')
|
|
1035
1224
|
.option('--last-name <name>', 'Last name')
|
|
1036
|
-
.
|
|
1225
|
+
.requiredOption('--email <email>', 'Email address (unique)')
|
|
1037
1226
|
.option('--phone <phone>', 'Phone number')
|
|
1038
|
-
.option('--company <company>', 'Company name')
|
|
1227
|
+
.option('--company <company>', 'Company name (text)')
|
|
1228
|
+
.option('--company-id <uuid>', 'Company UUID (links to companies table)')
|
|
1229
|
+
.option('--lead-id <uuid>', 'Lead UUID to link to this contact on creation')
|
|
1039
1230
|
.option('--title <title>', 'Job title')
|
|
1040
1231
|
.option('--tags <tags>', 'Comma-separated tags')
|
|
1041
1232
|
.option('--do-not-contact', 'Mark as do not contact')
|
|
@@ -1047,11 +1238,14 @@ contactsCmd
|
|
|
1047
1238
|
const client = getClient(globalOpts, agentName);
|
|
1048
1239
|
try {
|
|
1049
1240
|
const body = {
|
|
1050
|
-
|
|
1241
|
+
name: opts.name || opts.firstName,
|
|
1242
|
+
first_name: opts.firstName || opts.name,
|
|
1051
1243
|
last_name: opts.lastName,
|
|
1052
1244
|
email: opts.email,
|
|
1053
1245
|
phone: opts.phone,
|
|
1054
1246
|
company: opts.company,
|
|
1247
|
+
company_id: opts.companyId,
|
|
1248
|
+
lead_id: opts.leadId,
|
|
1055
1249
|
title: opts.title,
|
|
1056
1250
|
notes: opts.notes,
|
|
1057
1251
|
do_not_contact: opts.doNotContact === true,
|
|
@@ -1068,8 +1262,10 @@ contactsCmd
|
|
|
1068
1262
|
.command('list')
|
|
1069
1263
|
.description('List contacts')
|
|
1070
1264
|
.option('--email <email>', 'Filter by email (partial match)')
|
|
1071
|
-
.option('--company <company>', 'Filter by company (partial match)')
|
|
1265
|
+
.option('--company <company>', 'Filter by company name (partial match)')
|
|
1266
|
+
.option('--company-id <uuid>', 'Filter by company UUID')
|
|
1072
1267
|
.option('--tag <tag>', 'Filter by tag')
|
|
1268
|
+
.option('--limit <n>', 'Max results per page (default 20)')
|
|
1073
1269
|
.option('--page <n>', 'Page number', '1')
|
|
1074
1270
|
.option('--per-page <n>', 'Results per page', '20')
|
|
1075
1271
|
.action(async (opts) => {
|
|
@@ -1077,8 +1273,10 @@ contactsCmd
|
|
|
1077
1273
|
const client = getClient(globalOpts);
|
|
1078
1274
|
try {
|
|
1079
1275
|
const params = { page: opts.page, per_page: opts.perPage };
|
|
1276
|
+
if (opts.limit) params.limit = opts.limit;
|
|
1080
1277
|
if (opts.email) params.email = opts.email;
|
|
1081
1278
|
if (opts.company) params.company = opts.company;
|
|
1279
|
+
if (opts.companyId) params.company_id = opts.companyId;
|
|
1082
1280
|
if (opts.tag) params.tag = opts.tag;
|
|
1083
1281
|
const res = await client.get('/contacts', { params });
|
|
1084
1282
|
printJSON(res.data);
|
|
@@ -1104,11 +1302,13 @@ contactsCmd
|
|
|
1104
1302
|
contactsCmd
|
|
1105
1303
|
.command('update <id>')
|
|
1106
1304
|
.description('Update a contact')
|
|
1305
|
+
.option('--name <name>', 'Full name (alias for --first-name)')
|
|
1107
1306
|
.option('--first-name <name>', 'First name')
|
|
1108
1307
|
.option('--last-name <name>', 'Last name')
|
|
1109
1308
|
.option('--email <email>', 'Email address')
|
|
1110
1309
|
.option('--phone <phone>', 'Phone number')
|
|
1111
|
-
.option('--company <company>', 'Company name')
|
|
1310
|
+
.option('--company <company>', 'Company name (text)')
|
|
1311
|
+
.option('--company-id <uuid>', 'Company UUID (links to companies table)')
|
|
1112
1312
|
.option('--title <title>', 'Job title')
|
|
1113
1313
|
.option('--role <role>', 'Role: champion|economic_buyer|technical_buyer|gatekeeper|influencer|end_user')
|
|
1114
1314
|
.option('--tags <tags>', 'Comma-separated tags')
|
|
@@ -1118,11 +1318,13 @@ contactsCmd
|
|
|
1118
1318
|
const client = getClient(globalOpts);
|
|
1119
1319
|
try {
|
|
1120
1320
|
const body = {};
|
|
1321
|
+
if (opts.name !== undefined) body.name = opts.name;
|
|
1121
1322
|
if (opts.firstName !== undefined) body.first_name = opts.firstName;
|
|
1122
1323
|
if (opts.lastName !== undefined) body.last_name = opts.lastName;
|
|
1123
1324
|
if (opts.email !== undefined) body.email = opts.email;
|
|
1124
1325
|
if (opts.phone !== undefined) body.phone = opts.phone;
|
|
1125
1326
|
if (opts.company !== undefined) body.company = opts.company;
|
|
1327
|
+
if (opts.companyId !== undefined) body.company_id = opts.companyId;
|
|
1126
1328
|
if (opts.title !== undefined) body.title = opts.title;
|
|
1127
1329
|
if (opts.role !== undefined) body.role = opts.role;
|
|
1128
1330
|
if (opts.tags !== undefined) body.tags = opts.tags.split(',').map(t => t.trim());
|
|
@@ -3286,4 +3488,81 @@ gdprCmd
|
|
|
3286
3488
|
}
|
|
3287
3489
|
});
|
|
3288
3490
|
|
|
3491
|
+
// ============================================================
|
|
3492
|
+
// REPORTS COMMANDS — Sprint 52 T3
|
|
3493
|
+
// ============================================================
|
|
3494
|
+
const reportsCmd = program.command('reports').description('Manage and run saved CRM reports (Sprint 52 T3)');
|
|
3495
|
+
|
|
3496
|
+
reportsCmd
|
|
3497
|
+
.command('list')
|
|
3498
|
+
.description('List all saved reports')
|
|
3499
|
+
.action(async () => {
|
|
3500
|
+
const globalOpts = program.opts();
|
|
3501
|
+
const client = getClient(globalOpts);
|
|
3502
|
+
try {
|
|
3503
|
+
const res = await client.get('/reports');
|
|
3504
|
+
const reports = res.data.data || res.data;
|
|
3505
|
+
if (!reports || reports.length === 0) { console.log('No reports found.'); return; }
|
|
3506
|
+
reports.forEach(r => console.log(`[${r.id}] ${r.name} — type: ${r.dimension}/${r.metric}, period: ${r.period}`));
|
|
3507
|
+
} catch (err) { handleError(err); }
|
|
3508
|
+
});
|
|
3509
|
+
|
|
3510
|
+
reportsCmd
|
|
3511
|
+
.command('create')
|
|
3512
|
+
.description('Create and save a report definition')
|
|
3513
|
+
.requiredOption('--name <name>', 'Report name')
|
|
3514
|
+
.option('--type <type>', 'Report type: leads (count by stage) | pipeline (deal value by pipeline) | revenue (deal value by stage)', 'leads')
|
|
3515
|
+
.option('--period <period>', 'Time period: 7d | 30d | 90d | 365d | all', '30d')
|
|
3516
|
+
.option('--filter-status <status>', 'Filter leads by status')
|
|
3517
|
+
.option('--filter-stage <stage>', 'Filter leads by pipeline stage')
|
|
3518
|
+
.option('--chart-type <type>', 'Chart type: bar | line | pie | table', 'bar')
|
|
3519
|
+
.action(async (opts) => {
|
|
3520
|
+
const globalOpts = program.opts();
|
|
3521
|
+
const client = getClient(globalOpts);
|
|
3522
|
+
try {
|
|
3523
|
+
const body = {
|
|
3524
|
+
name: opts.name,
|
|
3525
|
+
type: opts.type,
|
|
3526
|
+
period: opts.period,
|
|
3527
|
+
chart_type: opts.chartType,
|
|
3528
|
+
};
|
|
3529
|
+
if (opts.filterStatus) body.filter_status = opts.filterStatus;
|
|
3530
|
+
if (opts.filterStage) body.filter_stage = opts.filterStage;
|
|
3531
|
+
const res = await client.post('/reports', body);
|
|
3532
|
+
const r = res.data.data || res.data;
|
|
3533
|
+
console.log(`Report created: [${r.id}] ${r.name}`);
|
|
3534
|
+
printJSON(res.data);
|
|
3535
|
+
} catch (err) { handleError(err); }
|
|
3536
|
+
});
|
|
3537
|
+
|
|
3538
|
+
reportsCmd
|
|
3539
|
+
.command('run <report-id>')
|
|
3540
|
+
.description('Run a saved report and display or save results')
|
|
3541
|
+
.option('--output <file>', 'Save results to this JSON file')
|
|
3542
|
+
.action(async (reportId, opts) => {
|
|
3543
|
+
const globalOpts = program.opts();
|
|
3544
|
+
const client = getClient(globalOpts);
|
|
3545
|
+
try {
|
|
3546
|
+
const res = await client.get(`/reports/${reportId}/run`);
|
|
3547
|
+
if (opts.output) {
|
|
3548
|
+
fs.writeFileSync(opts.output, JSON.stringify(res.data, null, 2));
|
|
3549
|
+
console.log(`Report results saved to ${opts.output}`);
|
|
3550
|
+
} else {
|
|
3551
|
+
printJSON(res.data);
|
|
3552
|
+
}
|
|
3553
|
+
} catch (err) { handleError(err); }
|
|
3554
|
+
});
|
|
3555
|
+
|
|
3556
|
+
reportsCmd
|
|
3557
|
+
.command('delete <report-id>')
|
|
3558
|
+
.description('Delete a saved report')
|
|
3559
|
+
.action(async (reportId) => {
|
|
3560
|
+
const globalOpts = program.opts();
|
|
3561
|
+
const client = getClient(globalOpts);
|
|
3562
|
+
try {
|
|
3563
|
+
await client.delete(`/reports/${reportId}`);
|
|
3564
|
+
console.log(`Report ${reportId} deleted.`);
|
|
3565
|
+
} catch (err) { handleError(err); }
|
|
3566
|
+
});
|
|
3567
|
+
|
|
3289
3568
|
program.parse(process.argv);
|