@startanaicompany/crm 2.8.0 → 2.10.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 +50 -0
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -398,6 +398,10 @@ leadsCmd
398
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
+ .option('--score <n>', 'Lead score (0-100 integer). Conventions: 0-19 Cold, 20-39 Warm, 40-59 Developing, 60-79 Qualified, 80-100 Hot')
402
+ .option('--score-reason <text>', 'Score reason/explanation')
403
+ .option('--close-probability <n>', 'Close probability 0-100 integer')
404
+ .option('--close-reason <reason>', `Close reason (${['price','timing','competition','no_budget','no_decision','fit','other'].join('|')})`)
401
405
  .option('--notes <notes>', 'Notes')
402
406
  .option('--assigned-to <assignedTo>', 'Assigned to')
403
407
  .option('--tag <tag>', 'Tag (repeatable)', (v, prev) => prev.concat([v]), [])
@@ -419,6 +423,11 @@ leadsCmd
419
423
  source: opts.source,
420
424
  ...(opts.dealValue !== undefined && { deal_value: parseFloat(opts.dealValue) }),
421
425
  ...(opts.pipelineStage && { pipeline_stage: opts.pipelineStage }),
426
+ // --score 0 is valid — use !== undefined not falsy check
427
+ ...(opts.score !== undefined && { score: parseInt(opts.score) }),
428
+ ...(opts.scoreReason && { score_reason: opts.scoreReason }),
429
+ ...(opts.closeProbability !== undefined && { close_probability: parseInt(opts.closeProbability) }),
430
+ ...(opts.closeReason && { close_reason: opts.closeReason }),
422
431
  ...(opts.notes && { notes: opts.notes }),
423
432
  ...(opts.assignedTo && { assigned_to: opts.assignedTo }),
424
433
  ...(opts.tag.length > 0 && { tags: opts.tag }),
@@ -448,6 +457,9 @@ leadsCmd
448
457
  .option('--order <dir>', 'Sort direction: asc|desc', 'desc')
449
458
  .option('--score-min <n>', 'Filter leads with score >= n (0-100)')
450
459
  .option('--score-max <n>', 'Filter leads with score <= n (0-100)')
460
+ .option('--close-probability-min <n>', 'Filter leads with close_probability >= n')
461
+ .option('--close-probability-max <n>', 'Filter leads with close_probability <= n')
462
+ .option('--not-contacted-since-days <n>', 'Filter stale leads not contacted in N days (excludes converted/lost)')
451
463
  .option('--created-after <date>', 'Filter leads created after this ISO8601 date (e.g. 2026-01-01T00:00:00Z)')
452
464
  .option('--created-before <date>', 'Filter leads created before this ISO8601 date')
453
465
  .option('--updated-after <date>', 'Filter leads updated after this ISO8601 date')
@@ -471,6 +483,9 @@ leadsCmd
471
483
  ...(opts.order && { order: opts.order }),
472
484
  ...(opts.scoreMin !== undefined && { score_min: opts.scoreMin }),
473
485
  ...(opts.scoreMax !== undefined && { score_max: opts.scoreMax }),
486
+ ...(opts.closeProbabilityMin !== undefined && { close_probability_min: opts.closeProbabilityMin }),
487
+ ...(opts.closeProbabilityMax !== undefined && { close_probability_max: opts.closeProbabilityMax }),
488
+ ...(opts.notContactedSinceDays !== undefined && { not_contacted_since_days: opts.notContactedSinceDays }),
474
489
  ...(opts.createdAfter && { created_after: opts.createdAfter }),
475
490
  ...(opts.createdBefore && { created_before: opts.createdBefore }),
476
491
  ...(opts.updatedAfter && { updated_after: opts.updatedAfter }),
@@ -1344,7 +1359,42 @@ emailsCmd
1344
1359
  }
1345
1360
  });
1346
1361
 
1362
+ emailsCmd
1363
+ .command('sync')
1364
+ .description('Sync Gmail inbox into CRM (deduped by Gmail message ID)')
1365
+ .option('--max-results <n>', 'Max number of messages to fetch (default 100, max 500)', '100')
1366
+ .action(async (opts) => {
1367
+ const globalOpts = program.opts();
1368
+ const client = getClient(globalOpts);
1369
+ try {
1370
+ const res = await client.post('/emails/sync', { max_results: parseInt(opts.maxResults) || 100 });
1371
+ printJSON(res.data);
1372
+ } catch (err) {
1373
+ handleError(err);
1374
+ }
1375
+ });
1347
1376
 
1377
+ emailsCmd
1378
+ .command('send')
1379
+ .description('Send an email via Gmail (rate-limited: 1/30min, 30/day per workspace)')
1380
+ .requiredOption('--to <email>', 'Recipient email address')
1381
+ .requiredOption('--subject <subject>', 'Email subject')
1382
+ .requiredOption('--body <text>', 'Email body text')
1383
+ .option('--lead-id <id>', 'Link sent email to a lead ID')
1384
+ .option('--dry-run', 'Validate only — do not send or consume rate limit quota')
1385
+ .action(async (opts) => {
1386
+ const globalOpts = program.opts();
1387
+ const client = getClient(globalOpts);
1388
+ try {
1389
+ const body = { to: opts.to, subject: opts.subject, body: opts.body };
1390
+ if (opts.leadId) body.lead_id = opts.leadId;
1391
+ if (opts.dryRun) body.dry_run = true;
1392
+ const res = await client.post('/emails/send', body);
1393
+ printJSON(res.data);
1394
+ } catch (err) {
1395
+ handleError(err);
1396
+ }
1397
+ });
1348
1398
 
1349
1399
  // ============================================================
1350
1400
  // QUOTES commands
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "2.8.0",
4
- "description": "AI-first CRM CLI \u2014 manage leads and API keys from the terminal",
3
+ "version": "2.10.0",
4
+ "description": "AI-first CRM CLI manage leads and API keys from the terminal",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "saac_crm": "./index.js"