@startanaicompany/crm 2.1.0 → 2.3.2

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 +759 -7
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -99,7 +99,7 @@ const program = new Command();
99
99
  program
100
100
  .name('saac_crm')
101
101
  .description('AI-first CRM CLI — manage leads and API keys')
102
- .version('1.0.3')
102
+ .version('2.3.0')
103
103
  .option('--api-key <key>', 'API key (overrides SAAC_CRM_API_KEY env and config)')
104
104
  .option('--url <url>', 'API base URL (overrides config)');
105
105
 
@@ -298,6 +298,8 @@ leadsCmd
298
298
  .option('--company <company>', 'Company name')
299
299
  .option('--status <status>', 'Status: new|contacted|qualified|unresponsive|converted|lost', 'new')
300
300
  .option('--source <source>', 'Source: api|import|referral', 'api')
301
+ .option('--deal-value <amount>', 'Deal value (numeric)')
302
+ .option('--pipeline-stage <stage>', 'Pipeline stage: new|prospecting|discovery|qualified|demo_scheduled|demo_completed|proposal_sent|negotiation|contract_sent|closed_won|closed_lost|no_decision|dormant')
301
303
  .option('--notes <notes>', 'Notes')
302
304
  .option('--assigned-to <assignedTo>', 'Assigned to')
303
305
  .option('--tag <tag>', 'Tag (repeatable)', (v, prev) => prev.concat([v]), [])
@@ -315,6 +317,8 @@ leadsCmd
315
317
  ...(opts.company && { company: opts.company }),
316
318
  status: opts.status,
317
319
  source: opts.source,
320
+ ...(opts.dealValue !== undefined && { deal_value: parseFloat(opts.dealValue) }),
321
+ ...(opts.pipelineStage && { pipeline_stage: opts.pipelineStage }),
318
322
  ...(opts.notes && { notes: opts.notes }),
319
323
  ...(opts.assignedTo && { assigned_to: opts.assignedTo }),
320
324
  ...(opts.tag.length > 0 && { tags: opts.tag }),
@@ -394,6 +398,7 @@ leadsCmd
394
398
  .option('--company <company>', 'New company')
395
399
  .option('--status <status>', 'New status')
396
400
  .option('--source <source>', 'New source')
401
+ .option('--deal-value <amount>', 'Deal value (numeric)')
397
402
  .option('--notes <notes>', 'New notes')
398
403
  .option('--assigned-to <assignedTo>', 'New assigned-to')
399
404
  .option('--tag <tag>', 'Replace tags (repeatable)', (v, prev) => prev.concat([v]), [])
@@ -409,6 +414,7 @@ leadsCmd
409
414
  ...(opts.company !== undefined && { company: opts.company }),
410
415
  ...(opts.status && { status: opts.status }),
411
416
  ...(opts.source && { source: opts.source }),
417
+ ...(opts.dealValue !== undefined && { deal_value: parseFloat(opts.dealValue) }),
412
418
  ...(opts.notes !== undefined && { notes: opts.notes }),
413
419
  ...(opts.assignedTo !== undefined && { assigned_to: opts.assignedTo }),
414
420
  ...(opts.tag.length > 0 && { tags: opts.tag }),
@@ -684,6 +690,7 @@ contactsCmd
684
690
  .option('--phone <phone>', 'Phone number')
685
691
  .option('--company <company>', 'Company name')
686
692
  .option('--title <title>', 'Job title')
693
+ .option('--role <role>', 'Role: champion|economic_buyer|technical_buyer|gatekeeper|influencer|end_user')
687
694
  .option('--tags <tags>', 'Comma-separated tags')
688
695
  .option('--notes <notes>', 'Notes')
689
696
  .action(async (id, opts) => {
@@ -697,6 +704,7 @@ contactsCmd
697
704
  if (opts.phone !== undefined) body.phone = opts.phone;
698
705
  if (opts.company !== undefined) body.company = opts.company;
699
706
  if (opts.title !== undefined) body.title = opts.title;
707
+ if (opts.role !== undefined) body.role = opts.role;
700
708
  if (opts.tags !== undefined) body.tags = opts.tags.split(',').map(t => t.trim());
701
709
  if (opts.notes !== undefined) body.notes = opts.notes;
702
710
  const res = await client.patch(`/contacts/${id}`, body);
@@ -766,7 +774,7 @@ callsCmd
766
774
  .requiredOption('--direction <dir>', 'inbound or outbound')
767
775
  .option('--lead-id <id>', 'Lead ID associated with this call')
768
776
  .option('--contact-id <id>', 'Contact ID associated with this call')
769
- .option('--outcome <outcome>', 'connected | left_voicemail | no_answer | busy | wrong_number | meeting_booked | demo_completed')
777
+ .option('--outcome <outcome>', 'connected | no_answer | voicemail | busy | failed | callback_requested | not_interested | wrong_number | left_message_with_gatekeeper | disconnected | scheduled')
770
778
  .option('--duration <seconds>', 'Duration in seconds')
771
779
  .option('--notes <notes>', 'Call notes')
772
780
  .option('--call-time <iso>', 'Call time (ISO8601, defaults to now)')
@@ -887,7 +895,7 @@ leadsStageCmd
887
895
 
888
896
  leadsStageCmd
889
897
  .command('set <lead-id> <stage>')
890
- .description('Set pipeline stage: new | qualified | proposal | negotiation | closed_won | closed_lost')
898
+ .description('Set pipeline stage: new|prospecting|discovery|qualified|demo_scheduled|demo_completed|proposal_sent|negotiation|contract_sent|closed_won|closed_lost|no_decision|dormant')
891
899
  .option('--note <note>', 'Optional note about why stage changed')
892
900
  .action(async (leadId, stage, opts) => {
893
901
  const globalOpts = program.opts();
@@ -937,6 +945,7 @@ meetingsCmd
937
945
  .option('--video-link <url>', 'Video call link (Zoom, Meet, etc.)')
938
946
  .option('--notes <notes>', 'Meeting notes or agenda')
939
947
  .option('--outcome <outcome>', 'scheduled | completed | cancelled | no_show')
948
+ .option('--sentiment <sentiment>', 'positive | neutral | negative | unknown')
940
949
  .action(async (opts) => {
941
950
  const globalOpts = program.opts();
942
951
  const client = getClient(globalOpts);
@@ -952,6 +961,7 @@ meetingsCmd
952
961
  if (opts.videoLink) body.video_link = opts.videoLink;
953
962
  if (opts.notes) body.notes = opts.notes;
954
963
  if (opts.outcome) body.outcome = opts.outcome;
964
+ if (opts.sentiment) body.sentiment = opts.sentiment;
955
965
  const res = await client.post('/meetings', body);
956
966
  printJSON(res.data);
957
967
  } catch (err) {
@@ -1010,6 +1020,7 @@ meetingsCmd
1010
1020
  .option('--video-link <url>', 'Video link')
1011
1021
  .option('--notes <notes>', 'Notes')
1012
1022
  .option('--outcome <outcome>', 'scheduled | completed | cancelled | no_show')
1023
+ .option('--sentiment <sentiment>', 'positive | neutral | negative | unknown')
1013
1024
  .action(async (id, opts) => {
1014
1025
  const globalOpts = program.opts();
1015
1026
  const client = getClient(globalOpts);
@@ -1022,6 +1033,7 @@ meetingsCmd
1022
1033
  if (opts.videoLink !== undefined) body.video_link = opts.videoLink;
1023
1034
  if (opts.notes !== undefined) body.notes = opts.notes;
1024
1035
  if (opts.outcome !== undefined) body.outcome = opts.outcome;
1036
+ if (opts.sentiment !== undefined) body.sentiment = opts.sentiment;
1025
1037
  const res = await client.patch(`/meetings/${id}`, body);
1026
1038
  printJSON(res.data);
1027
1039
  } catch (err) {
@@ -1061,9 +1073,9 @@ emailsCmd
1061
1073
  .requiredOption('--subject <subject>', 'Email subject line')
1062
1074
  .option('--contact-id <id>', 'Contact ID')
1063
1075
  .option('--lead-id <id>', 'Lead ID')
1064
- .option('--body <summary>', 'Body summary (max 1000 chars)')
1076
+ .option('--body-summary <summary>', 'Body summary (max 1000 chars)')
1065
1077
  .option('--thread-id <id>', 'Thread ID for grouping related emails')
1066
- .option('--email-time <iso>', 'When the email was sent/received (ISO8601, defaults to now)')
1078
+ .option('--email-timestamp <iso>', 'When the email was sent/received (ISO8601, defaults to now)')
1067
1079
  .action(async (opts) => {
1068
1080
  const globalOpts = program.opts();
1069
1081
  const client = getClient(globalOpts);
@@ -1074,9 +1086,9 @@ emailsCmd
1074
1086
  };
1075
1087
  if (opts.contactId) body.contact_id = opts.contactId;
1076
1088
  if (opts.leadId) body.lead_id = opts.leadId;
1077
- if (opts.body) body.body_summary = opts.body;
1089
+ if (opts.bodySummary) body.body_summary = opts.bodySummary;
1078
1090
  if (opts.threadId) body.thread_id = opts.threadId;
1079
- if (opts.emailTime) body.email_timestamp = opts.emailTime;
1091
+ if (opts.emailTimestamp) body.email_timestamp = opts.emailTimestamp;
1080
1092
  const res = await client.post('/emails', body);
1081
1093
  printJSON(res.data);
1082
1094
  } catch (err) {
@@ -1142,4 +1154,744 @@ emailsCmd
1142
1154
  });
1143
1155
 
1144
1156
 
1157
+
1158
+ // ============================================================
1159
+ // QUOTES commands
1160
+ // ============================================================
1161
+ const quotesCmd = program.command('quotes').description('Manage quotes');
1162
+
1163
+ quotesCmd
1164
+ .command('create')
1165
+ .description('Create a new quote')
1166
+ .requiredOption('--title <title>', 'Quote title')
1167
+ .option('--lead-id <id>', 'Lead ID to link')
1168
+ .option('--contact-id <id>', 'Contact ID to link')
1169
+ .option('--currency <code>', 'Currency code (default: USD)')
1170
+ .option('--validity-date <date>', 'Validity date (YYYY-MM-DD)')
1171
+ .option('--notes <notes>', 'Notes')
1172
+ .action(async (opts) => {
1173
+ const globalOpts = program.opts();
1174
+ const client = getClient(globalOpts);
1175
+ try {
1176
+
1177
+ const res = await client.post('/quotes', {
1178
+ title: opts.title,
1179
+ lead_id: opts.leadId,
1180
+ contact_id: opts.contactId,
1181
+ currency: opts.currency,
1182
+ validity_date: opts.validityDate,
1183
+ notes: opts.notes
1184
+ });
1185
+ printJSON(res.data);
1186
+
1187
+ } catch (err) {
1188
+ handleError(err);
1189
+ }
1190
+ });
1191
+
1192
+ quotesCmd
1193
+ .command('list')
1194
+ .description('List quotes')
1195
+ .option('--status <status>', 'Filter by status (draft/sent/accepted/rejected/expired)')
1196
+ .option('--lead-id <id>', 'Filter by lead ID')
1197
+ .option('--contact-id <id>', 'Filter by contact ID')
1198
+ .action(async (opts) => {
1199
+ const globalOpts = program.opts();
1200
+ const client = getClient(globalOpts);
1201
+ try {
1202
+
1203
+ const params = {};
1204
+ if (opts.status) params.status = opts.status;
1205
+ if (opts.leadId) params.lead_id = opts.leadId;
1206
+ if (opts.contactId) params.contact_id = opts.contactId;
1207
+ const res = await client.get('/quotes', { params });
1208
+ printJSON(res.data);
1209
+
1210
+ } catch (err) {
1211
+ handleError(err);
1212
+ }
1213
+ });
1214
+
1215
+ quotesCmd
1216
+ .command('get <id>')
1217
+ .description('Get a quote with all line items and totals')
1218
+ .action(async (id) => {
1219
+ const globalOpts = program.opts();
1220
+ const client = getClient(globalOpts);
1221
+ try {
1222
+
1223
+ const res = await client.get(`/quotes/${id}`);
1224
+ printJSON(res.data);
1225
+
1226
+ } catch (err) {
1227
+ handleError(err);
1228
+ }
1229
+ });
1230
+
1231
+ quotesCmd
1232
+ .command('update <id>')
1233
+ .description('Update quote metadata')
1234
+ .option('--title <title>', 'New title')
1235
+ .option('--currency <code>', 'Currency code')
1236
+ .option('--validity-date <date>', 'Validity date (YYYY-MM-DD)')
1237
+ .option('--notes <notes>', 'Notes')
1238
+ .action(async (id, opts) => {
1239
+ const globalOpts = program.opts();
1240
+ const client = getClient(globalOpts);
1241
+ try {
1242
+
1243
+ const body = {};
1244
+ if (opts.title) body.title = opts.title;
1245
+ if (opts.currency) body.currency = opts.currency;
1246
+ if (opts.validityDate) body.validity_date = opts.validityDate;
1247
+ if (opts.notes) body.notes = opts.notes;
1248
+ const res = await client.patch(`/quotes/${id}`, body);
1249
+ printJSON(res.data);
1250
+
1251
+ } catch (err) {
1252
+ handleError(err);
1253
+ }
1254
+ });
1255
+
1256
+ quotesCmd
1257
+ .command('add-line <quote-id>')
1258
+ .description('Add a line item to a quote')
1259
+ .requiredOption('--description <desc>', 'Line item description')
1260
+ .requiredOption('--unit-price <price>', 'Unit price', parseFloat)
1261
+ .option('--quantity <qty>', 'Quantity (default: 1)', parseFloat)
1262
+ .option('--discount <pct>', 'Discount percentage 0-100 (default: 0)', parseFloat)
1263
+ .option('--sort-order <n>', 'Sort order (default: 0)', parseInt)
1264
+ .action(async (quoteId, opts) => {
1265
+ const globalOpts = program.opts();
1266
+ const client = getClient(globalOpts);
1267
+ try {
1268
+
1269
+ const res = await client.post(`/quotes/${quoteId}/lines`, {
1270
+ description: opts.description,
1271
+ unit_price: opts.unitPrice,
1272
+ quantity: opts.quantity || 1,
1273
+ discount_percentage: opts.discount || 0,
1274
+ sort_order: opts.sortOrder || 0
1275
+ });
1276
+ printJSON(res.data);
1277
+
1278
+ } catch (err) {
1279
+ handleError(err);
1280
+ }
1281
+ });
1282
+
1283
+ quotesCmd
1284
+ .command('remove-line <quote-id> <line-id>')
1285
+ .description('Remove a line item from a quote')
1286
+ .action(async (quoteId, lineId) => {
1287
+ const globalOpts = program.opts();
1288
+ const client = getClient(globalOpts);
1289
+ try {
1290
+
1291
+ const res = await client.delete(`/quotes/${quoteId}/lines/${lineId}`);
1292
+ printJSON(res.data);
1293
+
1294
+ } catch (err) {
1295
+ handleError(err);
1296
+ }
1297
+ });
1298
+
1299
+ quotesCmd
1300
+ .command('send <id>')
1301
+ .description('Mark a quote as sent')
1302
+ .action(async (id) => {
1303
+ const globalOpts = program.opts();
1304
+ const client = getClient(globalOpts);
1305
+ try {
1306
+
1307
+ const res = await client.post(`/quotes/${id}/status`, { status: 'sent' });
1308
+ printJSON(res.data);
1309
+
1310
+ } catch (err) {
1311
+ handleError(err);
1312
+ }
1313
+ });
1314
+
1315
+ quotesCmd
1316
+ .command('accept <id>')
1317
+ .description('Mark a quote as accepted (auto-creates draft contract)')
1318
+ .action(async (id) => {
1319
+ const globalOpts = program.opts();
1320
+ const client = getClient(globalOpts);
1321
+ try {
1322
+
1323
+ const res = await client.post(`/quotes/${id}/status`, { status: 'accepted' });
1324
+ printJSON(res.data);
1325
+
1326
+ } catch (err) {
1327
+ handleError(err);
1328
+ }
1329
+ });
1330
+
1331
+ quotesCmd
1332
+ .command('reject <id>')
1333
+ .description('Mark a quote as rejected')
1334
+ .action(async (id) => {
1335
+ const globalOpts = program.opts();
1336
+ const client = getClient(globalOpts);
1337
+ try {
1338
+
1339
+ const res = await client.post(`/quotes/${id}/status`, { status: 'rejected' });
1340
+ printJSON(res.data);
1341
+
1342
+ } catch (err) {
1343
+ handleError(err);
1344
+ }
1345
+ });
1346
+
1347
+ quotesCmd
1348
+ .command('version <id>')
1349
+ .description('Create a new version of a quote (copies all line items)')
1350
+ .action(async (id) => {
1351
+ const globalOpts = program.opts();
1352
+ const client = getClient(globalOpts);
1353
+ try {
1354
+
1355
+ const res = await client.post(`/quotes/${id}/version`);
1356
+ printJSON(res.data);
1357
+
1358
+ } catch (err) {
1359
+ handleError(err);
1360
+ }
1361
+ });
1362
+
1363
+ quotesCmd
1364
+ .command('delete <id>')
1365
+ .description('Soft-delete a quote')
1366
+ .action(async (id) => {
1367
+ const globalOpts = program.opts();
1368
+ const client = getClient(globalOpts);
1369
+ try {
1370
+
1371
+ const res = await client.delete(`/quotes/${id}`);
1372
+ printJSON(res.data);
1373
+
1374
+ } catch (err) {
1375
+ handleError(err);
1376
+ }
1377
+ });
1378
+
1379
+ // ============================================================
1380
+ // CONTRACTS commands
1381
+ // ============================================================
1382
+ const contractsCmd = program.command('contracts').description('Manage contracts');
1383
+
1384
+ contractsCmd
1385
+ .command('create')
1386
+ .description('Create a new contract')
1387
+ .requiredOption('--title <title>', 'Contract title')
1388
+ .option('--lead-id <id>', 'Lead ID to link')
1389
+ .option('--contact-id <id>', 'Contact ID to link')
1390
+ .option('--quote-id <id>', 'Quote ID to link')
1391
+ .option('--value <amount>', 'Contract value', parseFloat)
1392
+ .option('--currency <code>', 'Currency code (default: USD)')
1393
+ .option('--start-date <date>', 'Start date (YYYY-MM-DD)')
1394
+ .option('--end-date <date>', 'End date (YYYY-MM-DD)')
1395
+ .option('--document-url <url>', 'Document URL reference')
1396
+ .option('--notes <notes>', 'Notes')
1397
+ .action(async (opts) => {
1398
+ const globalOpts = program.opts();
1399
+ const client = getClient(globalOpts);
1400
+ try {
1401
+
1402
+ const res = await client.post('/contracts', {
1403
+ title: opts.title,
1404
+ lead_id: opts.leadId,
1405
+ contact_id: opts.contactId,
1406
+ quote_id: opts.quoteId,
1407
+ value: opts.value,
1408
+ currency: opts.currency,
1409
+ start_date: opts.startDate,
1410
+ end_date: opts.endDate,
1411
+ document_url: opts.documentUrl,
1412
+ notes: opts.notes
1413
+ });
1414
+ printJSON(res.data);
1415
+
1416
+ } catch (err) {
1417
+ handleError(err);
1418
+ }
1419
+ });
1420
+
1421
+ contractsCmd
1422
+ .command('list')
1423
+ .description('List contracts')
1424
+ .option('--status <status>', 'Filter by status (draft/sent/signed/expired/void)')
1425
+ .option('--lead-id <id>', 'Filter by lead ID')
1426
+ .option('--contact-id <id>', 'Filter by contact ID')
1427
+ .option('--quote-id <id>', 'Filter by quote ID')
1428
+ .action(async (opts) => {
1429
+ const globalOpts = program.opts();
1430
+ const client = getClient(globalOpts);
1431
+ try {
1432
+
1433
+ const params = {};
1434
+ if (opts.status) params.status = opts.status;
1435
+ if (opts.leadId) params.lead_id = opts.leadId;
1436
+ if (opts.contactId) params.contact_id = opts.contactId;
1437
+ if (opts.quoteId) params.quote_id = opts.quoteId;
1438
+ const res = await client.get('/contracts', { params });
1439
+ printJSON(res.data);
1440
+
1441
+ } catch (err) {
1442
+ handleError(err);
1443
+ }
1444
+ });
1445
+
1446
+ contractsCmd
1447
+ .command('get <id>')
1448
+ .description('Get a contract by ID')
1449
+ .action(async (id) => {
1450
+ const globalOpts = program.opts();
1451
+ const client = getClient(globalOpts);
1452
+ try {
1453
+
1454
+ const res = await client.get(`/contracts/${id}`);
1455
+ printJSON(res.data);
1456
+
1457
+ } catch (err) {
1458
+ handleError(err);
1459
+ }
1460
+ });
1461
+
1462
+ contractsCmd
1463
+ .command('update <id>')
1464
+ .description('Update contract metadata')
1465
+ .option('--title <title>', 'New title')
1466
+ .option('--value <amount>', 'Contract value', parseFloat)
1467
+ .option('--currency <code>', 'Currency code')
1468
+ .option('--start-date <date>', 'Start date')
1469
+ .option('--end-date <date>', 'End date')
1470
+ .option('--notes <notes>', 'Notes')
1471
+ .action(async (id, opts) => {
1472
+ const globalOpts = program.opts();
1473
+ const client = getClient(globalOpts);
1474
+ try {
1475
+
1476
+ const body = {};
1477
+ if (opts.title) body.title = opts.title;
1478
+ if (opts.value !== undefined) body.value = opts.value;
1479
+ if (opts.currency) body.currency = opts.currency;
1480
+ if (opts.startDate) body.start_date = opts.startDate;
1481
+ if (opts.endDate) body.end_date = opts.endDate;
1482
+ if (opts.notes) body.notes = opts.notes;
1483
+ const res = await client.patch(`/contracts/${id}`, body);
1484
+ printJSON(res.data);
1485
+
1486
+ } catch (err) {
1487
+ handleError(err);
1488
+ }
1489
+ });
1490
+
1491
+ contractsCmd
1492
+ .command('send <id>')
1493
+ .description('Mark a contract as sent')
1494
+ .action(async (id) => {
1495
+ const globalOpts = program.opts();
1496
+ const client = getClient(globalOpts);
1497
+ try {
1498
+
1499
+ const res = await client.post(`/contracts/${id}/status`, { status: 'sent' });
1500
+ printJSON(res.data);
1501
+
1502
+ } catch (err) {
1503
+ handleError(err);
1504
+ }
1505
+ });
1506
+
1507
+ contractsCmd
1508
+ .command('sign <id>')
1509
+ .description('Sign a contract (auto-moves linked lead to closed_won)')
1510
+ .option('--signed-by <name>', 'Name of signatory')
1511
+ .action(async (id, opts) => {
1512
+ const globalOpts = program.opts();
1513
+ const client = getClient(globalOpts);
1514
+ try {
1515
+
1516
+ const res = await client.post(`/contracts/${id}/status`, { status: 'signed', signed_by: opts.signedBy });
1517
+ printJSON(res.data);
1518
+
1519
+ } catch (err) {
1520
+ handleError(err);
1521
+ }
1522
+ });
1523
+
1524
+ contractsCmd
1525
+ .command('void <id>')
1526
+ .description('Void a contract')
1527
+ .action(async (id) => {
1528
+ const globalOpts = program.opts();
1529
+ const client = getClient(globalOpts);
1530
+ try {
1531
+
1532
+ const res = await client.post(`/contracts/${id}/status`, { status: 'void' });
1533
+ printJSON(res.data);
1534
+
1535
+ } catch (err) {
1536
+ handleError(err);
1537
+ }
1538
+ });
1539
+
1540
+ contractsCmd
1541
+ .command('attach-doc <id>')
1542
+ .description('Attach or update document URL on a contract')
1543
+ .requiredOption('--url <url>', 'Document URL')
1544
+ .action(async (id, opts) => {
1545
+ const globalOpts = program.opts();
1546
+ const client = getClient(globalOpts);
1547
+ try {
1548
+
1549
+ const res = await client.patch(`/contracts/${id}/document`, { document_url: opts.url });
1550
+ printJSON(res.data);
1551
+
1552
+ } catch (err) {
1553
+ handleError(err);
1554
+ }
1555
+ });
1556
+
1557
+ contractsCmd
1558
+ .command('delete <id>')
1559
+ .description('Soft-delete a contract')
1560
+ .action(async (id) => {
1561
+ const globalOpts = program.opts();
1562
+ const client = getClient(globalOpts);
1563
+ try {
1564
+
1565
+ const res = await client.delete(`/contracts/${id}`);
1566
+ printJSON(res.data);
1567
+
1568
+ } catch (err) {
1569
+ handleError(err);
1570
+ }
1571
+ });
1572
+
1573
+
1574
+ // ============================================================
1575
+ // BUDGETS commands — Sprint 5
1576
+ // ============================================================
1577
+ const budgetsCmd = program.command('budgets').description('Manage sales budgets');
1578
+
1579
+ budgetsCmd
1580
+ .command('create')
1581
+ .description('Create a new budget allocation')
1582
+ .requiredOption('--name <name>', 'Budget name')
1583
+ .requiredOption('--amount <amount>', 'Total budget amount', parseFloat)
1584
+ .requiredOption('--period-start <date>', 'Period start date (YYYY-MM-DD)')
1585
+ .requiredOption('--period-end <date>', 'Period end date (YYYY-MM-DD)')
1586
+ .option('--period-type <type>', 'Period type: monthly, quarterly, annual, custom (default: custom)')
1587
+ .option('--currency <code>', 'Currency code (default: USD)')
1588
+ .option('--owner-key <key>', 'API key of budget owner')
1589
+ .option('--tags <tags>', 'Comma-separated tags')
1590
+ .action(async (opts) => {
1591
+ const globalOpts = program.opts();
1592
+ const client = getClient(globalOpts);
1593
+ try {
1594
+ const res = await client.post('/budgets', {
1595
+ name: opts.name,
1596
+ total_amount: opts.amount,
1597
+ period_start: opts.periodStart,
1598
+ period_end: opts.periodEnd,
1599
+ period_type: opts.periodType || 'custom',
1600
+ currency: opts.currency,
1601
+ owner_key: opts.ownerKey,
1602
+ tags: opts.tags ? opts.tags.split(',').map(t => t.trim()) : undefined
1603
+ });
1604
+ printJSON(res.data);
1605
+ } catch (err) {
1606
+ handleError(err);
1607
+ }
1608
+ });
1609
+
1610
+ budgetsCmd
1611
+ .command('list')
1612
+ .description('List budgets with spend summary')
1613
+ .option('--status <status>', 'Filter by status (active/paused/exhausted/archived)')
1614
+ .option('--period-type <type>', 'Filter by period type')
1615
+ .action(async (opts) => {
1616
+ const globalOpts = program.opts();
1617
+ const client = getClient(globalOpts);
1618
+ try {
1619
+ const params = {};
1620
+ if (opts.status) params.status = opts.status;
1621
+ if (opts.periodType) params.period_type = opts.periodType;
1622
+ const res = await client.get('/budgets', { params });
1623
+ printJSON(res.data);
1624
+ } catch (err) {
1625
+ handleError(err);
1626
+ }
1627
+ });
1628
+
1629
+ budgetsCmd
1630
+ .command('get <id>')
1631
+ .description('Get a budget with line items and spend summary')
1632
+ .action(async (id) => {
1633
+ const globalOpts = program.opts();
1634
+ const client = getClient(globalOpts);
1635
+ try {
1636
+ const res = await client.get(`/budgets/${id}`);
1637
+ printJSON(res.data);
1638
+ } catch (err) {
1639
+ handleError(err);
1640
+ }
1641
+ });
1642
+
1643
+ budgetsCmd
1644
+ .command('update <id>')
1645
+ .description('Update budget metadata')
1646
+ .option('--name <name>', 'New name')
1647
+ .option('--amount <amount>', 'New total amount', parseFloat)
1648
+ .option('--status <status>', 'New status (active/paused/exhausted/archived)')
1649
+ .option('--period-start <date>', 'New period start')
1650
+ .option('--period-end <date>', 'New period end')
1651
+ .option('--currency <code>', 'Currency code')
1652
+ .action(async (id, opts) => {
1653
+ const globalOpts = program.opts();
1654
+ const client = getClient(globalOpts);
1655
+ try {
1656
+ const body = {};
1657
+ if (opts.name) body.name = opts.name;
1658
+ if (opts.amount !== undefined) body.total_amount = opts.amount;
1659
+ if (opts.status) body.status = opts.status;
1660
+ if (opts.periodStart) body.period_start = opts.periodStart;
1661
+ if (opts.periodEnd) body.period_end = opts.periodEnd;
1662
+ if (opts.currency) body.currency = opts.currency;
1663
+ const res = await client.patch(`/budgets/${id}`, body);
1664
+ printJSON(res.data);
1665
+ } catch (err) {
1666
+ handleError(err);
1667
+ }
1668
+ });
1669
+
1670
+ budgetsCmd
1671
+ .command('spend <id>')
1672
+ .description('Log a spend event against a budget')
1673
+ .requiredOption('--description <desc>', 'What was purchased')
1674
+ .requiredOption('--amount <amount>', 'Spend amount', parseFloat)
1675
+ .requiredOption('--date <date>', 'Transaction date (YYYY-MM-DD)')
1676
+ .option('--category <cat>', 'Category: data_enrichment, advertising, events, tools, other (default: other)')
1677
+ .option('--lead-id <id>', 'Link to a lead')
1678
+ .option('--reference <ref>', 'Invoice or order reference')
1679
+ .action(async (id, opts) => {
1680
+ const globalOpts = program.opts();
1681
+ const client = getClient(globalOpts);
1682
+ try {
1683
+ const res = await client.post(`/budgets/${id}/spend`, {
1684
+ description: opts.description,
1685
+ amount: opts.amount,
1686
+ transaction_date: opts.date,
1687
+ category: opts.category || 'other',
1688
+ lead_id: opts.leadId,
1689
+ reference: opts.reference
1690
+ });
1691
+ printJSON(res.data);
1692
+ } catch (err) {
1693
+ handleError(err);
1694
+ }
1695
+ });
1696
+
1697
+ budgetsCmd
1698
+ .command('line-items <id>')
1699
+ .description('List spend line items for a budget')
1700
+ .option('--category <cat>', 'Filter by category')
1701
+ .action(async (id, opts) => {
1702
+ const globalOpts = program.opts();
1703
+ const client = getClient(globalOpts);
1704
+ try {
1705
+ const params = {};
1706
+ if (opts.category) params.category = opts.category;
1707
+ const res = await client.get(`/budgets/${id}/line-items`, { params });
1708
+ printJSON(res.data);
1709
+ } catch (err) {
1710
+ handleError(err);
1711
+ }
1712
+ });
1713
+
1714
+ budgetsCmd
1715
+ .command('check <id>')
1716
+ .description('Check if a spend amount is approved (APPROVED/INSUFFICIENT_FUNDS)')
1717
+ .requiredOption('--amount <amount>', 'Amount to check', parseFloat)
1718
+ .action(async (id, opts) => {
1719
+ const globalOpts = program.opts();
1720
+ const client = getClient(globalOpts);
1721
+ try {
1722
+ const res = await client.get(`/budgets/${id}/check`, { params: { amount: opts.amount } });
1723
+ printJSON(res.data);
1724
+ } catch (err) {
1725
+ handleError(err);
1726
+ }
1727
+ });
1728
+
1729
+ // ============================================================
1730
+ // TARGETS commands — Sprint 5
1731
+ // ============================================================
1732
+ const targetsCmd = program.command('targets').description('Manage sales targets');
1733
+
1734
+ targetsCmd
1735
+ .command('create')
1736
+ .description('Create a new sales target')
1737
+ .requiredOption('--name <name>', 'Target name')
1738
+ .requiredOption('--metric <metric>', 'Metric: calls_made, emails_sent, meetings_booked, leads_qualified, revenue_closed, deals_closed, contracts_signed, or any custom')
1739
+ .requiredOption('--goal <value>', 'Goal value', parseFloat)
1740
+ .requiredOption('--period-start <date>', 'Period start date (YYYY-MM-DD)')
1741
+ .requiredOption('--period-end <date>', 'Period end date (YYYY-MM-DD)')
1742
+ .option('--type <type>', 'Target type: activity, revenue, pipeline, conversion (default: activity)')
1743
+ .option('--period-type <type>', 'Period type: daily, weekly, monthly, quarterly, annual (default: monthly)')
1744
+ .option('--unit <unit>', 'Unit label: calls, USD, leads, % (default: count)')
1745
+ .option('--owner-key <key>', 'API key owner of this target')
1746
+ .option('--tags <tags>', 'Comma-separated tags')
1747
+ .action(async (opts) => {
1748
+ const globalOpts = program.opts();
1749
+ const client = getClient(globalOpts);
1750
+ try {
1751
+ const res = await client.post('/targets', {
1752
+ name: opts.name,
1753
+ metric: opts.metric,
1754
+ goal_value: opts.goal,
1755
+ period_start: opts.periodStart,
1756
+ period_end: opts.periodEnd,
1757
+ target_type: opts.type || 'activity',
1758
+ period_type: opts.periodType || 'monthly',
1759
+ unit: opts.unit || 'count',
1760
+ owner_key: opts.ownerKey,
1761
+ tags: opts.tags ? opts.tags.split(',').map(t => t.trim()) : undefined
1762
+ });
1763
+ printJSON(res.data);
1764
+ } catch (err) {
1765
+ handleError(err);
1766
+ }
1767
+ });
1768
+
1769
+ targetsCmd
1770
+ .command('list')
1771
+ .description('List targets with live-computed actuals and status')
1772
+ .option('--status <status>', 'Filter by status: on_track, at_risk, behind, achieved, failed')
1773
+ .option('--type <type>', 'Filter by target_type')
1774
+ .option('--metric <metric>', 'Filter by metric')
1775
+ .option('--owner-key <key>', 'Filter by owner API key')
1776
+ .action(async (opts) => {
1777
+ const globalOpts = program.opts();
1778
+ const client = getClient(globalOpts);
1779
+ try {
1780
+ const params = {};
1781
+ if (opts.status) params.status = opts.status;
1782
+ if (opts.type) params.target_type = opts.type;
1783
+ if (opts.metric) params.metric = opts.metric;
1784
+ if (opts.ownerKey) params.owner_key = opts.ownerKey;
1785
+ const res = await client.get('/targets', { params });
1786
+ printJSON(res.data);
1787
+ } catch (err) {
1788
+ handleError(err);
1789
+ }
1790
+ });
1791
+
1792
+ targetsCmd
1793
+ .command('get <id>')
1794
+ .description('Get a target with full breakdown: current, remaining, attainment %, days remaining')
1795
+ .action(async (id) => {
1796
+ const globalOpts = program.opts();
1797
+ const client = getClient(globalOpts);
1798
+ try {
1799
+ const res = await client.get(`/targets/${id}`);
1800
+ printJSON(res.data);
1801
+ } catch (err) {
1802
+ handleError(err);
1803
+ }
1804
+ });
1805
+
1806
+ targetsCmd
1807
+ .command('update <id>')
1808
+ .description('Update a target')
1809
+ .option('--name <name>', 'New name')
1810
+ .option('--goal <value>', 'New goal value', parseFloat)
1811
+ .option('--period-start <date>', 'New period start')
1812
+ .option('--period-end <date>', 'New period end')
1813
+ .option('--unit <unit>', 'New unit label')
1814
+ .option('--owner-key <key>', 'New owner API key')
1815
+ .action(async (id, opts) => {
1816
+ const globalOpts = program.opts();
1817
+ const client = getClient(globalOpts);
1818
+ try {
1819
+ const body = {};
1820
+ if (opts.name) body.name = opts.name;
1821
+ if (opts.goal !== undefined) body.goal_value = opts.goal;
1822
+ if (opts.periodStart) body.period_start = opts.periodStart;
1823
+ if (opts.periodEnd) body.period_end = opts.periodEnd;
1824
+ if (opts.unit) body.unit = opts.unit;
1825
+ if (opts.ownerKey) body.owner_key = opts.ownerKey;
1826
+ const res = await client.patch(`/targets/${id}`, body);
1827
+ printJSON(res.data);
1828
+ } catch (err) {
1829
+ handleError(err);
1830
+ }
1831
+ });
1832
+
1833
+ targetsCmd
1834
+ .command('progress <id>')
1835
+ .description('Log manual progress toward a target')
1836
+ .requiredOption('--value <n>', 'Progress value to add', parseFloat)
1837
+ .option('--source <src>', 'Source: manual, auto_calls, auto_emails, auto_meetings, auto_leads (default: manual)')
1838
+ .option('--note <note>', 'Context note')
1839
+ .option('--logged-at <iso>', 'When this progress happened (ISO8601, defaults to now)')
1840
+ .action(async (id, opts) => {
1841
+ const globalOpts = program.opts();
1842
+ const client = getClient(globalOpts);
1843
+ try {
1844
+ const res = await client.post(`/targets/${id}/progress`, {
1845
+ value: opts.value,
1846
+ source: opts.source || 'manual',
1847
+ note: opts.note,
1848
+ logged_at: opts.loggedAt
1849
+ });
1850
+ printJSON(res.data);
1851
+ } catch (err) {
1852
+ handleError(err);
1853
+ }
1854
+ });
1855
+
1856
+ targetsCmd
1857
+ .command('dashboard')
1858
+ .description('Show all active targets with attainment % and status summary')
1859
+ .option('--owner-key <key>', 'Filter by owner API key')
1860
+ .action(async (opts) => {
1861
+ const globalOpts = program.opts();
1862
+ const client = getClient(globalOpts);
1863
+ try {
1864
+ const params = {};
1865
+ if (opts.ownerKey) params.owner_key = opts.ownerKey;
1866
+ const res = await client.get('/targets/dashboard', { params });
1867
+ printJSON(res.data);
1868
+ } catch (err) {
1869
+ handleError(err);
1870
+ }
1871
+ });
1872
+
1873
+ // ============================================================
1874
+ // PERFORMANCE REPORT command — Sprint 5
1875
+ // ============================================================
1876
+ program
1877
+ .command('performance')
1878
+ .description('Combined performance report: all targets + budgets in one JSON response')
1879
+ .option('--period-start <date>', 'Filter from date (YYYY-MM-DD)')
1880
+ .option('--period-end <date>', 'Filter to date (YYYY-MM-DD)')
1881
+ .option('--owner-key <key>', 'Filter by owner API key')
1882
+ .action(async (opts) => {
1883
+ const globalOpts = program.opts();
1884
+ const client = getClient(globalOpts);
1885
+ try {
1886
+ const params = {};
1887
+ if (opts.periodStart) params.period_start = opts.periodStart;
1888
+ if (opts.periodEnd) params.period_end = opts.periodEnd;
1889
+ if (opts.ownerKey) params.owner_key = opts.ownerKey;
1890
+ const res = await client.get('/performance', { params });
1891
+ printJSON(res.data);
1892
+ } catch (err) {
1893
+ handleError(err);
1894
+ }
1895
+ });
1896
+
1145
1897
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "2.1.0",
3
+ "version": "2.3.2",
4
4
  "description": "AI-first CRM CLI \u2014 manage leads and API keys from the terminal",
5
5
  "main": "index.js",
6
6
  "bin": {