@solidnumber/cli 2.10.2 → 2.11.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.
- package/README.md +1 -1
- package/dist/commands/agent.js +60 -0
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/demo.d.ts +8 -0
- package/dist/commands/demo.d.ts.map +1 -1
- package/dist/commands/demo.js +114 -0
- package/dist/commands/demo.js.map +1 -1
- package/dist/commands/dispatch_shortcuts.d.ts +20 -0
- package/dist/commands/dispatch_shortcuts.d.ts.map +1 -0
- package/dist/commands/dispatch_shortcuts.js +177 -0
- package/dist/commands/dispatch_shortcuts.js.map +1 -0
- package/dist/commands/voice.js +306 -0
- package/dist/commands/voice.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platform-docs/llms.txt +9 -6
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.leadDispatchCommand = exports.calendarCommand = exports.dealCommand = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Per-domain shortcuts that wrap `solid agent dispatch` with cleaner
|
|
9
|
+
* ergonomics. All of these are equivalent to:
|
|
10
|
+
*
|
|
11
|
+
* solid agent dispatch <verb> --args '{...}' --confirm
|
|
12
|
+
*
|
|
13
|
+
* but read better in shell history + tab-completion. The backend
|
|
14
|
+
* contract is identical — they all POST to /api/v1/ada/cli-dispatch
|
|
15
|
+
* with the same role gates / nonce signing / audit log.
|
|
16
|
+
*
|
|
17
|
+
* Used by both humans and the CLI-as-agent users (per memory
|
|
18
|
+
* feedback_cli_is_for_ai). The universal `solid agent dispatch`
|
|
19
|
+
* remains the escape hatch for any verb that doesn't have a wrapper
|
|
20
|
+
* yet.
|
|
21
|
+
*/
|
|
22
|
+
const commander_1 = require("commander");
|
|
23
|
+
const ora_1 = __importDefault(require("ora"));
|
|
24
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
25
|
+
const config_1 = require("../lib/config");
|
|
26
|
+
const api_client_1 = require("../lib/api-client");
|
|
27
|
+
const json_output_1 = require("../lib/json-output");
|
|
28
|
+
function requireAuth() {
|
|
29
|
+
if (!config_1.config.isLoggedIn()) {
|
|
30
|
+
console.error(chalk_1.default.red('Not logged in. Run `solid auth login` first.'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function _dispatch(verb, args, options, spinnerLabel) {
|
|
35
|
+
requireAuth();
|
|
36
|
+
const spinner = (0, ora_1.default)(spinnerLabel).start();
|
|
37
|
+
try {
|
|
38
|
+
const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', { verb, args, confirm: true });
|
|
39
|
+
const data = res.data || {};
|
|
40
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
41
|
+
spinner.stop();
|
|
42
|
+
console.log(JSON.stringify(data, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (data.ok === false || data.error) {
|
|
46
|
+
const reason = data?.error?.reason || 'unknown';
|
|
47
|
+
const msg = data?.error?.message || 'Verb denied or failed.';
|
|
48
|
+
spinner.fail(chalk_1.default.red(`${verb}: ${reason}`));
|
|
49
|
+
console.error(chalk_1.default.red(` ${msg}`));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
spinner.succeed(chalk_1.default.green(`${verb}: ${data?.result?.status || 'ok'}`));
|
|
53
|
+
if (data?.result) {
|
|
54
|
+
const compact = {};
|
|
55
|
+
for (const key of Object.keys(data.result).slice(0, 6)) {
|
|
56
|
+
const v = data.result[key];
|
|
57
|
+
compact[key] = typeof v === 'string' ? (v.length > 60 ? v.slice(0, 57) + '...' : v) : v;
|
|
58
|
+
}
|
|
59
|
+
if (Object.keys(compact).length > 0) {
|
|
60
|
+
console.log(chalk_1.default.dim(' ' + JSON.stringify(compact, null, 2).replace(/\n/g, '\n ')));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
spinner.fail(chalk_1.default.red(`Dispatch failed: ${(0, api_client_1.handleApiError)(error).message}`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ── solid deal ───────────────────────────────────────────────────────
|
|
70
|
+
exports.dealCommand = new commander_1.Command('deal')
|
|
71
|
+
.description('Sales deals — create / advance / mark won (wraps Jackson verbs)');
|
|
72
|
+
exports.dealCommand
|
|
73
|
+
.command('create')
|
|
74
|
+
.description('Create a new sales deal')
|
|
75
|
+
.requiredOption('--title <text>', 'Deal title')
|
|
76
|
+
.requiredOption('--contact-id <id>', 'Owning CRM contact ID')
|
|
77
|
+
.option('--amount-cents <n>', 'Deal value in cents')
|
|
78
|
+
.option('--stage <stage>', 'Initial stage (new / qualified / proposal_sent / negotiation)', 'new')
|
|
79
|
+
.option('--notes <text>', 'Optional notes')
|
|
80
|
+
.option('--confirm', 'Required: actually create the deal')
|
|
81
|
+
.option('--json', 'Output as JSON')
|
|
82
|
+
.action(async (options) => {
|
|
83
|
+
if (!options.confirm) {
|
|
84
|
+
console.error(chalk_1.default.red('Refusing to create a deal without --confirm.'));
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
const args = {
|
|
88
|
+
title: options.title,
|
|
89
|
+
contact_id: Number(options.contactId),
|
|
90
|
+
stage: options.stage,
|
|
91
|
+
};
|
|
92
|
+
if (options.amountCents)
|
|
93
|
+
args.amount_cents = Number(options.amountCents);
|
|
94
|
+
if (options.notes)
|
|
95
|
+
args.notes = options.notes;
|
|
96
|
+
await _dispatch('deals_create', args, options, `Creating deal "${options.title}"...`);
|
|
97
|
+
});
|
|
98
|
+
exports.dealCommand
|
|
99
|
+
.command('advance <deal_id> <to_stage>')
|
|
100
|
+
.description('Move a deal to the next stage (one step at a time)')
|
|
101
|
+
.option('--confirm', 'Required: actually advance the deal')
|
|
102
|
+
.option('--json', 'Output as JSON')
|
|
103
|
+
.action(async (dealId, toStage, options) => {
|
|
104
|
+
if (!options.confirm) {
|
|
105
|
+
console.error(chalk_1.default.red('Refusing to advance a deal without --confirm.'));
|
|
106
|
+
process.exit(2);
|
|
107
|
+
}
|
|
108
|
+
await _dispatch('deals_advance', { deal_id: Number(dealId), to_stage: toStage }, options, `Advancing deal ${dealId} → ${toStage}...`);
|
|
109
|
+
});
|
|
110
|
+
exports.dealCommand
|
|
111
|
+
.command('won <deal_id>')
|
|
112
|
+
.description('Close a deal as won (records revenue attribution)')
|
|
113
|
+
.option('--amount-cents <n>', 'Final closed-won amount in cents')
|
|
114
|
+
.option('--confirm', 'Required: actually close the deal')
|
|
115
|
+
.option('--json', 'Output as JSON')
|
|
116
|
+
.action(async (dealId, options) => {
|
|
117
|
+
if (!options.confirm) {
|
|
118
|
+
console.error(chalk_1.default.red('Refusing to mark a deal won without --confirm.'));
|
|
119
|
+
process.exit(2);
|
|
120
|
+
}
|
|
121
|
+
const args = { deal_id: Number(dealId) };
|
|
122
|
+
if (options.amountCents)
|
|
123
|
+
args.amount_cents = Number(options.amountCents);
|
|
124
|
+
await _dispatch('deals_won', args, options, `Closing deal ${dealId} as won...`);
|
|
125
|
+
});
|
|
126
|
+
// ── solid calendar ───────────────────────────────────────────────────
|
|
127
|
+
exports.calendarCommand = new commander_1.Command('calendar')
|
|
128
|
+
.description('Google Calendar event create (requires Google linked)');
|
|
129
|
+
exports.calendarCommand
|
|
130
|
+
.command('event')
|
|
131
|
+
.description('Create a Google Calendar event on the user\'s primary calendar')
|
|
132
|
+
.requiredOption('--title <text>', 'Event title')
|
|
133
|
+
.requiredOption('--start <iso>', 'Start time, ISO-8601 with tz (e.g. 2026-06-01T15:00:00-06:00)')
|
|
134
|
+
.requiredOption('--end <iso>', 'End time, ISO-8601 with tz')
|
|
135
|
+
.option('--attendees <emails>', 'Comma-separated attendee emails')
|
|
136
|
+
.option('--description <text>', 'Event body / agenda')
|
|
137
|
+
.option('--location <text>', 'Where')
|
|
138
|
+
.option('--meet', 'Attach a Google Meet link')
|
|
139
|
+
.option('--confirm', 'Required: actually create the event')
|
|
140
|
+
.option('--json', 'Output as JSON')
|
|
141
|
+
.action(async (options) => {
|
|
142
|
+
if (!options.confirm) {
|
|
143
|
+
console.error(chalk_1.default.red('Refusing to create an event without --confirm.'));
|
|
144
|
+
process.exit(2);
|
|
145
|
+
}
|
|
146
|
+
const attendees = options.attendees
|
|
147
|
+
? String(options.attendees).split(',').map((s) => s.trim()).filter(Boolean)
|
|
148
|
+
: [];
|
|
149
|
+
const args = {
|
|
150
|
+
title: options.title,
|
|
151
|
+
start_time: options.start,
|
|
152
|
+
end_time: options.end,
|
|
153
|
+
};
|
|
154
|
+
if (attendees.length)
|
|
155
|
+
args.attendees = attendees;
|
|
156
|
+
if (options.description)
|
|
157
|
+
args.description = options.description;
|
|
158
|
+
if (options.location)
|
|
159
|
+
args.location = options.location;
|
|
160
|
+
if (options.meet)
|
|
161
|
+
args.with_meet_link = true;
|
|
162
|
+
await _dispatch('google_calendar_event_create', args, options, `Creating event "${options.title}"...`);
|
|
163
|
+
});
|
|
164
|
+
// ── solid lead promote ───────────────────────────────────────────────
|
|
165
|
+
exports.leadDispatchCommand = new commander_1.Command('lead-promote')
|
|
166
|
+
.description('Promote a lead_submission to a real CRM contact')
|
|
167
|
+
.argument('<lead_id>', 'LeadSubmission ID')
|
|
168
|
+
.option('--confirm', 'Required: actually promote the lead')
|
|
169
|
+
.option('--json', 'Output as JSON')
|
|
170
|
+
.action(async (leadId, options) => {
|
|
171
|
+
if (!options.confirm) {
|
|
172
|
+
console.error(chalk_1.default.red('Refusing to promote a lead without --confirm.'));
|
|
173
|
+
process.exit(2);
|
|
174
|
+
}
|
|
175
|
+
await _dispatch('crm_lead_promote', { lead_id: Number(leadId) }, options, `Promoting lead ${leadId} to contact...`);
|
|
176
|
+
});
|
|
177
|
+
//# sourceMappingURL=dispatch_shortcuts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch_shortcuts.js","sourceRoot":"","sources":["../../src/commands/dispatch_shortcuts.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/voice.js
CHANGED
|
@@ -568,13 +568,319 @@ exports.voiceCommand
|
|
|
568
568
|
fail(spinner, 'Failed to load cost summary', error);
|
|
569
569
|
}
|
|
570
570
|
});
|
|
571
|
+
// ── Health (per-tenant readiness audit) ─────────────────────────────
|
|
572
|
+
exports.voiceCommand
|
|
573
|
+
.command('health')
|
|
574
|
+
.description('Per-tenant voice readiness audit — every layer that must work for calls to actually answer')
|
|
575
|
+
.option('--company <id>', 'Audit a specific company_id (default: authenticated company)')
|
|
576
|
+
.option('--json', 'Output as JSON')
|
|
577
|
+
.action(async (options) => {
|
|
578
|
+
requireAuth();
|
|
579
|
+
const companyId = options.company ?? config_1.config.companyId;
|
|
580
|
+
if (!companyId) {
|
|
581
|
+
console.error(chalk_1.default.red('No company_id available (login or pass --company)'));
|
|
582
|
+
process.exit(1);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const spinner = (0, ora_1.default)(`Auditing voice readiness for company ${companyId}...`).start();
|
|
586
|
+
try {
|
|
587
|
+
const response = await api_client_1.apiClient.get(`/api/v1/voice/health/${companyId}`);
|
|
588
|
+
const r = response.data;
|
|
589
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
590
|
+
spinner.stop();
|
|
591
|
+
console.log(JSON.stringify(r, null, 2));
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const overall = r.overall ?? 'unknown';
|
|
595
|
+
const overallChalk = overall === 'ok' ? chalk_1.default.green : overall === 'warn' ? chalk_1.default.yellow : chalk_1.default.red;
|
|
596
|
+
spinner.succeed(`Voice readiness for company ${companyId}: ${overallChalk(overall.toUpperCase())}`);
|
|
597
|
+
console.log('');
|
|
598
|
+
for (const [name, check] of Object.entries(r.checks ?? {})) {
|
|
599
|
+
const tag = check.status === 'ok' ? chalk_1.default.green(' ✓ ') : check.status === 'warn' ? chalk_1.default.yellow(' ⚠ ') : chalk_1.default.red(' ✗ ');
|
|
600
|
+
console.log(`${tag}${chalk_1.default.bold(name.padEnd(20))}${check.message}`);
|
|
601
|
+
}
|
|
602
|
+
console.log('');
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
fail(spinner, 'Voice health audit failed', error);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
// ── Diagnose (per-call event timeline) ───────────────────────────────
|
|
609
|
+
exports.voiceCommand
|
|
610
|
+
.command('diagnose <call_id>')
|
|
611
|
+
.description('Walk the production logs for one voice call and produce a per-event timeline + verdict')
|
|
612
|
+
.option('--json', 'Output raw JSON (default: human-readable)')
|
|
613
|
+
.action(async (callId, options) => {
|
|
614
|
+
requireAuth();
|
|
615
|
+
const spinner = (0, ora_1.default)(`Diagnosing call ${callId}...`).start();
|
|
616
|
+
try {
|
|
617
|
+
const response = await api_client_1.apiClient.get(`/api/v1/voice/diagnose/${callId}`);
|
|
618
|
+
const r = response.data;
|
|
619
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
620
|
+
spinner.stop();
|
|
621
|
+
console.log(JSON.stringify(r, null, 2));
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const verdict = r.verdict ?? 'unknown';
|
|
625
|
+
const tag = verdict === 'ok' ? chalk_1.default.green('OK') : chalk_1.default.red(verdict.toUpperCase());
|
|
626
|
+
spinner.succeed(`Call ${callId}: ${tag}`);
|
|
627
|
+
console.log('');
|
|
628
|
+
console.log(` ${chalk_1.default.bold('Session configured:')} ${r.session_configured ? chalk_1.default.green('yes') : chalk_1.default.red('no')}`);
|
|
629
|
+
console.log(` ${chalk_1.default.bold('Greetings triggered:')} ${r.greeting_count}`);
|
|
630
|
+
console.log(` ${chalk_1.default.bold('Response.done events:')} ${r.response_done_count}`);
|
|
631
|
+
console.log(` ${chalk_1.default.bold('Barge-ins:')} ${r.barge_in_count}`);
|
|
632
|
+
console.log(` ${chalk_1.default.bold('Audio chunks to Twilio:')} ${r.audio_chunks_to_twilio}`);
|
|
633
|
+
if (r.cost) {
|
|
634
|
+
console.log('');
|
|
635
|
+
console.log(` ${chalk_1.default.bold('Cost:')} ${r.cost.cents}¢ across ${r.cost.responses} responses`);
|
|
636
|
+
if (r.cost.cache_hit_rate_pct !== null) {
|
|
637
|
+
console.log(` ${chalk_1.default.bold('Cache hit rate:')} ${r.cost.cache_hit_rate_pct}%`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (r.issues?.length) {
|
|
641
|
+
console.log('');
|
|
642
|
+
console.log(chalk_1.default.yellow('Issues:'));
|
|
643
|
+
for (const issue of r.issues)
|
|
644
|
+
console.log(` ${chalk_1.default.yellow('●')} ${issue}`);
|
|
645
|
+
}
|
|
646
|
+
console.log('');
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
fail(spinner, 'Diagnose failed', error);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
// ── Outbound dispatch (ADA verbs) ────────────────────────────────────
|
|
653
|
+
//
|
|
654
|
+
// `solid voice call <contact-query>` and `solid voice text <contact-query>`
|
|
655
|
+
// dispatch an outbound voice call or SMS through the same backend verb
|
|
656
|
+
// registry ADA uses on /dashboard/ada. Auth + company come from the
|
|
657
|
+
// active CLI session — never from args. The --confirm flag is required
|
|
658
|
+
// (it's the CLI equivalent of clicking Confirm on the dashboard card).
|
|
659
|
+
//
|
|
660
|
+
// See Owners-Manual/42-UVX-User-Voice-Experience/13-ADA-OUTBOUND-DISPATCH.md.
|
|
661
|
+
async function _resolveContactId(query) {
|
|
662
|
+
// Try numeric ID first; otherwise use the typeahead endpoint.
|
|
663
|
+
const id = Number.parseInt(query, 10);
|
|
664
|
+
if (!Number.isNaN(id) && /^\d+$/.test(query.trim())) {
|
|
665
|
+
try {
|
|
666
|
+
const r = await api_client_1.apiClient.get(`/api/v1/crm/contacts/${id}`);
|
|
667
|
+
const c = r.data || {};
|
|
668
|
+
if (c?.id)
|
|
669
|
+
return { id: c.id, name: `${c.first_name ?? ''} ${c.last_name ?? ''}`.trim(), phone: c.phone ?? null };
|
|
670
|
+
}
|
|
671
|
+
catch { /* fall through to search */ }
|
|
672
|
+
}
|
|
673
|
+
const r = await api_client_1.apiClient.get('/api/v1/crm/contacts/typeahead', { params: { q: query, limit: 5 } });
|
|
674
|
+
const list = Array.isArray(r.data) ? r.data : [];
|
|
675
|
+
if (list.length === 0)
|
|
676
|
+
return null;
|
|
677
|
+
if (list.length > 1) {
|
|
678
|
+
console.error(chalk_1.default.yellow(`Multiple matches for "${query}":`));
|
|
679
|
+
for (const c of list)
|
|
680
|
+
console.error(` ${chalk_1.default.cyan(c.id)} ${c.name} ${chalk_1.default.dim(c.phone || '')}`);
|
|
681
|
+
console.error(chalk_1.default.dim('Re-run with a numeric ID to disambiguate.'));
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
return { id: list[0].id, name: list[0].name, phone: list[0].phone };
|
|
685
|
+
}
|
|
686
|
+
exports.voiceCommand
|
|
687
|
+
.command('call <contact-query>')
|
|
688
|
+
.description('Dispatch an outbound voice call through Sarah (or another agent)')
|
|
689
|
+
.option('-i, --intent <text>', 'One-sentence purpose of the call (passed to the agent brief)', 'follow-up')
|
|
690
|
+
.option('-a, --agent <type>', 'Agent type to dispatch', 'customer_service')
|
|
691
|
+
.option('--confirm', 'Required: confirm this call should be placed')
|
|
692
|
+
.option('--json', 'Output as JSON')
|
|
693
|
+
.action(async (contactQuery, options) => {
|
|
694
|
+
requireAuth();
|
|
695
|
+
if (!options.confirm) {
|
|
696
|
+
console.error(chalk_1.default.red('Refusing to place a call without --confirm.'));
|
|
697
|
+
console.error(chalk_1.default.dim(' Example: solid voice call "Jane Smoke" --intent "follow up on pricing" --confirm'));
|
|
698
|
+
process.exit(2);
|
|
699
|
+
}
|
|
700
|
+
const spinner = (0, ora_1.default)(`Resolving ${contactQuery}...`).start();
|
|
701
|
+
try {
|
|
702
|
+
const match = await _resolveContactId(contactQuery);
|
|
703
|
+
if (!match) {
|
|
704
|
+
spinner.fail(chalk_1.default.red(`No contact matched "${contactQuery}".`));
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
spinner.text = `Dispatching call to ${match.name}...`;
|
|
708
|
+
const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
|
|
709
|
+
verb: 'voice_outbound_call',
|
|
710
|
+
args: {
|
|
711
|
+
contact_id: match.id,
|
|
712
|
+
agent_type: options.agent,
|
|
713
|
+
intent: options.intent,
|
|
714
|
+
},
|
|
715
|
+
confirm: true,
|
|
716
|
+
});
|
|
717
|
+
const data = res.data || {};
|
|
718
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
719
|
+
spinner.stop();
|
|
720
|
+
console.log(JSON.stringify(data, null, 2));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (!data.ok) {
|
|
724
|
+
spinner.fail(chalk_1.default.red(`Call denied: ${data?.error?.reason || 'unknown'}`));
|
|
725
|
+
if (data?.error?.message)
|
|
726
|
+
console.error(chalk_1.default.red(` ${data.error.message}`));
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
const r = data.result || {};
|
|
730
|
+
spinner.succeed(chalk_1.default.green(`Call dispatched to ${match.name} (${match.phone}) — call_id=${r.call_id}`));
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
fail(spinner, 'Dispatch failed', error);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
// ── Translate (per-phone live translation toggle) ───────────────────
|
|
737
|
+
//
|
|
738
|
+
// `solid voice translate enable <phone>` / `disable <phone>` / `status <phone>`
|
|
739
|
+
// flips voice_config.translate_enabled on the matching phone row.
|
|
740
|
+
// Backend gating (Professional+ tier) is enforced at call time, not
|
|
741
|
+
// at toggle time — the CLI just sets the flag.
|
|
742
|
+
async function _findPhoneNumberRow(phone) {
|
|
743
|
+
const res = await api_client_1.apiClient.get('/api/v1/voice/phone-numbers');
|
|
744
|
+
const data = res.data;
|
|
745
|
+
const list = Array.isArray(data) ? data : (data?.phone_numbers || data?.items || []);
|
|
746
|
+
const wanted = phone.replace(/\D/g, '');
|
|
747
|
+
for (const row of list) {
|
|
748
|
+
const candidate = String(row.phone_number || '').replace(/\D/g, '');
|
|
749
|
+
if (candidate.endsWith(wanted) || wanted.endsWith(candidate)) {
|
|
750
|
+
return {
|
|
751
|
+
id: row.id,
|
|
752
|
+
phone_number: row.phone_number,
|
|
753
|
+
voice_config: (row.voice_config && typeof row.voice_config === 'object') ? row.voice_config : {},
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
async function _toggleTranslate(phone, enabled, options) {
|
|
760
|
+
requireAuth();
|
|
761
|
+
const spinner = (0, ora_1.default)(`${enabled ? 'Enabling' : 'Disabling'} live translation on ${phone}...`).start();
|
|
762
|
+
try {
|
|
763
|
+
const row = await _findPhoneNumberRow(phone);
|
|
764
|
+
if (!row) {
|
|
765
|
+
spinner.fail(chalk_1.default.red(`No phone number found matching "${phone}".`));
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
const nextConfig = { ...row.voice_config, translate_enabled: enabled };
|
|
769
|
+
const res = await api_client_1.apiClient.patch(`/api/v1/voice/phone-numbers/${row.id}`, {
|
|
770
|
+
voice_config: nextConfig,
|
|
771
|
+
});
|
|
772
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
773
|
+
spinner.stop();
|
|
774
|
+
console.log(JSON.stringify({ phone: row.phone_number, translate_enabled: enabled, response: res.data }, null, 2));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
spinner.succeed(chalk_1.default.green(`Live translation ${enabled ? 'enabled' : 'disabled'} on ${row.phone_number}.`));
|
|
778
|
+
if (enabled) {
|
|
779
|
+
console.log(chalk_1.default.dim(' Note: translate-on-call requires Professional+ tier; the gate runs at call time.'));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
fail(spinner, 'Translate toggle failed', error);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const translateCmd = exports.voiceCommand
|
|
787
|
+
.command('translate')
|
|
788
|
+
.description('Per-phone live translation toggle');
|
|
789
|
+
translateCmd
|
|
790
|
+
.command('enable <phone>')
|
|
791
|
+
.description('Enable live translation on the given phone number')
|
|
792
|
+
.option('--json', 'Output as JSON')
|
|
793
|
+
.action(async (phone, options) => _toggleTranslate(phone, true, options));
|
|
794
|
+
translateCmd
|
|
795
|
+
.command('disable <phone>')
|
|
796
|
+
.description('Disable live translation on the given phone number')
|
|
797
|
+
.option('--json', 'Output as JSON')
|
|
798
|
+
.action(async (phone, options) => _toggleTranslate(phone, false, options));
|
|
799
|
+
translateCmd
|
|
800
|
+
.command('status <phone>')
|
|
801
|
+
.description('Show whether live translation is enabled on the given phone number')
|
|
802
|
+
.option('--json', 'Output as JSON')
|
|
803
|
+
.action(async (phone, options) => {
|
|
804
|
+
requireAuth();
|
|
805
|
+
const spinner = (0, ora_1.default)(`Checking translate status for ${phone}...`).start();
|
|
806
|
+
try {
|
|
807
|
+
const row = await _findPhoneNumberRow(phone);
|
|
808
|
+
if (!row) {
|
|
809
|
+
spinner.fail(chalk_1.default.red(`No phone number found matching "${phone}".`));
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
const enabled = Boolean(row.voice_config?.translate_enabled);
|
|
813
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
814
|
+
spinner.stop();
|
|
815
|
+
console.log(JSON.stringify({ phone: row.phone_number, translate_enabled: enabled }, null, 2));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
spinner.succeed(`${row.phone_number}: translate ${enabled ? chalk_1.default.green('enabled') : chalk_1.default.dim('disabled')}`);
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
fail(spinner, 'Translate status check failed', error);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
exports.voiceCommand
|
|
825
|
+
.command('text <contact-query>')
|
|
826
|
+
.description('Send an outbound SMS to a CRM contact')
|
|
827
|
+
.requiredOption('-m, --message <text>', 'SMS body (max 1000 chars; STOP is auto-appended for TCPA)')
|
|
828
|
+
.option('-i, --intent <text>', 'Optional one-sentence reason')
|
|
829
|
+
.option('--confirm', 'Required: confirm this SMS should be sent')
|
|
830
|
+
.option('--json', 'Output as JSON')
|
|
831
|
+
.action(async (contactQuery, options) => {
|
|
832
|
+
requireAuth();
|
|
833
|
+
if (!options.confirm) {
|
|
834
|
+
console.error(chalk_1.default.red('Refusing to send an SMS without --confirm.'));
|
|
835
|
+
console.error(chalk_1.default.dim(' Example: solid voice text "Jane Smoke" --message "Quick reminder of our 3pm call." --confirm'));
|
|
836
|
+
process.exit(2);
|
|
837
|
+
}
|
|
838
|
+
const spinner = (0, ora_1.default)(`Resolving ${contactQuery}...`).start();
|
|
839
|
+
try {
|
|
840
|
+
const match = await _resolveContactId(contactQuery);
|
|
841
|
+
if (!match) {
|
|
842
|
+
spinner.fail(chalk_1.default.red(`No contact matched "${contactQuery}".`));
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
spinner.text = `Sending SMS to ${match.name}...`;
|
|
846
|
+
const res = await api_client_1.apiClient.post('/api/v1/ada/cli-dispatch', {
|
|
847
|
+
verb: 'sms_send',
|
|
848
|
+
args: {
|
|
849
|
+
contact_id: match.id,
|
|
850
|
+
message: options.message,
|
|
851
|
+
intent: options.intent,
|
|
852
|
+
},
|
|
853
|
+
confirm: true,
|
|
854
|
+
});
|
|
855
|
+
const data = res.data || {};
|
|
856
|
+
if ((0, json_output_1.isJsonOutput)(options)) {
|
|
857
|
+
spinner.stop();
|
|
858
|
+
console.log(JSON.stringify(data, null, 2));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (!data.ok) {
|
|
862
|
+
spinner.fail(chalk_1.default.red(`SMS denied: ${data?.error?.reason || 'unknown'}`));
|
|
863
|
+
if (data?.error?.message)
|
|
864
|
+
console.error(chalk_1.default.red(` ${data.error.message}`));
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
spinner.succeed(chalk_1.default.green(`SMS sent to ${match.name} (${match.phone}).`));
|
|
868
|
+
}
|
|
869
|
+
catch (error) {
|
|
870
|
+
fail(spinner, 'Dispatch failed', error);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
571
873
|
const command_kit_1 = require("../lib/command-kit");
|
|
572
874
|
(0, command_kit_1.appendExamples)(exports.voiceCommand, [
|
|
573
875
|
{ cmd: 'solid voice calls list --today', why: "Today's call log" },
|
|
574
876
|
{ cmd: 'solid voice calls get <id>', why: 'Transcript + recording link' },
|
|
877
|
+
{ cmd: 'solid voice diagnose <id>', why: 'Per-call event timeline + verdict' },
|
|
878
|
+
{ cmd: 'solid voice health', why: 'Tenant voice readiness audit' },
|
|
575
879
|
{ cmd: 'solid voice numbers list', why: 'Owned phone numbers' },
|
|
576
880
|
{ cmd: 'solid voice numbers buy --area-code 512', why: 'Buy a new number' },
|
|
577
881
|
{ cmd: 'solid voice voicemail list --unread', why: 'Unlistened voicemails' },
|
|
578
882
|
{ cmd: 'solid voice personality get', why: 'Industry voice profile' },
|
|
883
|
+
{ cmd: 'solid voice call "Jane Smoke" --intent "follow up" --confirm', why: 'Dispatch outbound voice via Sarah' },
|
|
884
|
+
{ cmd: 'solid voice text "Jane Smoke" --message "Confirming 3pm." --confirm', why: 'Outbound SMS through Solid#' },
|
|
579
885
|
]);
|
|
580
886
|
//# sourceMappingURL=voice.js.map
|