@startanaicompany/crm 2.5.2 → 2.6.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 +93 -15
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -438,6 +438,8 @@ 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('--pipeline-stage <stage>', 'Filter by pipeline stage (exact match)')
441
443
  .option('--tag <tag>', 'Filter by tag — repeatable, AND logic', (v, prev) => prev.concat([v]), [])
442
444
  .option('--api-key-id <id>', 'Filter by the API key ID that created the lead')
443
445
  .option('--self', 'Filter leads created by the current API key (shorthand for --api-key-id self)')
@@ -456,6 +458,8 @@ leadsCmd
456
458
  ...(opts.status && { status: opts.status }),
457
459
  ...(opts.email && { email: opts.email }),
458
460
  ...(opts.company && { company: opts.company }),
461
+ ...(opts.name && { name: opts.name }),
462
+ ...(opts.pipelineStage && { pipeline_stage: opts.pipelineStage }),
459
463
  ...(apiKeyIdFilter && { api_key_id: apiKeyIdFilter }),
460
464
  ...(opts.createdAfter && { created_after: opts.createdAfter }),
461
465
  ...(opts.createdBefore && { created_before: opts.createdBefore }),
@@ -501,27 +505,46 @@ leadsCmd
501
505
  .option('--deal-value <amount>', 'Deal value (numeric)')
502
506
  .option('--notes <notes>', 'New notes')
503
507
  .option('--assigned-to <assignedTo>', 'New assigned-to')
504
- .option('--tag <tag>', 'Replace tags (repeatable)', (v, prev) => prev.concat([v]), [])
508
+ .option('--tag <tag>', 'Replace ALL tags (repeatable). Use --add-tag / --remove-tag for surgical edits.', (v, prev) => prev.concat([v]), [])
509
+ .option('--add-tag <tag>', 'Add a single tag without replacing others (repeatable)', (v, prev) => prev.concat([v]), [])
510
+ .option('--remove-tag <tag>', 'Remove a single tag (repeatable)', (v, prev) => prev.concat([v]), [])
505
511
  .option('--external-id <externalId>', 'New external ID')
506
512
  .option('--version <version>', 'Optimistic lock version')
507
513
  .action(async (id, opts) => {
508
514
  const globalOpts = program.opts();
509
515
  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
516
  try {
517
+ // Surgical tag ops: call PATCH /leads/:id/tags first if add/remove specified
518
+ if (opts.addTag.length > 0 || opts.removeTag.length > 0) {
519
+ const tagRes = await client.patch(`/leads/${id}/tags`, {
520
+ ...(opts.addTag.length > 0 && { add_tags: opts.addTag }),
521
+ ...(opts.removeTag.length > 0 && { remove_tags: opts.removeTag })
522
+ });
523
+ // If no other fields to update, return tag result
524
+ const hasOtherFields = opts.name || opts.email || opts.phone !== undefined ||
525
+ opts.company !== undefined || opts.status || opts.source ||
526
+ opts.dealValue !== undefined || opts.notes !== undefined ||
527
+ opts.assignedTo !== undefined || opts.tag.length > 0 ||
528
+ opts.externalId !== undefined;
529
+ if (!hasOtherFields) {
530
+ printJSON(tagRes.data);
531
+ return;
532
+ }
533
+ }
534
+ const body = {
535
+ ...(opts.name && { name: opts.name }),
536
+ ...(opts.email && { email: opts.email }),
537
+ ...(opts.phone !== undefined && { phone: opts.phone }),
538
+ ...(opts.company !== undefined && { company: opts.company }),
539
+ ...(opts.status && { status: opts.status }),
540
+ ...(opts.source && { source: opts.source }),
541
+ ...(opts.dealValue !== undefined && { deal_value: parseFloat(opts.dealValue) }),
542
+ ...(opts.notes !== undefined && { notes: opts.notes }),
543
+ ...(opts.assignedTo !== undefined && { assigned_to: opts.assignedTo }),
544
+ ...(opts.tag.length > 0 && { tags: opts.tag }),
545
+ ...(opts.externalId !== undefined && { external_id: opts.externalId }),
546
+ ...(opts.version !== undefined && { version: parseInt(opts.version) })
547
+ };
525
548
  const res = await client.put(`/leads/${id}`, body);
526
549
  printJSON(res.data);
527
550
  } catch (err) {
@@ -1825,6 +1848,61 @@ contractsCmd
1825
1848
  });
1826
1849
 
1827
1850
 
1851
+ contractsCmd
1852
+ .command('esign <id>')
1853
+ .description('Send e-signing email(s) to all pending signatories on a contract')
1854
+ .action(async (id) => {
1855
+ const globalOpts = program.opts();
1856
+ const client = getClient(globalOpts);
1857
+ try {
1858
+ const res = await client.post(`/contracts/${id}/sign`, {});
1859
+ const d = res.data.data;
1860
+ console.log(`Signing emails sent to ${d.sent_to} signatory(ies).`);
1861
+ printJSON(res.data);
1862
+ } catch (err) {
1863
+ handleError(err);
1864
+ }
1865
+ });
1866
+
1867
+ contractsCmd
1868
+ .command('esign-status <id>')
1869
+ .description('Check e-signing status for a contract')
1870
+ .action(async (id) => {
1871
+ const globalOpts = program.opts();
1872
+ const client = getClient(globalOpts);
1873
+ try {
1874
+ const res = await client.get(`/contracts/${id}/sign/status`);
1875
+ printJSON(res.data);
1876
+ } catch (err) {
1877
+ handleError(err);
1878
+ }
1879
+ });
1880
+
1881
+ contractsCmd
1882
+ .command('export <id>')
1883
+ .description('Export a contract as PDF and get a download URL')
1884
+ .option('--format <format>', 'Export format (pdf)', 'pdf')
1885
+ .option('--output <file>', 'Save PDF to local file (optional)')
1886
+ .action(async (id, opts) => {
1887
+ const globalOpts = program.opts();
1888
+ const client = getClient(globalOpts);
1889
+ try {
1890
+ const res = await client.get(`/contracts/${id}/export`, { params: { format: opts.format } });
1891
+ const d = res.data.data;
1892
+ console.log(`Download URL: ${d.download_url}`);
1893
+ console.log(`Expires: ${d.expires_at}`);
1894
+ if (opts.output) {
1895
+ // Download the PDF and save locally
1896
+ const baseUrl = globalOpts.apiUrl || process.env.SAAC_CRM_API_URL || loadConfig().apiUrl;
1897
+ const dlRes = await axios.get(`${baseUrl.replace('/api/v1', '')}${d.download_url}`, { responseType: 'arraybuffer' });
1898
+ fs.writeFileSync(opts.output, dlRes.data);
1899
+ console.log(`Saved to ${opts.output}`);
1900
+ }
1901
+ } catch (err) {
1902
+ handleError(err);
1903
+ }
1904
+ });
1905
+
1828
1906
  // ============================================================
1829
1907
  // BUDGETS commands — Sprint 5
1830
1908
  // ============================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "2.5.2",
3
+ "version": "2.6.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": {