@startanaicompany/crm 2.7.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 +77 -2
  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 }),
@@ -444,8 +453,13 @@ leadsCmd
444
453
  .option('--tag <tag>', 'Filter by tag — repeatable, AND logic', (v, prev) => prev.concat([v]), [])
445
454
  .option('--api-key-id <id>', 'Filter by the API key ID that created the lead')
446
455
  .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')
456
+ .option('--sort-by <field>', 'Sort field: created_at|updated_at|deal_value|name|company|last_activity_at|status_changed_at|score')
448
457
  .option('--order <dir>', 'Sort direction: asc|desc', 'desc')
458
+ .option('--score-min <n>', 'Filter leads with score >= n (0-100)')
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)')
449
463
  .option('--created-after <date>', 'Filter leads created after this ISO8601 date (e.g. 2026-01-01T00:00:00Z)')
450
464
  .option('--created-before <date>', 'Filter leads created before this ISO8601 date')
451
465
  .option('--updated-after <date>', 'Filter leads updated after this ISO8601 date')
@@ -467,6 +481,11 @@ leadsCmd
467
481
  ...(apiKeyIdFilter && { api_key_id: apiKeyIdFilter }),
468
482
  ...(opts.sortBy && { sort_by: opts.sortBy }),
469
483
  ...(opts.order && { order: opts.order }),
484
+ ...(opts.scoreMin !== undefined && { score_min: opts.scoreMin }),
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 }),
470
489
  ...(opts.createdAfter && { created_after: opts.createdAfter }),
471
490
  ...(opts.createdBefore && { created_before: opts.createdBefore }),
472
491
  ...(opts.updatedAfter && { updated_after: opts.updatedAfter }),
@@ -514,9 +533,30 @@ leadsCmd
514
533
  }
515
534
  });
516
535
 
536
+ leadsCmd
537
+ .command('score')
538
+ .description('Set lead score (0-100). Conventions: 0-19 Cold, 20-39 Warm, 40-59 Developing, 60-79 Qualified, 80-100 Hot')
539
+ .requiredOption('--id <uuid>', 'Lead UUID')
540
+ .requiredOption('--score <n>', 'Score value (0-100 integer)')
541
+ .option('--reason <text>', 'Score reason/explanation')
542
+ .action(async (opts) => {
543
+ const globalOpts = program.opts();
544
+ const client = getClient(globalOpts);
545
+ const body = {
546
+ score: parseInt(opts.score),
547
+ ...(opts.reason && { score_reason: opts.reason })
548
+ };
549
+ try {
550
+ const res = await client.patch(`/leads/${opts.id}/score`, body);
551
+ printJSON(res.data);
552
+ } catch (err) {
553
+ handleError(err);
554
+ }
555
+ });
556
+
517
557
  leadsCmd
518
558
  .command('get <id>')
519
- .description('Get a single lead by ID')
559
+ .description('Get a single lead by ID (includes activity_summary)')
520
560
  .action(async (id) => {
521
561
  const globalOpts = program.opts();
522
562
  const client = getClient(globalOpts);
@@ -1319,7 +1359,42 @@ emailsCmd
1319
1359
  }
1320
1360
  });
1321
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
+ });
1322
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
+ });
1323
1398
 
1324
1399
  // ============================================================
1325
1400
  // QUOTES commands
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "2.7.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"