@startanaicompany/crm 2.13.1 → 2.14.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.
Files changed (2) hide show
  1. package/index.js +156 -0
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1020,6 +1020,162 @@ 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 51 T3: leads export / leads import
1124
+ leadsCmd
1125
+ .command('export')
1126
+ .description('Export leads to CSV or JSON (Sprint 51 T3)')
1127
+ .option('--format <format>', 'Output format: csv or json', 'csv')
1128
+ .option('--output <file>', 'Save to this file (default: leads-export.<ext>)')
1129
+ .option('--status <status>', 'Filter by status')
1130
+ .option('--pipeline-stage <stage>', 'Filter by pipeline stage')
1131
+ .option('--assigned-to <agent>', 'Filter by assigned agent')
1132
+ .option('--q <term>', 'Search term')
1133
+ .action(async (opts) => {
1134
+ const globalOpts = program.opts();
1135
+ const client = getClient(globalOpts);
1136
+ try {
1137
+ const fmt = opts.format === 'json' ? 'json' : 'csv';
1138
+ const params = { format: fmt };
1139
+ if (opts.status) params.status = opts.status;
1140
+ if (opts.pipelineStage) params.pipeline_stage = opts.pipelineStage;
1141
+ if (opts.assignedTo) params.assigned_to = opts.assignedTo;
1142
+ if (opts.q) params.q = opts.q;
1143
+ const res = await client.get('/leads/export', { params, responseType: fmt === 'json' ? 'text' : 'text' });
1144
+ const outFile = opts.output || `leads-export.${fmt}`;
1145
+ fs.writeFileSync(outFile, res.data);
1146
+ console.log(`Exported ${outFile}`);
1147
+ } catch (err) { handleError(err); }
1148
+ });
1149
+
1150
+ leadsCmd
1151
+ .command('import')
1152
+ .description('Import leads from CSV file (Sprint 51 T3). Required columns: name, email. Optional: phone, company, status, deal_value, tags (semicolon-separated).')
1153
+ .requiredOption('--file <path>', 'Path to CSV file')
1154
+ .option('--dry-run', 'Validate without writing to database', false)
1155
+ .action(async (opts) => {
1156
+ const globalOpts = program.opts();
1157
+ const client = getClient(globalOpts);
1158
+ try {
1159
+ const csvData = fs.readFileSync(opts.file, 'utf8');
1160
+ const params = {};
1161
+ if (opts.dryRun) params.dry_run = 'true';
1162
+ const res = await client.post('/leads/import', csvData, {
1163
+ params,
1164
+ headers: { 'Content-Type': 'text/csv' },
1165
+ });
1166
+ const d = res.data.data;
1167
+ if (opts.dryRun) {
1168
+ console.log(`Dry-run: ${d.imported} rows would import, ${d.failed.length} errors.`);
1169
+ } else {
1170
+ console.log(`Imported: ${d.imported} leads. Errors: ${d.failed.length}`);
1171
+ }
1172
+ if (d.failed.length > 0) {
1173
+ console.log('Errors:');
1174
+ d.failed.forEach(f => console.log(` Row ${f.row}: ${f.reason}`));
1175
+ }
1176
+ } catch (err) { handleError(err); }
1177
+ });
1178
+
1023
1179
  // ============================================================
1024
1180
  // CONTACTS
1025
1181
  // ============================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "2.13.1",
3
+ "version": "2.14.0",
4
4
  "description": "AI-first CRM CLI — manage leads and API keys from the terminal",
5
5
  "main": "index.js",
6
6
  "bin": {