@solidnumber/cli 2.11.10 → 2.11.12

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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/auth.d.ts +1 -0
  3. package/dist/commands/auth.d.ts.map +1 -1
  4. package/dist/commands/auth.js +5 -21
  5. package/dist/commands/auth.js.map +1 -1
  6. package/dist/commands/billing.d.ts.map +1 -1
  7. package/dist/commands/billing.js +10 -2
  8. package/dist/commands/billing.js.map +1 -1
  9. package/dist/commands/deploy.d.ts.map +1 -1
  10. package/dist/commands/deploy.js +4 -2
  11. package/dist/commands/deploy.js.map +1 -1
  12. package/dist/commands/mcp.js +105 -24
  13. package/dist/commands/mcp.js.map +1 -1
  14. package/dist/commands/orders.d.ts.map +1 -1
  15. package/dist/commands/orders.js +10 -0
  16. package/dist/commands/orders.js.map +1 -1
  17. package/dist/commands/payment.d.ts.map +1 -1
  18. package/dist/commands/payment.js +5 -2
  19. package/dist/commands/payment.js.map +1 -1
  20. package/dist/commands/publish.d.ts.map +1 -1
  21. package/dist/commands/publish.js +4 -2
  22. package/dist/commands/publish.js.map +1 -1
  23. package/dist/commands/push.d.ts +2 -0
  24. package/dist/commands/push.d.ts.map +1 -1
  25. package/dist/commands/push.js +9 -87
  26. package/dist/commands/push.js.map +1 -1
  27. package/dist/commands/sales.d.ts +19 -0
  28. package/dist/commands/sales.d.ts.map +1 -0
  29. package/dist/commands/sales.js +896 -0
  30. package/dist/commands/sales.js.map +1 -0
  31. package/dist/commands/schema.js +208 -1
  32. package/dist/commands/schema.js.map +1 -1
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +93 -0
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/commands/webhooks.d.ts.map +1 -1
  37. package/dist/commands/webhooks.js +4 -0
  38. package/dist/commands/webhooks.js.map +1 -1
  39. package/dist/data/workflow-recipes.json +132 -0
  40. package/dist/index.js +4 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/lib/agent-mode.d.ts.map +1 -1
  43. package/dist/lib/agent-mode.js +3 -0
  44. package/dist/lib/agent-mode.js.map +1 -1
  45. package/dist/lib/api-client.d.ts.map +1 -1
  46. package/dist/lib/api-client.js +13 -9
  47. package/dist/lib/api-client.js.map +1 -1
  48. package/dist/lib/command-kit.d.ts +10 -0
  49. package/dist/lib/command-kit.d.ts.map +1 -1
  50. package/dist/lib/command-kit.js +17 -0
  51. package/dist/lib/command-kit.js.map +1 -1
  52. package/dist/lib/offline-queue.d.ts +1 -1
  53. package/dist/lib/offline-queue.d.ts.map +1 -1
  54. package/dist/lib/offline-queue.js +2 -2
  55. package/dist/lib/offline-queue.js.map +1 -1
  56. package/dist/lib/persona-groups.d.ts +1 -1
  57. package/dist/lib/persona-groups.d.ts.map +1 -1
  58. package/dist/lib/persona-groups.js +4 -1
  59. package/dist/lib/persona-groups.js.map +1 -1
  60. package/dist/lib/push-utils.d.ts +39 -0
  61. package/dist/lib/push-utils.d.ts.map +1 -0
  62. package/dist/lib/push-utils.js +120 -0
  63. package/dist/lib/push-utils.js.map +1 -0
  64. package/dist/lib/url-utils.d.ts +5 -0
  65. package/dist/lib/url-utils.d.ts.map +1 -0
  66. package/dist/lib/url-utils.js +23 -0
  67. package/dist/lib/url-utils.js.map +1 -0
  68. package/dist/lib/verb-metadata.d.ts +105 -0
  69. package/dist/lib/verb-metadata.d.ts.map +1 -0
  70. package/dist/lib/verb-metadata.js +171 -0
  71. package/dist/lib/verb-metadata.js.map +1 -0
  72. package/package.json +3 -2
  73. package/platform-docs/llms.txt +7 -6
@@ -0,0 +1,896 @@
1
+ "use strict";
2
+ /**
3
+ * Sales Agent CLI — Phase 1 read-only verbs.
4
+ *
5
+ * Wraps existing CRM endpoints into a sales-focused surface for AI agents.
6
+ * Same verbs, different context per industry — the KB provides differentiation.
7
+ *
8
+ * Endpoints composed:
9
+ * GET /api/v1/crm/pipeline/summary
10
+ * GET /api/v1/crm/leads/stats
11
+ * GET /api/v1/crm/leads/pipeline-sources
12
+ * GET /api/v1/crm/dashboard/revenue/summary
13
+ * GET /api/v1/crm/dashboard/summary
14
+ * GET /api/v1/crm/sales-funnel
15
+ * GET /api/v1/crm/tasks
16
+ * GET /api/v1/crm/lead-scoring/high-value-contacts
17
+ */
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.salesCommand = void 0;
23
+ const commander_1 = require("commander");
24
+ const ora_1 = __importDefault(require("ora"));
25
+ const chalk_1 = __importDefault(require("chalk"));
26
+ const config_1 = require("../lib/config");
27
+ const api_client_1 = require("../lib/api-client");
28
+ const json_output_1 = require("../lib/json-output");
29
+ const ui_1 = require("../lib/ui");
30
+ function requireAuth() {
31
+ if (!config_1.config.isLoggedIn()) {
32
+ console.error(chalk_1.default.red('Not logged in. Run `solid auth login` first.'));
33
+ process.exit(1);
34
+ }
35
+ }
36
+ function fail(spinner, msg, err) {
37
+ spinner.fail(chalk_1.default.red(msg));
38
+ console.error(chalk_1.default.red(` ${(0, api_client_1.handleApiError)(err).message}`));
39
+ process.exit(1);
40
+ }
41
+ function dollars(cents) {
42
+ const n = Number(cents);
43
+ if (!Number.isFinite(n))
44
+ return '$0';
45
+ return `$${(n / 100).toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
46
+ }
47
+ function dollarsDirect(val) {
48
+ const n = Number(val);
49
+ if (!Number.isFinite(n))
50
+ return '$0';
51
+ return `$${n.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
52
+ }
53
+ function pct(val) {
54
+ const n = Number(val);
55
+ if (!Number.isFinite(n))
56
+ return '0%';
57
+ return `${n.toFixed(1)}%`;
58
+ }
59
+ function unwrapDispatch(data) {
60
+ if (data.ok === false) {
61
+ const err = (data.error || {});
62
+ throw new Error(String(err.message || err.reason || 'Dispatch failed'));
63
+ }
64
+ return (data.result || data);
65
+ }
66
+ exports.salesCommand = new commander_1.Command('sales')
67
+ .description('Sales pipeline, forecasting, and growth intelligence');
68
+ // ── pipeline ────────────────────────────────────────────────────────
69
+ exports.salesCommand
70
+ .command('pipeline')
71
+ .description('Full pipeline view: stages, values, velocity')
72
+ .option('--json', 'Output as JSON')
73
+ .action(async (opts) => {
74
+ requireAuth();
75
+ const spinner = (0, ora_1.default)({ text: 'Loading pipeline...', stream: process.stderr }).start();
76
+ try {
77
+ const res = await api_client_1.apiClient.get('/api/v1/crm/pipeline/summary');
78
+ spinner.stop();
79
+ const data = res.data;
80
+ if ((0, json_output_1.isJsonOutput)(opts)) {
81
+ console.log(JSON.stringify(data, null, 2));
82
+ return;
83
+ }
84
+ console.log(ui_1.ui.header('Sales Pipeline'));
85
+ const stages = (data.stages || []);
86
+ if (stages.length) {
87
+ console.log(ui_1.ui.table(['Stage', 'Deals', 'Value'], stages.map((s) => [
88
+ String(s.stage || s.name || '—'),
89
+ String(s.count ?? 0),
90
+ dollarsDirect(s.value ?? 0),
91
+ ])));
92
+ }
93
+ console.log('');
94
+ console.log(ui_1.ui.label('Total deals', String(data.total_deals ?? '—')));
95
+ console.log(ui_1.ui.label('Pipeline value', dollarsDirect(data.total_pipeline_value ?? 0)));
96
+ console.log(ui_1.ui.label('Won value', dollarsDirect(data.won_value ?? 0)));
97
+ console.log(ui_1.ui.label('Conversion rate', pct(data.conversion_rate)));
98
+ console.log('');
99
+ }
100
+ catch (e) {
101
+ fail(spinner, 'Failed to load pipeline', e);
102
+ }
103
+ });
104
+ // ── prospects ───────────────────────────────────────────────────────
105
+ exports.salesCommand
106
+ .command('prospects')
107
+ .description('Unqualified leads ready for outreach, ranked by score')
108
+ .option('-l, --limit <n>', 'Max results', '25')
109
+ .option('--json', 'Output as JSON')
110
+ .action(async (opts) => {
111
+ requireAuth();
112
+ const spinner = (0, ora_1.default)({ text: 'Loading prospects...', stream: process.stderr }).start();
113
+ try {
114
+ const [statsRes, highValueRes] = await Promise.all([
115
+ api_client_1.apiClient.get('/api/v1/crm/leads/stats'),
116
+ api_client_1.apiClient.get('/api/v1/crm/lead-scoring/high-value-contacts', {
117
+ params: { limit: parseInt(opts.limit, 10) },
118
+ }),
119
+ ]);
120
+ spinner.stop();
121
+ const stats = statsRes.data;
122
+ const contacts = (highValueRes.data.high_value_contacts || []);
123
+ if ((0, json_output_1.isJsonOutput)(opts)) {
124
+ console.log(JSON.stringify({ stats, prospects: contacts }, null, 2));
125
+ return;
126
+ }
127
+ console.log(ui_1.ui.header('Prospects'));
128
+ console.log(ui_1.ui.label('Total leads', String(stats.total_leads ?? '—')));
129
+ console.log(ui_1.ui.label('Conversion rate', pct(stats.conversion_rate)));
130
+ console.log(ui_1.ui.label('Avg score', String(stats.average_score ?? '—')));
131
+ console.log('');
132
+ if (contacts.length) {
133
+ console.log(ui_1.ui.table(['ID', 'Name', 'Score', 'Grade', 'Source', 'Spent'], contacts.map((c) => [
134
+ String(c.id),
135
+ [c.first_name, c.last_name].filter(Boolean).join(' ') || chalk_1.default.dim('—'),
136
+ String(c.lead_score ?? '—'),
137
+ String(c.lead_grade ?? '—'),
138
+ String(c.source || chalk_1.default.dim('—')),
139
+ dollarsDirect(c.total_spent ?? 0),
140
+ ])));
141
+ }
142
+ else {
143
+ console.log(chalk_1.default.yellow(' No high-value prospects found.'));
144
+ }
145
+ console.log('');
146
+ }
147
+ catch (e) {
148
+ fail(spinner, 'Failed to load prospects', e);
149
+ }
150
+ });
151
+ // ── sources ─────────────────────────────────────────────────────────
152
+ exports.salesCommand
153
+ .command('sources')
154
+ .description('Lead funnel performance — which sources convert best')
155
+ .option('--days <n>', 'Lookback period in days', '30')
156
+ .option('--json', 'Output as JSON')
157
+ .action(async (opts) => {
158
+ requireAuth();
159
+ const spinner = (0, ora_1.default)({ text: 'Loading lead sources...', stream: process.stderr }).start();
160
+ try {
161
+ const res = await api_client_1.apiClient.get('/api/v1/crm/leads/pipeline-sources', {
162
+ params: { days: parseInt(opts.days, 10) },
163
+ });
164
+ spinner.stop();
165
+ const data = res.data;
166
+ if ((0, json_output_1.isJsonOutput)(opts)) {
167
+ console.log(JSON.stringify(data, null, 2));
168
+ return;
169
+ }
170
+ console.log(ui_1.ui.header(`Lead Sources (last ${opts.days} days)`));
171
+ const sources = (data.sources || []);
172
+ if (sources.length) {
173
+ console.log(ui_1.ui.table(['Source', 'Leads', 'Deals', 'Won', 'Conv %', 'Avg Score'], sources.map((s) => [
174
+ String(s.source || '—'),
175
+ String(s.leads ?? 0),
176
+ String(s.deals ?? 0),
177
+ String(s.won ?? 0),
178
+ pct(s.conversion_rate),
179
+ String(s.avg_score ?? '—'),
180
+ ])));
181
+ }
182
+ else {
183
+ console.log(chalk_1.default.yellow(' No source data available for this period.'));
184
+ }
185
+ console.log('');
186
+ }
187
+ catch (e) {
188
+ fail(spinner, 'Failed to load lead sources', e);
189
+ }
190
+ });
191
+ // ── forecast ────────────────────────────────────────────────────────
192
+ exports.salesCommand
193
+ .command('forecast')
194
+ .description('Revenue forecast by stage and probability')
195
+ .option('--json', 'Output as JSON')
196
+ .action(async (opts) => {
197
+ requireAuth();
198
+ const spinner = (0, ora_1.default)({ text: 'Loading forecast...', stream: process.stderr }).start();
199
+ try {
200
+ const [revenueRes, pipelineRes] = await Promise.all([
201
+ api_client_1.apiClient.get('/api/v1/crm/dashboard/revenue/summary'),
202
+ api_client_1.apiClient.get('/api/v1/crm/pipeline/summary'),
203
+ ]);
204
+ spinner.stop();
205
+ const revenue = revenueRes.data;
206
+ const pipeline = pipelineRes.data;
207
+ if ((0, json_output_1.isJsonOutput)(opts)) {
208
+ console.log(JSON.stringify({ revenue, pipeline }, null, 2));
209
+ return;
210
+ }
211
+ console.log(ui_1.ui.header('Revenue Forecast'));
212
+ console.log(ui_1.ui.label('Total revenue', dollarsDirect(revenue.total_revenue ?? 0)));
213
+ console.log(ui_1.ui.label('Monthly revenue', dollarsDirect(revenue.monthly_revenue ?? 0)));
214
+ console.log(ui_1.ui.label('YTD revenue', dollarsDirect(revenue.ytd_revenue ?? 0)));
215
+ console.log(ui_1.ui.label('Avg deal value', dollarsDirect(revenue.average_deal_value ?? 0)));
216
+ console.log('');
217
+ const stages = (pipeline.stages || []);
218
+ if (stages.length) {
219
+ console.log(ui_1.ui.divider('Pipeline by Stage'));
220
+ console.log(ui_1.ui.table(['Stage', 'Deals', 'Value', 'Weighted'], stages.map((s) => {
221
+ const value = Number(s.value ?? s.total_value ?? 0);
222
+ const prob = Number(s.probability ?? s.conversion_rate ?? 0) / 100;
223
+ return [
224
+ String(s.name || s.stage || '—'),
225
+ String(s.count ?? s.deals ?? 0),
226
+ dollarsDirect(value),
227
+ dollarsDirect(value * (Number.isFinite(prob) ? prob : 0)),
228
+ ];
229
+ })));
230
+ }
231
+ console.log('');
232
+ }
233
+ catch (e) {
234
+ fail(spinner, 'Failed to load forecast', e);
235
+ }
236
+ });
237
+ // ── report ──────────────────────────────────────────────────────────
238
+ exports.salesCommand
239
+ .command('report [period]')
240
+ .description('Sales performance report (default: this month)')
241
+ .option('--json', 'Output as JSON')
242
+ .action(async (period, opts) => {
243
+ requireAuth();
244
+ const p = period || 'mtd';
245
+ const spinner = (0, ora_1.default)({ text: `Loading ${p} report...`, stream: process.stderr }).start();
246
+ try {
247
+ const [summaryRes, leadsRes, customersRes] = await Promise.all([
248
+ api_client_1.apiClient.get('/api/v1/crm/dashboard/summary'),
249
+ api_client_1.apiClient.get('/api/v1/crm/dashboard/leads/overview', { params: { period: p } }),
250
+ api_client_1.apiClient.get('/api/v1/crm/dashboard/customers/summary'),
251
+ ]);
252
+ spinner.stop();
253
+ const summary = summaryRes.data;
254
+ const leads = leadsRes.data;
255
+ const customers = customersRes.data;
256
+ if ((0, json_output_1.isJsonOutput)(opts)) {
257
+ console.log(JSON.stringify({ period: p, summary, leads, customers }, null, 2));
258
+ return;
259
+ }
260
+ console.log(ui_1.ui.header(`Sales Report — ${p.toUpperCase()}`));
261
+ console.log(ui_1.ui.divider('Revenue'));
262
+ console.log(ui_1.ui.label('Total revenue', dollarsDirect(summary.revenue ?? summary.total_revenue ?? 0)));
263
+ console.log(ui_1.ui.label('Total deals', String(summary.deals ?? summary.total_deals ?? 0)));
264
+ console.log('');
265
+ console.log(ui_1.ui.divider('Leads'));
266
+ console.log(ui_1.ui.label('New leads (period)', String(leads.mtd_leads ?? leads.new_leads ?? 0)));
267
+ console.log(ui_1.ui.label('Conversion rate', pct(leads.conversion_rate)));
268
+ console.log(ui_1.ui.label('Avg deal value', dollarsDirect(leads.avg_deal_value ?? 0)));
269
+ console.log('');
270
+ console.log(ui_1.ui.divider('Customers'));
271
+ console.log(ui_1.ui.label('Total customers', String(customers.total_customers ?? 0)));
272
+ console.log(ui_1.ui.label('New this month', String(customers.new_customers_mtd ?? 0)));
273
+ console.log(ui_1.ui.label('Repeat rate', pct(customers.repeat_rate)));
274
+ console.log(ui_1.ui.label('Churn rate', pct(customers.churn_rate)));
275
+ console.log('');
276
+ }
277
+ catch (e) {
278
+ fail(spinner, 'Failed to load sales report', e);
279
+ }
280
+ });
281
+ // ── velocity ────────────────────────────────────────────────────────
282
+ exports.salesCommand
283
+ .command('velocity')
284
+ .description('Deal velocity metrics — how fast deals move through stages')
285
+ .option('--json', 'Output as JSON')
286
+ .action(async (opts) => {
287
+ requireAuth();
288
+ const spinner = (0, ora_1.default)({ text: 'Loading velocity...', stream: process.stderr }).start();
289
+ try {
290
+ const res = await api_client_1.apiClient.get('/api/v1/crm/sales-funnel');
291
+ spinner.stop();
292
+ const data = res.data;
293
+ if ((0, json_output_1.isJsonOutput)(opts)) {
294
+ console.log(JSON.stringify(data, null, 2));
295
+ return;
296
+ }
297
+ console.log(ui_1.ui.header('Deal Velocity'));
298
+ const stages = (data.stages || []);
299
+ if (stages.length) {
300
+ console.log(ui_1.ui.table(['Stage', 'Count', 'Conv from Prev'], stages.map((s) => [
301
+ String(s.stage || '—'),
302
+ String(s.count ?? 0),
303
+ s.conversion_from_previous != null ? pct(s.conversion_from_previous) : chalk_1.default.dim('—'),
304
+ ])));
305
+ }
306
+ console.log('');
307
+ if (data.lost_count != null) {
308
+ console.log(ui_1.ui.label('Lost deals', String(data.lost_count)));
309
+ }
310
+ if (data.total_deals != null) {
311
+ console.log(ui_1.ui.label('Total deals (excl. lost)', String(data.total_deals)));
312
+ }
313
+ console.log('');
314
+ }
315
+ catch (e) {
316
+ fail(spinner, 'Failed to load velocity', e);
317
+ }
318
+ });
319
+ // ── followup ────────────────────────────────────────────────────────
320
+ exports.salesCommand
321
+ .command('followup')
322
+ .alias('follow-up')
323
+ .description('Overdue follow-ups ranked by priority')
324
+ .option('-l, --limit <n>', 'Max results', '25')
325
+ .option('--json', 'Output as JSON')
326
+ .action(async (opts) => {
327
+ requireAuth();
328
+ const spinner = (0, ora_1.default)({ text: 'Loading follow-ups...', stream: process.stderr }).start();
329
+ try {
330
+ const res = await api_client_1.apiClient.get('/api/v1/crm/tasks', {
331
+ params: {
332
+ status: 'pending',
333
+ limit: parseInt(opts.limit, 10),
334
+ offset: 0,
335
+ },
336
+ });
337
+ spinner.stop();
338
+ const data = res.data;
339
+ const tasks = (data.tasks || data.items || []);
340
+ if ((0, json_output_1.isJsonOutput)(opts)) {
341
+ console.log(JSON.stringify(data, null, 2));
342
+ return;
343
+ }
344
+ console.log(ui_1.ui.header('Overdue Follow-ups'));
345
+ if (tasks.length) {
346
+ const now = Date.now();
347
+ const overdue = tasks.filter((t) => {
348
+ const due = t.due_date ? new Date(String(t.due_date)).getTime() : Infinity;
349
+ return due <= now;
350
+ });
351
+ const upcoming = tasks.filter((t) => {
352
+ const due = t.due_date ? new Date(String(t.due_date)).getTime() : Infinity;
353
+ return due > now;
354
+ });
355
+ if (overdue.length) {
356
+ console.log(chalk_1.default.red(` ${overdue.length} overdue`));
357
+ console.log(ui_1.ui.table(['ID', 'Title', 'Due', 'Priority', 'Contact'], overdue.map((t) => [
358
+ String(t.id),
359
+ String(t.title || '—'),
360
+ t.due_date ? String(t.due_date).split('T')[0] : chalk_1.default.dim('—'),
361
+ String(t.priority || chalk_1.default.dim('normal')),
362
+ String(t.contact_name || t.contact_id || chalk_1.default.dim('—')),
363
+ ])));
364
+ console.log('');
365
+ }
366
+ if (upcoming.length) {
367
+ console.log(chalk_1.default.green(` ${upcoming.length} upcoming`));
368
+ console.log(ui_1.ui.table(['ID', 'Title', 'Due', 'Priority', 'Contact'], upcoming.map((t) => [
369
+ String(t.id),
370
+ String(t.title || '—'),
371
+ t.due_date ? String(t.due_date).split('T')[0] : chalk_1.default.dim('—'),
372
+ String(t.priority || chalk_1.default.dim('normal')),
373
+ String(t.contact_name || t.contact_id || chalk_1.default.dim('—')),
374
+ ])));
375
+ }
376
+ }
377
+ else {
378
+ console.log(chalk_1.default.green(' No pending follow-ups. Pipeline is clean.'));
379
+ }
380
+ console.log('');
381
+ }
382
+ catch (e) {
383
+ fail(spinner, 'Failed to load follow-ups', e);
384
+ }
385
+ });
386
+ // ── win-rate ────────────────────────────────────────────────────────
387
+ exports.salesCommand
388
+ .command('win-rate')
389
+ .description('Win/loss analysis by source and stage')
390
+ .option('--json', 'Output as JSON')
391
+ .action(async (opts) => {
392
+ requireAuth();
393
+ const spinner = (0, ora_1.default)({ text: 'Loading win rate...', stream: process.stderr }).start();
394
+ try {
395
+ const [funnelRes, tierRes] = await Promise.all([
396
+ api_client_1.apiClient.get('/api/v1/crm/sales-funnel'),
397
+ api_client_1.apiClient.get('/api/v1/crm/lead-scoring/tier-breakdown'),
398
+ ]);
399
+ spinner.stop();
400
+ const funnel = funnelRes.data;
401
+ const tiers = tierRes.data;
402
+ if ((0, json_output_1.isJsonOutput)(opts)) {
403
+ console.log(JSON.stringify({ funnel, tiers }, null, 2));
404
+ return;
405
+ }
406
+ console.log(ui_1.ui.header('Win Rate Analysis'));
407
+ const stages = (funnel.stages || []);
408
+ if (stages.length) {
409
+ console.log(ui_1.ui.divider('Funnel'));
410
+ console.log(ui_1.ui.table(['Stage', 'Count', 'Conv from Prev'], stages.map((s) => [
411
+ String(s.stage || '—'),
412
+ String(s.count ?? 0),
413
+ s.conversion_from_previous != null ? pct(s.conversion_from_previous) : chalk_1.default.dim('—'),
414
+ ])));
415
+ console.log('');
416
+ }
417
+ console.log(ui_1.ui.divider('Lead Score Distribution'));
418
+ console.log(ui_1.ui.label('A-grade', String(tiers.A ?? tiers.a ?? 0)));
419
+ console.log(ui_1.ui.label('B-grade', String(tiers.B ?? tiers.b ?? 0)));
420
+ console.log(ui_1.ui.label('C-grade', String(tiers.C ?? tiers.c ?? 0)));
421
+ console.log(ui_1.ui.label('D-grade', String(tiers.D ?? tiers.d ?? 0)));
422
+ console.log(ui_1.ui.label('F-grade', String(tiers.F ?? tiers.f ?? 0)));
423
+ console.log('');
424
+ }
425
+ catch (e) {
426
+ fail(spinner, 'Failed to load win rate', e);
427
+ }
428
+ });
429
+ // ═══════════════════════════════════════════════════════════════════
430
+ // Phase 2 — AI-powered suggest verbs
431
+ // ═══════════════════════════════════════════════════════════════════
432
+ // ── score ───────────────────────────────────────────────────────────
433
+ exports.salesCommand
434
+ .command('score <lead_id>')
435
+ .description('AI-score a lead with industry-specific reasoning')
436
+ .option('--json', 'Output as JSON')
437
+ .action(async (leadId, opts) => {
438
+ requireAuth();
439
+ const spinner = (0, ora_1.default)({ text: 'Scoring lead...', stream: process.stderr }).start();
440
+ try {
441
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
442
+ verb: 'sales.score',
443
+ args: { lead_id: parseInt(leadId, 10) },
444
+ });
445
+ spinner.stop();
446
+ const data = unwrapDispatch(res.data);
447
+ if ((0, json_output_1.isJsonOutput)(opts)) {
448
+ console.log(JSON.stringify(data, null, 2));
449
+ return;
450
+ }
451
+ const suggestions = (data.suggestions || []);
452
+ const s = suggestions[0] || {};
453
+ const lead = (s.lead || {});
454
+ console.log(ui_1.ui.header(`Lead Score — ${lead.name || `#${leadId}`}`));
455
+ console.log(ui_1.ui.label('Score', `${s.ai_score ?? '—'}/100`));
456
+ console.log(ui_1.ui.label('Grade', String(s.ai_grade ?? '—')));
457
+ console.log(ui_1.ui.label('Confidence', `${data.confidence_band || '—'} (${((Number(data.confidence) || 0) * 100).toFixed(0)}%)`));
458
+ console.log('');
459
+ if (s.reasoning)
460
+ console.log(` ${chalk_1.default.dim(String(s.reasoning))}`);
461
+ console.log('');
462
+ const signals = (s.signals || []);
463
+ if (signals.length) {
464
+ console.log(ui_1.ui.divider('Signals'));
465
+ for (const sig of signals)
466
+ console.log(` ${chalk_1.default.green('+')} ${sig}`);
467
+ console.log('');
468
+ }
469
+ const risks = (s.risks || []);
470
+ if (risks.length) {
471
+ console.log(ui_1.ui.divider('Risks'));
472
+ for (const r of risks)
473
+ console.log(` ${chalk_1.default.red('−')} ${r}`);
474
+ console.log('');
475
+ }
476
+ if (s.recommended_action) {
477
+ console.log(ui_1.ui.divider('Next Action'));
478
+ console.log(` ${chalk_1.default.cyan('→')} ${s.recommended_action}`);
479
+ console.log('');
480
+ }
481
+ }
482
+ catch (e) {
483
+ fail(spinner, 'Failed to score lead', e);
484
+ }
485
+ });
486
+ // ── suggest ─────────────────────────────────────────────────────────
487
+ exports.salesCommand
488
+ .command('suggest <deal_id>')
489
+ .description('AI next-best-action for a deal')
490
+ .option('--json', 'Output as JSON')
491
+ .action(async (dealId, opts) => {
492
+ requireAuth();
493
+ const spinner = (0, ora_1.default)({ text: 'Analyzing deal...', stream: process.stderr }).start();
494
+ try {
495
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
496
+ verb: 'sales.suggest',
497
+ args: { deal_id: parseInt(dealId, 10) },
498
+ });
499
+ spinner.stop();
500
+ const data = unwrapDispatch(res.data);
501
+ if ((0, json_output_1.isJsonOutput)(opts)) {
502
+ console.log(JSON.stringify(data, null, 2));
503
+ return;
504
+ }
505
+ const evidence = (data.evidence || {});
506
+ const deal = (evidence.deal || {});
507
+ console.log(ui_1.ui.header(`Deal Suggestions — ${deal.title || `#${dealId}`}`));
508
+ if (evidence.deal_health)
509
+ console.log(ui_1.ui.label('Health', String(evidence.deal_health)));
510
+ if (evidence.insight)
511
+ console.log(ui_1.ui.label('Insight', String(evidence.insight)));
512
+ console.log(ui_1.ui.label('Confidence', `${data.confidence_band || '—'} (${((Number(data.confidence) || 0) * 100).toFixed(0)}%)`));
513
+ console.log('');
514
+ const suggestions = (data.suggestions || []);
515
+ if (suggestions.length) {
516
+ for (let i = 0; i < suggestions.length; i++) {
517
+ const s = suggestions[i];
518
+ const pri = String(s.priority || 'medium');
519
+ const badge = pri === 'high' ? chalk_1.default.red('[HIGH]') : pri === 'low' ? chalk_1.default.dim('[LOW]') : chalk_1.default.yellow('[MED]');
520
+ console.log(` ${i + 1}. ${badge} ${s.action}`);
521
+ if (s.reasoning)
522
+ console.log(` ${chalk_1.default.dim(String(s.reasoning))}`);
523
+ }
524
+ }
525
+ console.log('');
526
+ }
527
+ catch (e) {
528
+ fail(spinner, 'Failed to get suggestions', e);
529
+ }
530
+ });
531
+ // ── outreach ────────────────────────────────────────────────────────
532
+ exports.salesCommand
533
+ .command('outreach <lead_id>')
534
+ .description('Generate personalized outreach copy')
535
+ .option('--channel <channel>', 'Channel: email, sms, call_script', 'email')
536
+ .option('--json', 'Output as JSON')
537
+ .action(async (leadId, opts) => {
538
+ requireAuth();
539
+ const spinner = (0, ora_1.default)({ text: `Generating ${opts.channel} outreach...`, stream: process.stderr }).start();
540
+ try {
541
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
542
+ verb: 'sales.outreach',
543
+ args: { lead_id: parseInt(leadId, 10), channel: opts.channel },
544
+ });
545
+ spinner.stop();
546
+ const data = unwrapDispatch(res.data);
547
+ if ((0, json_output_1.isJsonOutput)(opts)) {
548
+ console.log(JSON.stringify(data, null, 2));
549
+ return;
550
+ }
551
+ const suggestions = (data.suggestions || []);
552
+ const s = suggestions[0] || {};
553
+ const contact = (s.contact || {});
554
+ console.log(ui_1.ui.header(`Outreach — ${contact.name || `Lead #${leadId}`} (${s.channel || opts.channel})`));
555
+ if (s.error) {
556
+ console.log(chalk_1.default.yellow(` ${s.error}`));
557
+ console.log('');
558
+ return;
559
+ }
560
+ if (s.subject) {
561
+ console.log(ui_1.ui.label('Subject', String(s.subject)));
562
+ console.log('');
563
+ }
564
+ if (s.body) {
565
+ console.log(String(s.body));
566
+ console.log('');
567
+ }
568
+ if (s.cta) {
569
+ console.log(ui_1.ui.divider('CTA'));
570
+ console.log(` ${chalk_1.default.cyan(String(s.cta))}`);
571
+ console.log('');
572
+ }
573
+ if (s.personalization_notes) {
574
+ console.log(chalk_1.default.dim(` Personalization: ${s.personalization_notes}`));
575
+ console.log('');
576
+ }
577
+ }
578
+ catch (e) {
579
+ fail(spinner, 'Failed to generate outreach', e);
580
+ }
581
+ });
582
+ // ── opportunities ───────────────────────────────────────────────────
583
+ exports.salesCommand
584
+ .command('opportunities')
585
+ .description('AI-identified growth opportunities')
586
+ .option('--json', 'Output as JSON')
587
+ .action(async (opts) => {
588
+ requireAuth();
589
+ const spinner = (0, ora_1.default)({ text: 'Analyzing growth opportunities...', stream: process.stderr }).start();
590
+ try {
591
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
592
+ verb: 'sales.opportunities',
593
+ args: {},
594
+ });
595
+ spinner.stop();
596
+ const data = unwrapDispatch(res.data);
597
+ if ((0, json_output_1.isJsonOutput)(opts)) {
598
+ console.log(JSON.stringify(data, null, 2));
599
+ return;
600
+ }
601
+ const evidence = (data.evidence || {});
602
+ console.log(ui_1.ui.header('Growth Opportunities'));
603
+ if (evidence.health_score != null)
604
+ console.log(ui_1.ui.label('Health score', `${evidence.health_score}/100`));
605
+ if (evidence.health_summary)
606
+ console.log(ui_1.ui.label('Summary', String(evidence.health_summary)));
607
+ console.log(ui_1.ui.label('Confidence', `${data.confidence_band || '—'} (${((Number(data.confidence) || 0) * 100).toFixed(0)}%)`));
608
+ console.log('');
609
+ const opps = (data.suggestions || []);
610
+ for (let i = 0; i < opps.length; i++) {
611
+ const o = opps[i];
612
+ const impact = String(o.impact || 'medium');
613
+ const effort = String(o.effort || 'medium');
614
+ const badge = impact === 'high' ? chalk_1.default.green('HIGH IMPACT') : impact === 'low' ? chalk_1.default.dim('low impact') : chalk_1.default.yellow('med impact');
615
+ console.log(` ${i + 1}. ${chalk_1.default.bold(String(o.title))} ${chalk_1.default.dim(`[${badge}, ${effort} effort]`)}`);
616
+ if (o.description)
617
+ console.log(` ${chalk_1.default.dim(String(o.description))}`);
618
+ }
619
+ console.log('');
620
+ }
621
+ catch (e) {
622
+ fail(spinner, 'Failed to analyze opportunities', e);
623
+ }
624
+ });
625
+ // ── compare ─────────────────────────────────────────────────────────
626
+ exports.salesCommand
627
+ .command('compare [period]')
628
+ .description('Period-over-period comparison with AI analysis')
629
+ .option('--json', 'Output as JSON')
630
+ .action(async (period, opts) => {
631
+ requireAuth();
632
+ const p = period || 'mtd';
633
+ const spinner = (0, ora_1.default)({ text: `Comparing ${p.toUpperCase()} periods...`, stream: process.stderr }).start();
634
+ try {
635
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
636
+ verb: 'sales.compare',
637
+ args: { period: p },
638
+ });
639
+ spinner.stop();
640
+ const data = unwrapDispatch(res.data);
641
+ if ((0, json_output_1.isJsonOutput)(opts)) {
642
+ console.log(JSON.stringify(data, null, 2));
643
+ return;
644
+ }
645
+ const suggestions = (data.suggestions || []);
646
+ const s = suggestions[0] || {};
647
+ const current = (s.current || {});
648
+ const previous = (s.previous || {});
649
+ const deltas = (s.deltas || {});
650
+ const analysis = (s.analysis || {});
651
+ console.log(ui_1.ui.header(`Sales Comparison — ${p.toUpperCase()}`));
652
+ console.log(ui_1.ui.table(['Metric', 'Current', 'Previous', 'Change'], [
653
+ ['Revenue', dollarsDirect(current.revenue ?? 0), dollarsDirect(previous.revenue ?? 0), deltas.revenue != null ? `${Number(deltas.revenue) > 0 ? '+' : ''}${deltas.revenue}%` : '—'],
654
+ ['Deals created', String(current.deals_created ?? 0), String(previous.deals_created ?? 0), deltas.deals_created != null ? `${Number(deltas.deals_created) > 0 ? '+' : ''}${deltas.deals_created}%` : '—'],
655
+ ['Deals won', String(current.deals_won ?? 0), String(previous.deals_won ?? 0), deltas.deals_won != null ? `${Number(deltas.deals_won) > 0 ? '+' : ''}${deltas.deals_won}%` : '—'],
656
+ ['Win rate', pct(current.win_rate), pct(previous.win_rate), deltas.win_rate != null ? `${Number(deltas.win_rate) > 0 ? '+' : ''}${deltas.win_rate}%` : '—'],
657
+ ['New leads', String(current.new_leads ?? 0), String(previous.new_leads ?? 0), deltas.new_leads != null ? `${Number(deltas.new_leads) > 0 ? '+' : ''}${deltas.new_leads}%` : '—'],
658
+ ['Avg deal', dollarsDirect(current.avg_deal_value ?? 0), dollarsDirect(previous.avg_deal_value ?? 0), deltas.avg_deal_value != null ? `${Number(deltas.avg_deal_value) > 0 ? '+' : ''}${deltas.avg_deal_value}%` : '—'],
659
+ ]));
660
+ console.log('');
661
+ if (analysis.trend)
662
+ console.log(ui_1.ui.label('Trend', String(analysis.trend)));
663
+ if (analysis.key_change)
664
+ console.log(ui_1.ui.label('Key change', String(analysis.key_change)));
665
+ if (analysis.summary) {
666
+ console.log('');
667
+ console.log(` ${chalk_1.default.dim(String(analysis.summary))}`);
668
+ }
669
+ if (analysis.recommendation) {
670
+ console.log('');
671
+ console.log(` ${chalk_1.default.cyan('→')} ${analysis.recommendation}`);
672
+ }
673
+ console.log('');
674
+ }
675
+ catch (e) {
676
+ fail(spinner, 'Failed to compare periods', e);
677
+ }
678
+ });
679
+ // ═══════════════════════════════════════════════════════════════════
680
+ // Phase 3 — Write verbs (consent-gated)
681
+ // ═══════════════════════════════════════════════════════════════════
682
+ // ── qualify ─────────────────────────────────────────────────────────
683
+ exports.salesCommand
684
+ .command('qualify <lead_id>')
685
+ .description('Promote a lead to a qualified deal')
686
+ .option('--title <title>', 'Deal title (auto-generated if omitted)')
687
+ .option('--value <amount>', 'Deal value in dollars')
688
+ .option('--stage <stage>', 'Initial stage', 'Qualified')
689
+ .option('--notes <notes>', 'Notes for the deal')
690
+ .option('--yes', 'Confirm (required in agent mode)')
691
+ .option('--json', 'Output as JSON')
692
+ .action(async (leadId, opts) => {
693
+ requireAuth();
694
+ const spinner = (0, ora_1.default)({ text: 'Qualifying lead...', stream: process.stderr }).start();
695
+ try {
696
+ const args = { lead_id: parseInt(leadId, 10), stage: opts.stage };
697
+ if (opts.title)
698
+ args.title = opts.title;
699
+ if (opts.value)
700
+ args.value = parseFloat(opts.value);
701
+ if (opts.notes)
702
+ args.notes = opts.notes;
703
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
704
+ verb: 'sales.qualify', args, confirm: true,
705
+ });
706
+ const data = unwrapDispatch(res.data);
707
+ spinner.succeed(chalk_1.default.green('Lead qualified'));
708
+ if ((0, json_output_1.isJsonOutput)(opts)) {
709
+ console.log(JSON.stringify(data, null, 2));
710
+ return;
711
+ }
712
+ console.log(ui_1.ui.successBox('Deal Created', [
713
+ `Deal ID: ${data.deal_id}`,
714
+ `Title: ${data.title}`,
715
+ `Stage: ${data.stage}`,
716
+ `Contact: ${data.contact_name || `#${leadId}`}`,
717
+ data.value ? `Value: $${data.value}` : '',
718
+ ].filter(Boolean)));
719
+ console.log('');
720
+ }
721
+ catch (e) {
722
+ fail(spinner, 'Failed to qualify lead', e);
723
+ }
724
+ });
725
+ // ── advance ─────────────────────────────────────────────────────────
726
+ exports.salesCommand
727
+ .command('advance <deal_id>')
728
+ .description('Move a deal to the next pipeline stage')
729
+ .option('--stage <stage>', 'Target stage (auto-advances if omitted)')
730
+ .option('--notes <notes>', 'Notes')
731
+ .option('--yes', 'Confirm (required in agent mode)')
732
+ .option('--json', 'Output as JSON')
733
+ .action(async (dealId, opts) => {
734
+ requireAuth();
735
+ const spinner = (0, ora_1.default)({ text: 'Advancing deal...', stream: process.stderr }).start();
736
+ try {
737
+ const args = { deal_id: parseInt(dealId, 10) };
738
+ if (opts.stage)
739
+ args.stage = opts.stage;
740
+ if (opts.notes)
741
+ args.notes = opts.notes;
742
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
743
+ verb: 'sales.advance', args, confirm: true,
744
+ });
745
+ const data = unwrapDispatch(res.data);
746
+ spinner.succeed(chalk_1.default.green('Deal advanced'));
747
+ if ((0, json_output_1.isJsonOutput)(opts)) {
748
+ console.log(JSON.stringify(data, null, 2));
749
+ return;
750
+ }
751
+ console.log(ui_1.ui.successBox('Deal Advanced', [
752
+ `Deal: ${data.title || `#${dealId}`}`,
753
+ `${data.old_stage} → ${chalk_1.default.green(String(data.new_stage))}`,
754
+ ]));
755
+ console.log('');
756
+ }
757
+ catch (e) {
758
+ fail(spinner, 'Failed to advance deal', e);
759
+ }
760
+ });
761
+ // ── close ───────────────────────────────────────────────────────────
762
+ exports.salesCommand
763
+ .command('close <deal_id>')
764
+ .description('Close a deal as won or lost')
765
+ .option('--outcome <outcome>', 'won or lost', 'won')
766
+ .option('--reason <reason>', 'Close reason')
767
+ .option('--yes', 'Confirm (required in agent mode)')
768
+ .option('--json', 'Output as JSON')
769
+ .action(async (dealId, opts) => {
770
+ requireAuth();
771
+ const spinner = (0, ora_1.default)({ text: 'Closing deal...', stream: process.stderr }).start();
772
+ try {
773
+ const args = { deal_id: parseInt(dealId, 10), outcome: opts.outcome };
774
+ if (opts.reason)
775
+ args.close_reason = opts.reason;
776
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
777
+ verb: 'sales.close', args, confirm: true,
778
+ });
779
+ const data = unwrapDispatch(res.data);
780
+ spinner.succeed(chalk_1.default.green(`Deal closed — ${data.outcome}`));
781
+ if ((0, json_output_1.isJsonOutput)(opts)) {
782
+ console.log(JSON.stringify(data, null, 2));
783
+ return;
784
+ }
785
+ const color = String(data.outcome).toLowerCase() === 'won' ? chalk_1.default.green : chalk_1.default.red;
786
+ console.log(ui_1.ui.successBox('Deal Closed', [
787
+ `Deal: ${data.title || `#${dealId}`}`,
788
+ `Outcome: ${color(String(data.outcome))}`,
789
+ data.amount ? `Value: $${data.amount}` : '',
790
+ ].filter(Boolean)));
791
+ console.log('');
792
+ }
793
+ catch (e) {
794
+ fail(spinner, 'Failed to close deal', e);
795
+ }
796
+ });
797
+ // ── nurture ─────────────────────────────────────────────────────────
798
+ exports.salesCommand
799
+ .command('nurture <lead_id>')
800
+ .description('Send a nurture step to a contact')
801
+ .option('--channel <channel>', 'email, sms, call_script, or voice', 'email')
802
+ .option('--message <message>', 'Custom message')
803
+ .option('--yes', 'Confirm (required in agent mode)')
804
+ .option('--json', 'Output as JSON')
805
+ .action(async (leadId, opts) => {
806
+ requireAuth();
807
+ const spinner = (0, ora_1.default)({ text: `Sending ${opts.channel} nurture...`, stream: process.stderr }).start();
808
+ try {
809
+ const args = { lead_id: parseInt(leadId, 10), channel: opts.channel };
810
+ if (opts.message)
811
+ args.message = opts.message;
812
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
813
+ verb: 'sales.nurture', args, confirm: true,
814
+ });
815
+ const data = unwrapDispatch(res.data);
816
+ spinner.succeed(chalk_1.default.green(`Nurture sent via ${data.channel}`));
817
+ if ((0, json_output_1.isJsonOutput)(opts)) {
818
+ console.log(JSON.stringify(data, null, 2));
819
+ return;
820
+ }
821
+ console.log(ui_1.ui.successBox('Nurture Sent', [
822
+ `Contact: ${data.contact_name || `#${leadId}`}`,
823
+ `Channel: ${data.channel}`,
824
+ `Follow-up task: #${data.followup_task_id} (due ${data.followup_due ? String(data.followup_due).split('T')[0] : '3 days'})`,
825
+ ]));
826
+ console.log('');
827
+ }
828
+ catch (e) {
829
+ fail(spinner, 'Failed to nurture contact', e);
830
+ }
831
+ });
832
+ // ── sequence ────────────────────────────────────────────────────────
833
+ const sequenceCommand = new commander_1.Command('sequence')
834
+ .description('Multi-step outreach sequences');
835
+ sequenceCommand
836
+ .command('create')
837
+ .description('Create a multi-step outreach sequence')
838
+ .requiredOption('--name <name>', 'Sequence name')
839
+ .option('--leads <ids>', 'Comma-separated lead IDs')
840
+ .option('--yes', 'Confirm (required in agent mode)')
841
+ .option('--json', 'Output as JSON')
842
+ .action(async (opts) => {
843
+ requireAuth();
844
+ const spinner = (0, ora_1.default)({ text: 'Creating sequence...', stream: process.stderr }).start();
845
+ try {
846
+ const args = { name: opts.name };
847
+ if (opts.leads)
848
+ args.lead_ids = opts.leads.split(',').map((s) => parseInt(s.trim(), 10));
849
+ const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
850
+ verb: 'sales.sequence_create', args, confirm: true,
851
+ });
852
+ const data = unwrapDispatch(res.data);
853
+ spinner.succeed(chalk_1.default.green('Sequence created'));
854
+ if ((0, json_output_1.isJsonOutput)(opts)) {
855
+ console.log(JSON.stringify(data, null, 2));
856
+ return;
857
+ }
858
+ console.log(ui_1.ui.successBox('Sequence Created', [
859
+ `Name: ${data.sequence_name}`,
860
+ `Contacts enrolled: ${data.contacts_enrolled}`,
861
+ `Steps: ${data.steps}`,
862
+ `Tasks scheduled: ${data.tasks_created}`,
863
+ ]));
864
+ if (data.invalid_lead_ids?.length) {
865
+ console.log(chalk_1.default.yellow(` Skipped invalid IDs: ${data.invalid_lead_ids.join(', ')}`));
866
+ }
867
+ console.log('');
868
+ }
869
+ catch (e) {
870
+ fail(spinner, 'Failed to create sequence', e);
871
+ }
872
+ });
873
+ exports.salesCommand.addCommand(sequenceCommand);
874
+ // ── Help ────────────────────────────────────────────────────────────
875
+ exports.salesCommand.addHelpText('after', `
876
+ Examples:
877
+ $ solid sales pipeline Full pipeline by stage
878
+ $ solid sales prospects High-value leads ranked by score
879
+ $ solid sales sources --days 60 Lead source performance (60-day window)
880
+ $ solid sales forecast Revenue forecast with weighted pipeline
881
+ $ solid sales report mtd Month-to-date performance report
882
+ $ solid sales velocity Deal velocity and cycle time
883
+ $ solid sales followup Overdue follow-ups
884
+ $ solid sales win-rate Win/loss funnel analysis
885
+ $ solid sales score 42 AI-score a lead with industry reasoning
886
+ $ solid sales suggest 17 Next-best-action for a deal
887
+ $ solid sales outreach 42 --sms Generate personalized SMS outreach
888
+ $ solid sales opportunities AI growth opportunities
889
+ $ solid sales compare qtd Quarter-over-quarter comparison
890
+ $ solid sales qualify 42 Promote lead → deal
891
+ $ solid sales advance 17 Move deal to next stage
892
+ $ solid sales close 17 --outcome won Close a deal
893
+ $ solid sales nurture 42 --sms Send SMS nurture step
894
+ $ solid sales sequence create --name "New Leads" --leads 42,43,44
895
+ `);
896
+ //# sourceMappingURL=sales.js.map