@startanaicompany/crm 2.3.2 → 2.4.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 +149 -5
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -96,10 +96,12 @@ function promptSecret(question) {
96
96
 
97
97
  const program = new Command();
98
98
 
99
+ const { version: pkgVersion } = require('./package.json');
100
+
99
101
  program
100
102
  .name('saac_crm')
101
103
  .description('AI-first CRM CLI — manage leads and API keys')
102
- .version('2.3.0')
104
+ .version(pkgVersion)
103
105
  .option('--api-key <key>', 'API key (overrides SAAC_CRM_API_KEY env and config)')
104
106
  .option('--url <url>', 'API base URL (overrides config)');
105
107
 
@@ -1506,16 +1508,158 @@ contractsCmd
1506
1508
 
1507
1509
  contractsCmd
1508
1510
  .command('sign <id>')
1509
- .description('Sign a contract (auto-moves linked lead to closed_won)')
1510
- .option('--signed-by <name>', 'Name of signatory')
1511
+ .description('Mark a specific signatory as signed (use --contact-id for multi-signatory; falls back to whole-contract sign if omitted)')
1512
+ .option('--contact-id <uuid>', 'Contact UUID of the signatory to mark as signed')
1513
+ .option('--method <method>', 'Signature method: electronic, wet_signature, docusign, hellosign, other')
1514
+ .option('--signed-at <datetime>', 'ISO8601 timestamp of signing (defaults to now)')
1515
+ .option('--signed-by <name>', 'Legacy: name of signatory (used when no --contact-id)')
1511
1516
  .action(async (id, opts) => {
1512
1517
  const globalOpts = program.opts();
1513
1518
  const client = getClient(globalOpts);
1514
1519
  try {
1520
+ if (opts.contactId) {
1521
+ // Sprint 8: per-signatory sign via PATCH /contracts/:id/signatories/:signatory_id
1522
+ // First find the signatory record for this contact
1523
+ const listRes = await client.get(`/contracts/${id}/signatories`);
1524
+ const signatories = listRes.data.data.signatories || [];
1525
+ const sig = signatories.find(s => s.contact_id === opts.contactId);
1526
+ if (!sig) {
1527
+ console.error(`No signatory found for contact ${opts.contactId} on contract ${id}`);
1528
+ process.exit(1);
1529
+ }
1530
+ const res = await client.patch(`/contracts/${id}/signatories/${sig.id}`, {
1531
+ status: 'signed',
1532
+ signature_method: opts.method || 'electronic',
1533
+ signed_at: opts.signedAt || new Date().toISOString(),
1534
+ });
1535
+ printJSON(res.data);
1536
+ } else {
1537
+ // Legacy whole-contract sign
1538
+ const res = await client.post(`/contracts/${id}/status`, { status: 'signed', signed_by: opts.signedBy });
1539
+ printJSON(res.data);
1540
+ }
1541
+ } catch (err) {
1542
+ handleError(err);
1543
+ }
1544
+ });
1545
+
1546
+ // Sprint 8: Multi-Signatory Commands
1547
+
1548
+ contractsCmd
1549
+ .command('signatories <id>')
1550
+ .description('List all signatories for a contract with status and timestamps')
1551
+ .action(async (id) => {
1552
+ const globalOpts = program.opts();
1553
+ const client = getClient(globalOpts);
1554
+ try {
1555
+ const res = await client.get(`/contracts/${id}/signatories`);
1556
+ const { signatories, summary } = res.data.data;
1557
+ console.log(`\nSignatories for contract ${id}`);
1558
+ console.log(`Summary: ${summary.signed}/${summary.total} signed | ${summary.pending} pending | ${summary.declined} declined`);
1559
+ if (summary.is_fully_executed) console.log('✅ Contract fully executed');
1560
+ console.log('');
1561
+ if (signatories.length === 0) {
1562
+ console.log('No signatories added yet.');
1563
+ } else {
1564
+ signatories.forEach(s => {
1565
+ const icon = s.status === 'signed' ? '✅' : s.status === 'declined' ? '⛔' : '⏳';
1566
+ const signedAt = s.signed_at ? ` | Signed: ${new Date(s.signed_at).toLocaleDateString()}` : '';
1567
+ const reminded = s.reminder_sent_at ? ` | Last reminded: ${new Date(s.reminder_sent_at).toLocaleDateString()}` : '';
1568
+ console.log(` ${icon} [${s.party}] ${s.contact_name} <${s.contact_email}> — ${s.role} | ${s.status}${signedAt}${reminded}`);
1569
+ if (s.notes) console.log(` Notes: ${s.notes}`);
1570
+ });
1571
+ }
1572
+ } catch (err) {
1573
+ handleError(err);
1574
+ }
1575
+ });
1515
1576
 
1516
- const res = await client.post(`/contracts/${id}/status`, { status: 'signed', signed_by: opts.signedBy });
1577
+ contractsCmd
1578
+ .command('add-signatory <id>')
1579
+ .description('Add a signatory to a contract')
1580
+ .requiredOption('--contact-id <uuid>', 'Contact UUID of the signatory')
1581
+ .requiredOption('--party <party>', 'Party: vendor or customer')
1582
+ .requiredOption('--role <role>', 'Role: signer, approver, or witness')
1583
+ .option('--method <method>', 'Signature method: electronic, wet_signature, docusign, hellosign, other')
1584
+ .option('--notes <notes>', 'Internal notes about this signatory')
1585
+ .action(async (id, opts) => {
1586
+ const globalOpts = program.opts();
1587
+ const client = getClient(globalOpts);
1588
+ try {
1589
+ const res = await client.post(`/contracts/${id}/signatories`, {
1590
+ contact_id: opts.contactId,
1591
+ party: opts.party,
1592
+ role: opts.role,
1593
+ signature_method: opts.method || undefined,
1594
+ notes: opts.notes || undefined,
1595
+ });
1596
+ printJSON(res.data);
1597
+ } catch (err) {
1598
+ handleError(err);
1599
+ }
1600
+ });
1601
+
1602
+ contractsCmd
1603
+ .command('decline-signature <id>')
1604
+ .description('Mark a signatory as declined (deal blocker alert)')
1605
+ .requiredOption('--contact-id <uuid>', 'Contact UUID of the signatory declining')
1606
+ .option('--notes <notes>', 'Reason for declining')
1607
+ .action(async (id, opts) => {
1608
+ const globalOpts = program.opts();
1609
+ const client = getClient(globalOpts);
1610
+ try {
1611
+ const listRes = await client.get(`/contracts/${id}/signatories`);
1612
+ const signatories = listRes.data.data.signatories || [];
1613
+ const sig = signatories.find(s => s.contact_id === opts.contactId);
1614
+ if (!sig) {
1615
+ console.error(`No signatory found for contact ${opts.contactId} on contract ${id}`);
1616
+ process.exit(1);
1617
+ }
1618
+ const res = await client.patch(`/contracts/${id}/signatories/${sig.id}`, {
1619
+ status: 'declined',
1620
+ notes: opts.notes || undefined,
1621
+ });
1622
+ console.log('⛔ Signature declined — deal blocker raised.');
1623
+ printJSON(res.data);
1624
+ } catch (err) {
1625
+ handleError(err);
1626
+ }
1627
+ });
1628
+
1629
+ contractsCmd
1630
+ .command('remind-signatory <id>')
1631
+ .description('Send a reminder to a pending signatory')
1632
+ .requiredOption('--contact-id <uuid>', 'Contact UUID of the signatory to remind')
1633
+ .action(async (id, opts) => {
1634
+ const globalOpts = program.opts();
1635
+ const client = getClient(globalOpts);
1636
+ try {
1637
+ const listRes = await client.get(`/contracts/${id}/signatories`);
1638
+ const signatories = listRes.data.data.signatories || [];
1639
+ const sig = signatories.find(s => s.contact_id === opts.contactId);
1640
+ if (!sig) {
1641
+ console.error(`No signatory found for contact ${opts.contactId} on contract ${id}`);
1642
+ process.exit(1);
1643
+ }
1644
+ const res = await client.post(`/contracts/${id}/signatories/${sig.id}/remind`);
1645
+ console.log(`📩 Reminder sent to ${sig.contact_name} (${sig.contact_email})`);
1646
+ printJSON(res.data);
1647
+ } catch (err) {
1648
+ handleError(err);
1649
+ }
1650
+ });
1651
+
1652
+ contractsCmd
1653
+ .command('remind-all-pending <id>')
1654
+ .description('Send reminders to all pending signatories on a contract')
1655
+ .action(async (id) => {
1656
+ const globalOpts = program.opts();
1657
+ const client = getClient(globalOpts);
1658
+ try {
1659
+ const res = await client.post(`/contracts/${id}/signatories/remind-all-pending`);
1660
+ const { reminded } = res.data.data;
1661
+ console.log(`📩 Reminders sent to ${reminded} pending signator${reminded === 1 ? 'y' : 'ies'}.`);
1517
1662
  printJSON(res.data);
1518
-
1519
1663
  } catch (err) {
1520
1664
  handleError(err);
1521
1665
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "description": "AI-first CRM CLI \u2014 manage leads and API keys from the terminal",
5
5
  "main": "index.js",
6
6
  "bin": {