@startanaicompany/crm 2.1.0 → 2.3.1

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