@startanaicompany/crm 2.5.2 → 2.7.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 +129 -16
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -395,7 +395,7 @@ leadsCmd
|
|
|
395
395
|
.option('--phone <phone>', 'Phone number')
|
|
396
396
|
.option('--company <company>', 'Company name')
|
|
397
397
|
.option('--status <status>', 'Status: new|contacted|qualified|unresponsive|converted|lost', 'new')
|
|
398
|
-
.option('--source <source>', 'Source: api|import|referral', 'api')
|
|
398
|
+
.option('--source <source>', 'Source: api|import|referral|linkedin|cold_email|website|inbound|partner|event|other', 'api')
|
|
399
399
|
.option('--deal-value <amount>', 'Deal value (numeric)')
|
|
400
400
|
.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')
|
|
401
401
|
.option('--notes <notes>', 'Notes')
|
|
@@ -438,9 +438,14 @@ leadsCmd
|
|
|
438
438
|
.option('--status <status>', 'Filter by status')
|
|
439
439
|
.option('--email <email>', 'Filter by email (partial match)')
|
|
440
440
|
.option('--company <company>', 'Filter by company (partial match)')
|
|
441
|
+
.option('--name <name>', 'Search by name or company (partial match)')
|
|
442
|
+
.option('--assigned-to <assignedTo>', 'Filter by assigned-to (partial match)')
|
|
443
|
+
.option('--pipeline-stage <stage>', 'Filter by pipeline stage (exact match)')
|
|
441
444
|
.option('--tag <tag>', 'Filter by tag — repeatable, AND logic', (v, prev) => prev.concat([v]), [])
|
|
442
445
|
.option('--api-key-id <id>', 'Filter by the API key ID that created the lead')
|
|
443
446
|
.option('--self', 'Filter leads created by the current API key (shorthand for --api-key-id self)')
|
|
447
|
+
.option('--sort-by <field>', 'Sort field: created_at|updated_at|deal_value|name|company|last_activity_at|status_changed_at')
|
|
448
|
+
.option('--order <dir>', 'Sort direction: asc|desc', 'desc')
|
|
444
449
|
.option('--created-after <date>', 'Filter leads created after this ISO8601 date (e.g. 2026-01-01T00:00:00Z)')
|
|
445
450
|
.option('--created-before <date>', 'Filter leads created before this ISO8601 date')
|
|
446
451
|
.option('--updated-after <date>', 'Filter leads updated after this ISO8601 date')
|
|
@@ -456,7 +461,12 @@ leadsCmd
|
|
|
456
461
|
...(opts.status && { status: opts.status }),
|
|
457
462
|
...(opts.email && { email: opts.email }),
|
|
458
463
|
...(opts.company && { company: opts.company }),
|
|
464
|
+
...(opts.name && { name: opts.name }),
|
|
465
|
+
...(opts.assignedTo && { assigned_to: opts.assignedTo }),
|
|
466
|
+
...(opts.pipelineStage && { pipeline_stage: opts.pipelineStage }),
|
|
459
467
|
...(apiKeyIdFilter && { api_key_id: apiKeyIdFilter }),
|
|
468
|
+
...(opts.sortBy && { sort_by: opts.sortBy }),
|
|
469
|
+
...(opts.order && { order: opts.order }),
|
|
460
470
|
...(opts.createdAfter && { created_after: opts.createdAfter }),
|
|
461
471
|
...(opts.createdBefore && { created_before: opts.createdBefore }),
|
|
462
472
|
...(opts.updatedAfter && { updated_after: opts.updatedAfter }),
|
|
@@ -475,6 +485,35 @@ leadsCmd
|
|
|
475
485
|
}
|
|
476
486
|
});
|
|
477
487
|
|
|
488
|
+
leadsCmd
|
|
489
|
+
.command('bulk-create')
|
|
490
|
+
.description('Bulk create up to 100 leads from a JSON file (207 Multi-Status)')
|
|
491
|
+
.requiredOption('--file <path>', 'Path to JSON file containing array of lead objects')
|
|
492
|
+
.option('--fail-fast', 'Stop processing on first failure', false)
|
|
493
|
+
.option('--from-agent-name <name>', 'Agent name for attribution (falls back to config defaultAgentName)')
|
|
494
|
+
.action(async (opts) => {
|
|
495
|
+
const globalOpts = program.opts();
|
|
496
|
+
const agentName = resolveAgentName(opts.fromAgentName);
|
|
497
|
+
const client = getClient(globalOpts, agentName);
|
|
498
|
+
let leads;
|
|
499
|
+
try {
|
|
500
|
+
const fs = require('fs');
|
|
501
|
+
const raw = fs.readFileSync(opts.file, 'utf8');
|
|
502
|
+
const parsed = JSON.parse(raw);
|
|
503
|
+
leads = Array.isArray(parsed) ? parsed : parsed.leads;
|
|
504
|
+
if (!Array.isArray(leads)) throw new Error('JSON must be an array or object with a "leads" array');
|
|
505
|
+
} catch (fileErr) {
|
|
506
|
+
console.error(`Error reading file: ${fileErr.message}`);
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
const res = await client.post('/leads/bulk', { leads, fail_fast: opts.failFast });
|
|
511
|
+
printJSON(res.data);
|
|
512
|
+
} catch (err) {
|
|
513
|
+
handleError(err);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
478
517
|
leadsCmd
|
|
479
518
|
.command('get <id>')
|
|
480
519
|
.description('Get a single lead by ID')
|
|
@@ -501,27 +540,46 @@ leadsCmd
|
|
|
501
540
|
.option('--deal-value <amount>', 'Deal value (numeric)')
|
|
502
541
|
.option('--notes <notes>', 'New notes')
|
|
503
542
|
.option('--assigned-to <assignedTo>', 'New assigned-to')
|
|
504
|
-
.option('--tag <tag>', 'Replace tags (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
543
|
+
.option('--tag <tag>', 'Replace ALL tags (repeatable). Use --add-tag / --remove-tag for surgical edits.', (v, prev) => prev.concat([v]), [])
|
|
544
|
+
.option('--add-tag <tag>', 'Add a single tag without replacing others (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
545
|
+
.option('--remove-tag <tag>', 'Remove a single tag (repeatable)', (v, prev) => prev.concat([v]), [])
|
|
505
546
|
.option('--external-id <externalId>', 'New external ID')
|
|
506
547
|
.option('--version <version>', 'Optimistic lock version')
|
|
507
548
|
.action(async (id, opts) => {
|
|
508
549
|
const globalOpts = program.opts();
|
|
509
550
|
const client = getClient(globalOpts);
|
|
510
|
-
const body = {
|
|
511
|
-
...(opts.name && { name: opts.name }),
|
|
512
|
-
...(opts.email && { email: opts.email }),
|
|
513
|
-
...(opts.phone !== undefined && { phone: opts.phone }),
|
|
514
|
-
...(opts.company !== undefined && { company: opts.company }),
|
|
515
|
-
...(opts.status && { status: opts.status }),
|
|
516
|
-
...(opts.source && { source: opts.source }),
|
|
517
|
-
...(opts.dealValue !== undefined && { deal_value: parseFloat(opts.dealValue) }),
|
|
518
|
-
...(opts.notes !== undefined && { notes: opts.notes }),
|
|
519
|
-
...(opts.assignedTo !== undefined && { assigned_to: opts.assignedTo }),
|
|
520
|
-
...(opts.tag.length > 0 && { tags: opts.tag }),
|
|
521
|
-
...(opts.externalId !== undefined && { external_id: opts.externalId }),
|
|
522
|
-
...(opts.version !== undefined && { version: parseInt(opts.version) })
|
|
523
|
-
};
|
|
524
551
|
try {
|
|
552
|
+
// Surgical tag ops: call PATCH /leads/:id/tags first if add/remove specified
|
|
553
|
+
if (opts.addTag.length > 0 || opts.removeTag.length > 0) {
|
|
554
|
+
const tagRes = await client.patch(`/leads/${id}/tags`, {
|
|
555
|
+
...(opts.addTag.length > 0 && { add_tags: opts.addTag }),
|
|
556
|
+
...(opts.removeTag.length > 0 && { remove_tags: opts.removeTag })
|
|
557
|
+
});
|
|
558
|
+
// If no other fields to update, return tag result
|
|
559
|
+
const hasOtherFields = opts.name || opts.email || opts.phone !== undefined ||
|
|
560
|
+
opts.company !== undefined || opts.status || opts.source ||
|
|
561
|
+
opts.dealValue !== undefined || opts.notes !== undefined ||
|
|
562
|
+
opts.assignedTo !== undefined || opts.tag.length > 0 ||
|
|
563
|
+
opts.externalId !== undefined;
|
|
564
|
+
if (!hasOtherFields) {
|
|
565
|
+
printJSON(tagRes.data);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const body = {
|
|
570
|
+
...(opts.name && { name: opts.name }),
|
|
571
|
+
...(opts.email && { email: opts.email }),
|
|
572
|
+
...(opts.phone !== undefined && { phone: opts.phone }),
|
|
573
|
+
...(opts.company !== undefined && { company: opts.company }),
|
|
574
|
+
...(opts.status && { status: opts.status }),
|
|
575
|
+
...(opts.source && { source: opts.source }),
|
|
576
|
+
...(opts.dealValue !== undefined && { deal_value: parseFloat(opts.dealValue) }),
|
|
577
|
+
...(opts.notes !== undefined && { notes: opts.notes }),
|
|
578
|
+
...(opts.assignedTo !== undefined && { assigned_to: opts.assignedTo }),
|
|
579
|
+
...(opts.tag.length > 0 && { tags: opts.tag }),
|
|
580
|
+
...(opts.externalId !== undefined && { external_id: opts.externalId }),
|
|
581
|
+
...(opts.version !== undefined && { version: parseInt(opts.version) })
|
|
582
|
+
};
|
|
525
583
|
const res = await client.put(`/leads/${id}`, body);
|
|
526
584
|
printJSON(res.data);
|
|
527
585
|
} catch (err) {
|
|
@@ -1825,6 +1883,61 @@ contractsCmd
|
|
|
1825
1883
|
});
|
|
1826
1884
|
|
|
1827
1885
|
|
|
1886
|
+
contractsCmd
|
|
1887
|
+
.command('esign <id>')
|
|
1888
|
+
.description('Send e-signing email(s) to all pending signatories on a contract')
|
|
1889
|
+
.action(async (id) => {
|
|
1890
|
+
const globalOpts = program.opts();
|
|
1891
|
+
const client = getClient(globalOpts);
|
|
1892
|
+
try {
|
|
1893
|
+
const res = await client.post(`/contracts/${id}/sign`, {});
|
|
1894
|
+
const d = res.data.data;
|
|
1895
|
+
console.log(`Signing emails sent to ${d.sent_to} signatory(ies).`);
|
|
1896
|
+
printJSON(res.data);
|
|
1897
|
+
} catch (err) {
|
|
1898
|
+
handleError(err);
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
contractsCmd
|
|
1903
|
+
.command('esign-status <id>')
|
|
1904
|
+
.description('Check e-signing status for a contract')
|
|
1905
|
+
.action(async (id) => {
|
|
1906
|
+
const globalOpts = program.opts();
|
|
1907
|
+
const client = getClient(globalOpts);
|
|
1908
|
+
try {
|
|
1909
|
+
const res = await client.get(`/contracts/${id}/sign/status`);
|
|
1910
|
+
printJSON(res.data);
|
|
1911
|
+
} catch (err) {
|
|
1912
|
+
handleError(err);
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
contractsCmd
|
|
1917
|
+
.command('export <id>')
|
|
1918
|
+
.description('Export a contract as PDF and get a download URL')
|
|
1919
|
+
.option('--format <format>', 'Export format (pdf)', 'pdf')
|
|
1920
|
+
.option('--output <file>', 'Save PDF to local file (optional)')
|
|
1921
|
+
.action(async (id, opts) => {
|
|
1922
|
+
const globalOpts = program.opts();
|
|
1923
|
+
const client = getClient(globalOpts);
|
|
1924
|
+
try {
|
|
1925
|
+
const res = await client.get(`/contracts/${id}/export`, { params: { format: opts.format } });
|
|
1926
|
+
const d = res.data.data;
|
|
1927
|
+
console.log(`Download URL: ${d.download_url}`);
|
|
1928
|
+
console.log(`Expires: ${d.expires_at}`);
|
|
1929
|
+
if (opts.output) {
|
|
1930
|
+
// Download the PDF and save locally
|
|
1931
|
+
const baseUrl = globalOpts.apiUrl || process.env.SAAC_CRM_API_URL || loadConfig().apiUrl;
|
|
1932
|
+
const dlRes = await axios.get(`${baseUrl.replace('/api/v1', '')}${d.download_url}`, { responseType: 'arraybuffer' });
|
|
1933
|
+
fs.writeFileSync(opts.output, dlRes.data);
|
|
1934
|
+
console.log(`Saved to ${opts.output}`);
|
|
1935
|
+
}
|
|
1936
|
+
} catch (err) {
|
|
1937
|
+
handleError(err);
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1828
1941
|
// ============================================================
|
|
1829
1942
|
// BUDGETS commands — Sprint 5
|
|
1830
1943
|
// ============================================================
|