@solidnumber/cli 2.11.11 → 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.
- package/README.md +1 -1
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +5 -21
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/billing.d.ts.map +1 -1
- package/dist/commands/billing.js +10 -2
- package/dist/commands/billing.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +4 -2
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/mcp.js +86 -0
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/orders.d.ts.map +1 -1
- package/dist/commands/orders.js +10 -0
- package/dist/commands/orders.js.map +1 -1
- package/dist/commands/payment.d.ts.map +1 -1
- package/dist/commands/payment.js +5 -2
- package/dist/commands/payment.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +4 -2
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +9 -87
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/sales.d.ts +19 -0
- package/dist/commands/sales.d.ts.map +1 -0
- package/dist/commands/sales.js +896 -0
- package/dist/commands/sales.js.map +1 -0
- package/dist/commands/schema.js +208 -1
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +93 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/webhooks.d.ts.map +1 -1
- package/dist/commands/webhooks.js +4 -0
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/data/workflow-recipes.json +132 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-mode.d.ts.map +1 -1
- package/dist/lib/agent-mode.js +3 -0
- package/dist/lib/agent-mode.js.map +1 -1
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +13 -9
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/command-kit.d.ts +10 -0
- package/dist/lib/command-kit.d.ts.map +1 -1
- package/dist/lib/command-kit.js +17 -0
- package/dist/lib/command-kit.js.map +1 -1
- package/dist/lib/offline-queue.d.ts +1 -1
- package/dist/lib/offline-queue.d.ts.map +1 -1
- package/dist/lib/offline-queue.js +2 -2
- package/dist/lib/offline-queue.js.map +1 -1
- package/dist/lib/persona-groups.d.ts +1 -1
- package/dist/lib/persona-groups.d.ts.map +1 -1
- package/dist/lib/persona-groups.js +4 -1
- package/dist/lib/persona-groups.js.map +1 -1
- package/dist/lib/push-utils.d.ts +39 -0
- package/dist/lib/push-utils.d.ts.map +1 -0
- package/dist/lib/push-utils.js +120 -0
- package/dist/lib/push-utils.js.map +1 -0
- package/dist/lib/url-utils.d.ts +5 -0
- package/dist/lib/url-utils.d.ts.map +1 -0
- package/dist/lib/url-utils.js +23 -0
- package/dist/lib/url-utils.js.map +1 -0
- package/dist/lib/verb-metadata.d.ts +105 -0
- package/dist/lib/verb-metadata.d.ts.map +1 -0
- package/dist/lib/verb-metadata.js +171 -0
- package/dist/lib/verb-metadata.js.map +1 -0
- package/package.json +3 -2
- 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
|