@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.
- package/index.js +149 -5
- 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(
|
|
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('
|
|
1510
|
-
.option('--
|
|
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
|
-
|
|
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
|
}
|