@velaro/cli 1.2.0 → 1.4.8
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 +161 -138
- package/bin/velaro.js +177 -62
- package/lib/api.js +91 -52
- package/lib/api.test.js +46 -0
- package/lib/banner.js +76 -0
- package/lib/commands/activity.js +133 -0
- package/lib/commands/acuity.js +66 -0
- package/lib/commands/agent.js +204 -50
- package/lib/commands/ai-config.js +193 -0
- package/lib/commands/ai-models.js +159 -0
- package/lib/commands/appointments.js +198 -0
- package/lib/commands/article.js +668 -388
- package/lib/commands/automation-draft.js +134 -0
- package/lib/commands/avatar.js +75 -0
- package/lib/commands/bigcommerce.js +50 -0
- package/lib/commands/billing-contacts.js +62 -0
- package/lib/commands/billing-email-preference.js +64 -0
- package/lib/commands/billing-subscription.js +265 -0
- package/lib/commands/billing.js +138 -0
- package/lib/commands/bot.js +141 -137
- package/lib/commands/bundle.js +168 -0
- package/lib/commands/calendly.js +62 -0
- package/lib/commands/callback.js +125 -0
- package/lib/commands/callrail.js +88 -0
- package/lib/commands/campaigns.js +44 -0
- package/lib/commands/case.js +102 -0
- package/lib/commands/check.js +163 -163
- package/lib/commands/compliance.js +229 -0
- package/lib/commands/conversation-efficiency.js +178 -0
- package/lib/commands/copilotstudio.js +114 -0
- package/lib/commands/coupon-grant.js +192 -0
- package/lib/commands/db.js +101 -0
- package/lib/commands/deployment.js +107 -107
- package/lib/commands/diagnostics.js +298 -0
- package/lib/commands/email-campaign.js +47 -0
- package/lib/commands/email-inbox.js +88 -0
- package/lib/commands/entitlement.js +176 -0
- package/lib/commands/env.js +45 -45
- package/lib/commands/feature-discovery.js +40 -0
- package/lib/commands/focus.js +278 -0
- package/lib/commands/index.js +38 -5
- package/lib/commands/ingest.js +31 -31
- package/lib/commands/inline-widget-config.js +126 -0
- package/lib/commands/integration.js +93 -0
- package/lib/commands/kb.js +450 -309
- package/lib/commands/login.js +86 -86
- package/lib/commands/logs.js +680 -0
- package/lib/commands/magento.js +210 -0
- package/lib/commands/mcp-key.js +188 -159
- package/lib/commands/migrate.js +134 -0
- package/lib/commands/migration-status.js +66 -0
- package/lib/commands/monday.js +137 -0
- package/lib/commands/netsuite.js +87 -0
- package/lib/commands/notifications.js +63 -0
- package/lib/commands/notion.js +70 -0
- package/lib/commands/ops.js +267 -173
- package/lib/commands/payment-recovery.js +170 -0
- package/lib/commands/pickup.js +172 -0
- package/lib/commands/pricing.js +132 -0
- package/lib/commands/product.js +55 -0
- package/lib/commands/recruiting.js +374 -0
- package/lib/commands/report.js +462 -0
- package/lib/commands/routing.js +304 -0
- package/lib/commands/rule.js +85 -85
- package/lib/commands/sharepoint.js +167 -0
- package/lib/commands/site-provision.js +68 -0
- package/lib/commands/site.js +62 -62
- package/lib/commands/sitesync.js +158 -0
- package/lib/commands/slack.js +64 -0
- package/lib/commands/squarespace.js +108 -0
- package/lib/commands/status.js +24 -24
- package/lib/commands/subscription.js +43 -0
- package/lib/commands/support.js +128 -0
- package/lib/commands/survey.js +216 -0
- package/lib/commands/team.js +144 -144
- package/lib/commands/teams-phone.js +131 -0
- package/lib/commands/teams.js +106 -0
- package/lib/commands/telephony.js +99 -0
- package/lib/commands/update.js +47 -47
- package/lib/commands/webflow.js +128 -0
- package/lib/commands/whoami.js +25 -22
- package/lib/commands/widget-container.js +152 -0
- package/lib/commands/woocommerce.js +240 -0
- package/lib/commands/workflow.js +233 -98
- package/lib/config.js +85 -83
- package/lib/kb-screenshot.js +320 -0
- package/lib/migrations/amscro.json +72 -0
- package/lib/migrations/azenta.json +68 -0
- package/lib/migrations/bluefire.json +49 -0
- package/lib/migrations/donaldson.json +75 -0
- package/lib/oauth.js +149 -135
- package/lib/run.js +21 -16
- package/lib/sharepoint-auth.js +138 -0
- package/lib/subscription.js +41 -39
- package/lib/track.js +35 -35
- package/lib/update-check.js +64 -64
- package/package.json +34 -19
- package/scripts/postinstall.js +12 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro teams-phone - Microsoft Teams Phone (SiteVoiceConfig ProviderSlug="teams") config CRUD
|
|
3
|
+
*
|
|
4
|
+
* velaro teams-phone status Show current configuration
|
|
5
|
+
* velaro teams-phone set --tenant-id ... Create or update the configuration
|
|
6
|
+
* velaro teams-phone remove Delete the configuration
|
|
7
|
+
* velaro teams-phone test Test the Graph subscription connectivity
|
|
8
|
+
*
|
|
9
|
+
* Backend contract: server/Velaro.Messaging/Controllers/Voice/TeamsPhoneConfigController.cs
|
|
10
|
+
* (velaro-messaging). Site is resolved server-side from the auth context — never
|
|
11
|
+
* caller-supplied here or on the server.
|
|
12
|
+
*
|
|
13
|
+
* Live in-call actions (hold/resume/end/transfer, TeamsPhoneCtiController) are
|
|
14
|
+
* intentionally NOT exposed here — see mcp-cli-exempt comments on that controller.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { messagingGet, messagingPost, messagingPut, messagingDel } from '../api.js';
|
|
18
|
+
|
|
19
|
+
const GRN = '\x1b[32m';
|
|
20
|
+
const RED = '\x1b[31m';
|
|
21
|
+
const YEL = '\x1b[33m';
|
|
22
|
+
const DIM = '\x1b[2m';
|
|
23
|
+
const BOLD = '\x1b[1m';
|
|
24
|
+
const RST = '\x1b[0m';
|
|
25
|
+
|
|
26
|
+
function printConfig(c) {
|
|
27
|
+
if (!c?.configured) {
|
|
28
|
+
console.log(`\n${DIM}Teams Phone is not configured for this site.${RST}\n`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(`\n${BOLD}Teams Phone configuration${RST}`);
|
|
32
|
+
console.log(` Tenant ID: ${c.tenantId ?? '(none)'}`);
|
|
33
|
+
console.log(` Application instance object ID: ${c.applicationInstanceObjectId ?? '(none)'}`);
|
|
34
|
+
console.log(` Assigned phone number: ${c.assignedPhoneNumber ?? '(none)'}`);
|
|
35
|
+
console.log(` Webhook secret configured: ${c.webhookSecretConfigured ? `${GRN}yes${RST}` : `${DIM}no${RST}`}`);
|
|
36
|
+
const subColor = c.graphSubscriptionActive ? GRN : YEL;
|
|
37
|
+
console.log(` Graph subscription active: ${subColor}${c.graphSubscriptionActive}${RST}` +
|
|
38
|
+
(c.graphSubscriptionExpiresAt ? ` (expires ${c.graphSubscriptionExpiresAt})` : ''));
|
|
39
|
+
console.log('');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// -- status ------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
async function statusHandler() {
|
|
45
|
+
const config = await messagingGet('/TeamsPhone/Config');
|
|
46
|
+
printConfig(config);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// -- set (create or update) ---------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
async function setHandler(argv) {
|
|
52
|
+
const existing = await messagingGet('/TeamsPhone/Config');
|
|
53
|
+
const body = {
|
|
54
|
+
tenantId: argv.tenantId,
|
|
55
|
+
applicationInstanceObjectId: argv.applicationInstanceObjectId,
|
|
56
|
+
assignedPhoneNumber: argv.assignedPhoneNumber,
|
|
57
|
+
webhookSecret: argv.webhookSecret,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (!existing?.configured) {
|
|
61
|
+
if (!body.tenantId) throw new Error('--tenant-id is required to create a new Teams Phone configuration.');
|
|
62
|
+
const created = await messagingPost('/TeamsPhone/Config', body);
|
|
63
|
+
console.log(`\n${GRN}✓${RST} Teams Phone configuration created.`);
|
|
64
|
+
printConfig(created);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const updated = await messagingPut('/TeamsPhone/Config', body);
|
|
69
|
+
console.log(`\n${GRN}✓${RST} Teams Phone configuration updated.`);
|
|
70
|
+
printConfig(updated);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// -- remove ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
async function removeHandler() {
|
|
76
|
+
const result = await messagingDel('/TeamsPhone/Config');
|
|
77
|
+
console.log(`\n ${GRN}✓${RST} ${result?.deleted ? 'Deleted.' : 'Delete request completed.'}\n`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// -- test -------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
async function testHandler() {
|
|
83
|
+
const result = await messagingPost('/TeamsPhone/Config/Test', {});
|
|
84
|
+
const color = result?.success ? GRN : RED;
|
|
85
|
+
console.log(`\n${color}${result?.success ? 'Success' : 'Failed'}${RST}: ${result?.message ?? '(no message)'}\n`);
|
|
86
|
+
if (!result?.success) process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -- command export ------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
export const teamsPhoneCommand = {
|
|
92
|
+
command: 'teams-phone <subcommand>',
|
|
93
|
+
describe: 'Microsoft Teams Phone integration configuration (config CRUD only — live call control is UI-only)',
|
|
94
|
+
builder: y => y
|
|
95
|
+
.command({
|
|
96
|
+
command: 'status',
|
|
97
|
+
describe: 'Show the Teams Phone configuration for this site',
|
|
98
|
+
builder: yy => yy,
|
|
99
|
+
handler: statusHandler,
|
|
100
|
+
})
|
|
101
|
+
.command({
|
|
102
|
+
command: 'set',
|
|
103
|
+
describe: 'Create or update the Teams Phone configuration (apply-if-present, partial update on existing config)',
|
|
104
|
+
builder: yy => yy
|
|
105
|
+
.option('tenant-id', { describe: 'Customer Microsoft 365 tenant ID (required on create)', type: 'string' })
|
|
106
|
+
.option('application-instance-object-id', { describe: 'Teams resource account (application instance) object ID. Pass "" to clear.', type: 'string' })
|
|
107
|
+
.option('assigned-phone-number', { describe: 'Phone number assigned to the resource account. Pass "" to clear.', type: 'string' })
|
|
108
|
+
.option('webhook-secret', { describe: 'Webhook secret. Omit to auto-generate on create or leave unchanged on update.', type: 'string' }),
|
|
109
|
+
handler: argv => setHandler({
|
|
110
|
+
tenantId: argv.tenantId,
|
|
111
|
+
applicationInstanceObjectId: argv.applicationInstanceObjectId,
|
|
112
|
+
assignedPhoneNumber: argv.assignedPhoneNumber,
|
|
113
|
+
webhookSecret: argv.webhookSecret,
|
|
114
|
+
}),
|
|
115
|
+
})
|
|
116
|
+
.command({
|
|
117
|
+
command: 'remove',
|
|
118
|
+
describe: 'Delete the Teams Phone configuration for this site',
|
|
119
|
+
builder: yy => yy,
|
|
120
|
+
handler: removeHandler,
|
|
121
|
+
})
|
|
122
|
+
.command({
|
|
123
|
+
command: 'test',
|
|
124
|
+
describe: 'Test the Teams Phone connection by creating/renewing the Graph /communications/calls subscription',
|
|
125
|
+
builder: yy => yy,
|
|
126
|
+
handler: testHandler,
|
|
127
|
+
})
|
|
128
|
+
.demandCommand(1, 'Specify a teams-phone subcommand.')
|
|
129
|
+
.strict(),
|
|
130
|
+
handler: () => {},
|
|
131
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { request } from '../api.js';
|
|
2
|
+
|
|
3
|
+
export const teamsCommand = {
|
|
4
|
+
command: 'teams <subcommand>',
|
|
5
|
+
describe: 'Send messages to Microsoft Teams channels',
|
|
6
|
+
builder: (yargs) =>
|
|
7
|
+
yargs
|
|
8
|
+
.command(
|
|
9
|
+
'notify [message]',
|
|
10
|
+
'Send a message to a Teams channel webhook',
|
|
11
|
+
(y) =>
|
|
12
|
+
y
|
|
13
|
+
.positional('message', { type: 'string', describe: 'Message to send' })
|
|
14
|
+
.option('title', { type: 'string', describe: 'Card title', alias: 't' })
|
|
15
|
+
.option('id', { type: 'number', describe: 'Webhook config ID (omit to list)', alias: 'i' }),
|
|
16
|
+
async (argv) => {
|
|
17
|
+
const webhooks = await request('GET', '/TeamsNotification');
|
|
18
|
+
|
|
19
|
+
if (!Array.isArray(webhooks) || !webhooks.length) {
|
|
20
|
+
console.log('No Teams webhooks configured. Go to Integrations → Microsoft Teams to add one.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!argv.id) {
|
|
25
|
+
console.log('Available Teams webhooks:');
|
|
26
|
+
for (const w of webhooks) {
|
|
27
|
+
console.log(` [${w.id}] ${w.name}${w.isEnabled ? '' : ' (disabled)'}`);
|
|
28
|
+
}
|
|
29
|
+
console.log('\nRun with --id <number> to send a message.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const webhook = webhooks.find((w) => w.id === argv.id);
|
|
34
|
+
if (!webhook) { console.error(`No webhook with id ${argv.id}`); process.exit(1); }
|
|
35
|
+
if (!webhook.isEnabled) { console.error(`Webhook "${webhook.name}" is disabled.`); process.exit(1); }
|
|
36
|
+
|
|
37
|
+
const message = argv.message;
|
|
38
|
+
if (!message) { console.error('Message is required. Pass it as an argument: velaro teams notify "hello"'); process.exit(1); }
|
|
39
|
+
|
|
40
|
+
await request('POST', `/TeamsNotification/Send/${argv.id}`, {
|
|
41
|
+
message,
|
|
42
|
+
title: argv.title || null,
|
|
43
|
+
});
|
|
44
|
+
console.log(`✓ Sent to Teams webhook "${webhook.name}"`);
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
.command(
|
|
48
|
+
'list',
|
|
49
|
+
'List configured Teams webhooks',
|
|
50
|
+
() => {},
|
|
51
|
+
async () => {
|
|
52
|
+
const webhooks = await request('GET', '/TeamsNotification');
|
|
53
|
+
if (!Array.isArray(webhooks) || !webhooks.length) {
|
|
54
|
+
console.log('No Teams webhooks configured.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const w of webhooks) {
|
|
58
|
+
console.log(`[${w.id}] ${w.name} — ${w.isEnabled ? 'enabled' : 'disabled'}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
.command(
|
|
63
|
+
'create <name> <webhookUrl>',
|
|
64
|
+
'Register a Teams incoming webhook (validated live before saving — see Integrations → Microsoft Teams for how to generate the URL in Teams)',
|
|
65
|
+
(y) =>
|
|
66
|
+
y
|
|
67
|
+
.positional('name', { type: 'string', describe: 'Label for this webhook, e.g. "ops-alerts"' })
|
|
68
|
+
.positional('webhookUrl', { type: 'string', describe: 'https://...logic.azure.com/... Workflows webhook URL' }),
|
|
69
|
+
async (argv) => {
|
|
70
|
+
const result = await request('POST', '/TeamsNotification', {
|
|
71
|
+
name: argv.name,
|
|
72
|
+
webhookUrl: argv.webhookUrl,
|
|
73
|
+
isEnabled: true,
|
|
74
|
+
});
|
|
75
|
+
console.log(`✓ Registered webhook "${result.name}" (id ${result.id}). Use: velaro teams notify-card --id ${result.id} ...`);
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
.command(
|
|
79
|
+
'notify-card',
|
|
80
|
+
'Send a rich Adaptive Card (title + fact fields + optional button) to a Teams webhook — for CI/QA/ops summaries',
|
|
81
|
+
(y) =>
|
|
82
|
+
y
|
|
83
|
+
.option('id', { type: 'number', describe: 'Webhook config ID (see: velaro teams list)', alias: 'i', demandOption: true })
|
|
84
|
+
.option('title', { type: 'string', describe: 'Card title', alias: 't', demandOption: true })
|
|
85
|
+
.option('fact', { type: 'array', describe: 'Repeatable "Label=Value" fact, e.g. --fact "Fixed=3" --fact "Commits=abc123"' })
|
|
86
|
+
.option('button-label', { type: 'string', describe: 'Optional action button label' })
|
|
87
|
+
.option('button-url', { type: 'string', describe: 'Optional action button URL (required if --button-label is set)' }),
|
|
88
|
+
async (argv) => {
|
|
89
|
+
const fields = (argv.fact || []).map((f) => {
|
|
90
|
+
const eq = f.indexOf('=');
|
|
91
|
+
if (eq < 0) { console.error(`--fact must be "Label=Value", got: ${f}`); process.exit(1); }
|
|
92
|
+
return { label: f.slice(0, eq), value: f.slice(eq + 1) };
|
|
93
|
+
});
|
|
94
|
+
const result = await request('POST', '/TeamsNotification/SendWorkflow', {
|
|
95
|
+
webhookId: argv.id,
|
|
96
|
+
title: argv.title,
|
|
97
|
+
fields,
|
|
98
|
+
...(argv.buttonLabel ? { buttonLabel: argv.buttonLabel, buttonUrl: argv.buttonUrl } : {}),
|
|
99
|
+
});
|
|
100
|
+
if (result?.success) console.log('✓ Card sent to Teams.');
|
|
101
|
+
else { console.error(`Send failed: ${result?.message || 'unknown error'}`); process.exit(1); }
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
.demandCommand(1, 'Specify a subcommand: notify, notify-card, create, list'),
|
|
105
|
+
handler: () => {},
|
|
106
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro telephony - Velaro Voice (white-label phone) provisioning
|
|
3
|
+
*
|
|
4
|
+
* velaro telephony status Show provisioning status for the site
|
|
5
|
+
* velaro telephony provision Provision (or re-confirm) a phone account
|
|
6
|
+
* velaro telephony deprovision Tear down the site's phone account
|
|
7
|
+
*
|
|
8
|
+
* Backend contract: server/Velaro.Messaging/Controllers/Voice/TelephonyProvisioningController.cs
|
|
9
|
+
* (velaro-messaging). Site is resolved server-side from the auth context — never
|
|
10
|
+
* caller-supplied here or on the server.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { messagingGet, messagingPost, messagingDel } from '../api.js';
|
|
14
|
+
|
|
15
|
+
const GRN = '\x1b[32m';
|
|
16
|
+
const RED = '\x1b[31m';
|
|
17
|
+
const YEL = '\x1b[33m';
|
|
18
|
+
const DIM = '\x1b[2m';
|
|
19
|
+
const BOLD = '\x1b[1m';
|
|
20
|
+
const RST = '\x1b[0m';
|
|
21
|
+
|
|
22
|
+
function printStatus(record) {
|
|
23
|
+
const provisioned = !!record?.provisioned;
|
|
24
|
+
const statusColor = provisioned ? GRN : record?.status === 'Failed' ? RED : YEL;
|
|
25
|
+
console.log(`\n${BOLD}Velaro Voice provisioning${RST}`);
|
|
26
|
+
console.log(` Provisioned: ${provisioned ? `${GRN}yes${RST}` : `${DIM}no${RST}`}`);
|
|
27
|
+
console.log(` Status: ${statusColor}${record?.status ?? '?'}${RST}`);
|
|
28
|
+
if (record?.providerSlug) console.log(` Provider: ${record.providerSlug}`);
|
|
29
|
+
if (record?.externalAccountId) console.log(` Account ID: ${record.externalAccountId}`);
|
|
30
|
+
if (record?.externalExtensionId) console.log(` Extension ID: ${record.externalExtensionId}`);
|
|
31
|
+
if (record?.provisionedNumber) console.log(` Number: ${record.provisionedNumber}`);
|
|
32
|
+
if (record?.errorMessage) console.log(` ${RED}Error: ${record.errorMessage}${RST}`);
|
|
33
|
+
console.log('');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// -- status ------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
async function statusHandler() {
|
|
39
|
+
const record = await messagingGet('/TelephonyProvisioning');
|
|
40
|
+
printStatus(record);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// -- provision ----------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
async function provisionHandler(argv) {
|
|
46
|
+
const body = {
|
|
47
|
+
siteName: argv.siteName,
|
|
48
|
+
contactEmail: argv.contactEmail,
|
|
49
|
+
areaCodePreference: argv.areaCode,
|
|
50
|
+
extensionNumber: argv.extension,
|
|
51
|
+
};
|
|
52
|
+
const record = await messagingPost('/TelephonyProvisioning', body);
|
|
53
|
+
printStatus(record);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// -- deprovision ----------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
async function deprovisionHandler() {
|
|
59
|
+
const record = await messagingDel('/TelephonyProvisioning');
|
|
60
|
+
console.log(`\n ${GRN}✓${RST} Deprovisioned. Status: ${record?.status ?? 'Deprovisioned'}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// -- command export ------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export const telephonyCommand = {
|
|
66
|
+
command: 'telephony <subcommand>',
|
|
67
|
+
describe: 'Velaro Voice (white-label phone) provisioning',
|
|
68
|
+
builder: y => y
|
|
69
|
+
.command({
|
|
70
|
+
command: 'status',
|
|
71
|
+
describe: 'Show Velaro Voice provisioning status for the site',
|
|
72
|
+
builder: yy => yy,
|
|
73
|
+
handler: statusHandler,
|
|
74
|
+
})
|
|
75
|
+
.command({
|
|
76
|
+
command: 'provision',
|
|
77
|
+
describe: 'Provision (or idempotently re-confirm) a Velaro Voice phone account for the site',
|
|
78
|
+
builder: yy => yy
|
|
79
|
+
.option('site-name', { describe: 'Display name for the account (default: "site-{id}")', type: 'string' })
|
|
80
|
+
.option('contact-email', { describe: 'Contact email for the carrier account', type: 'string' })
|
|
81
|
+
.option('area-code', { describe: 'Preferred area code, e.g. "805"', type: 'string' })
|
|
82
|
+
.option('extension', { describe: 'Extension number to provision alongside the account', type: 'number' }),
|
|
83
|
+
handler: argv => provisionHandler({
|
|
84
|
+
siteName: argv.siteName,
|
|
85
|
+
contactEmail: argv.contactEmail,
|
|
86
|
+
areaCode: argv.areaCode,
|
|
87
|
+
extension: argv.extension,
|
|
88
|
+
}),
|
|
89
|
+
})
|
|
90
|
+
.command({
|
|
91
|
+
command: 'deprovision',
|
|
92
|
+
describe: 'Deprovision the site\'s Velaro Voice phone account and release its number',
|
|
93
|
+
builder: yy => yy,
|
|
94
|
+
handler: deprovisionHandler,
|
|
95
|
+
})
|
|
96
|
+
.demandCommand(1, 'Specify a telephony subcommand.')
|
|
97
|
+
.strict(),
|
|
98
|
+
handler: () => {},
|
|
99
|
+
};
|
package/lib/commands/update.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
const { version: currentVersion } = require('../../package.json');
|
|
6
|
-
|
|
7
|
-
const NPM_REGISTRY = 'https://registry.npmjs.org/@velaro/cli/latest';
|
|
8
|
-
|
|
9
|
-
export const updateCommand = {
|
|
10
|
-
command: 'update',
|
|
11
|
-
describe: 'Update the Velaro CLI to the latest version',
|
|
12
|
-
handler: async () => {
|
|
13
|
-
process.stdout.write('Checking for updates... ');
|
|
14
|
-
|
|
15
|
-
let latest;
|
|
16
|
-
try {
|
|
17
|
-
const res = await fetch(NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
|
|
18
|
-
const data = await res.json();
|
|
19
|
-
latest = data?.version;
|
|
20
|
-
} catch {
|
|
21
|
-
console.error('Could not reach npm registry. Check your connection and try again.');
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (!latest) {
|
|
26
|
-
console.error('Could not determine the latest version.');
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (latest === currentVersion) {
|
|
31
|
-
console.log(`already up to date (${currentVersion}).`);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
console.log(`${currentVersion} → ${latest}`);
|
|
36
|
-
console.log('Running: npm install -g @velaro/cli@latest\n');
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
execSync('npm install -g @velaro/cli@latest', { stdio: 'inherit' });
|
|
40
|
-
console.log('\nUpdated successfully. Run velaro --version to confirm.');
|
|
41
|
-
} catch {
|
|
42
|
-
console.error('\nUpdate failed. Try running manually:');
|
|
43
|
-
console.error(' npm install -g @velaro/cli@latest');
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
};
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { version: currentVersion } = require('../../package.json');
|
|
6
|
+
|
|
7
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org/@velaro/cli/latest';
|
|
8
|
+
|
|
9
|
+
export const updateCommand = {
|
|
10
|
+
command: 'update',
|
|
11
|
+
describe: 'Update the Velaro CLI to the latest version',
|
|
12
|
+
handler: async () => {
|
|
13
|
+
process.stdout.write('Checking for updates... ');
|
|
14
|
+
|
|
15
|
+
let latest;
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
latest = data?.version;
|
|
20
|
+
} catch {
|
|
21
|
+
console.error('Could not reach npm registry. Check your connection and try again.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!latest) {
|
|
26
|
+
console.error('Could not determine the latest version.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (latest === currentVersion) {
|
|
31
|
+
console.log(`already up to date (${currentVersion}).`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`${currentVersion} → ${latest}`);
|
|
36
|
+
console.log('Running: npm install -g @velaro/cli@latest\n');
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
execSync('npm install -g @velaro/cli@latest', { stdio: 'inherit' });
|
|
40
|
+
console.log('\nUpdated successfully. Run velaro --version to confirm.');
|
|
41
|
+
} catch {
|
|
42
|
+
console.error('\nUpdate failed. Try running manually:');
|
|
43
|
+
console.error(' npm install -g @velaro/cli@latest');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro webflow - Webflow config CRUD (WebflowController.cs, this repo/admin API)
|
|
3
|
+
*
|
|
4
|
+
* velaro webflow status List connected Webflow sites for this account
|
|
5
|
+
* velaro webflow set <id> --enabled Update an existing connection (partial update)
|
|
6
|
+
* velaro webflow remove <id> Disconnect a Webflow site
|
|
7
|
+
* velaro webflow test <id> Test connectivity (live Webflow /v2/sites call)
|
|
8
|
+
*
|
|
9
|
+
* Backend contract: server/Velaro.Admin/Controllers/WebflowController.cs (this repo). Site is
|
|
10
|
+
* resolved server-side from GetSiteIdAsync() — never caller-supplied here or on the server.
|
|
11
|
+
*
|
|
12
|
+
* There is no "create" subcommand — a new Webflow connection is only created via the OAuth
|
|
13
|
+
* Connect flow in the admin UI (Webflow needs a real browser redirect to Webflow's own consent
|
|
14
|
+
* screen); this CLI only manages connections that already exist.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { get, post, put, del } from '../api.js';
|
|
18
|
+
|
|
19
|
+
const GRN = '\x1b[32m';
|
|
20
|
+
const RED = '\x1b[31m';
|
|
21
|
+
const DIM = '\x1b[2m';
|
|
22
|
+
const BOLD = '\x1b[1m';
|
|
23
|
+
const RST = '\x1b[0m';
|
|
24
|
+
|
|
25
|
+
function printConfig(c) {
|
|
26
|
+
console.log(`\n${BOLD}[${c.id}] ${c.webflowSiteName || c.displayName}${RST}`);
|
|
27
|
+
console.log(` Webflow site ID: ${c.webflowSiteId ?? '(none)'}`);
|
|
28
|
+
console.log(` Webflow workspace ID: ${c.webflowWorkspaceId ?? '(none)'}`);
|
|
29
|
+
console.log(` Widget injected: ${c.scriptRegistered ? `${GRN}yes${RST}` : `${DIM}no${RST}`}`);
|
|
30
|
+
console.log(` Push form submissions: ${c.pushFormSubmissions ? 'yes' : 'no'}`);
|
|
31
|
+
console.log(` Enabled: ${c.enabled ? `${GRN}yes${RST}` : `${DIM}no${RST}`}`);
|
|
32
|
+
console.log(` Defer widget load: ${c.deferWidgetLoadSeconds ? `${c.deferWidgetLoadSeconds}s` : `${DIM}off (immediate)${RST}`}`);
|
|
33
|
+
console.log(` Require cookie consent: ${c.requireCookieConsent ? 'yes' : 'no'}`);
|
|
34
|
+
console.log('');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// -- status ------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
async function statusHandler() {
|
|
40
|
+
const configs = await get('/Webflow/Configs');
|
|
41
|
+
if (!Array.isArray(configs) || configs.length === 0) {
|
|
42
|
+
console.log(`\n${DIM}No Webflow sites connected for this account.${RST}\n`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
configs.forEach(printConfig);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// -- set (update only) ---------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
async function setHandler(argv) {
|
|
51
|
+
if (!argv.id) throw new Error('Provide the Webflow config ID: velaro webflow set <id> [options]');
|
|
52
|
+
const body = {};
|
|
53
|
+
if (argv.displayName !== undefined) body.displayName = argv.displayName;
|
|
54
|
+
if (argv.enabled !== undefined) body.enabled = argv.enabled;
|
|
55
|
+
if (argv.pushFormSubmissions !== undefined) body.pushFormSubmissions = argv.pushFormSubmissions;
|
|
56
|
+
if (argv.deferWidgetLoadSeconds !== undefined) body.deferWidgetLoadSeconds = argv.deferWidgetLoadSeconds;
|
|
57
|
+
if (argv.requireCookieConsent !== undefined) body.requireCookieConsent = argv.requireCookieConsent;
|
|
58
|
+
|
|
59
|
+
const updated = await put(`/Webflow/Configs/${argv.id}`, body);
|
|
60
|
+
console.log(`\n${GRN}✓${RST} Webflow configuration updated.`);
|
|
61
|
+
printConfig(updated);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -- remove ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
async function removeHandler(argv) {
|
|
67
|
+
if (!argv.id) throw new Error('Provide the Webflow config ID: velaro webflow remove <id>');
|
|
68
|
+
await del(`/Webflow/Configs/${argv.id}`);
|
|
69
|
+
console.log(`\n ${GRN}✓${RST} Disconnected. Note: this does not remove the widget already injected on the live Webflow site — use "inject-widget"/"remove-widget" endpoints or the admin UI for that.\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// -- test -------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
async function testHandler(argv) {
|
|
75
|
+
if (!argv.id) throw new Error('Provide the Webflow config ID: velaro webflow test <id>');
|
|
76
|
+
const result = await get(`/Webflow/Configs/${argv.id}/TestConnection`);
|
|
77
|
+
const color = result?.success ? GRN : RED;
|
|
78
|
+
console.log(`\n${color}${result?.success ? 'Success' : 'Failed'}${RST}: ${result?.message ?? '(no message)'}\n`);
|
|
79
|
+
if (!result?.success) process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// -- command export ------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export const webflowCommand = {
|
|
85
|
+
command: 'webflow <subcommand>',
|
|
86
|
+
describe: 'Webflow integration configuration (config CRUD + connection test — new connections are OAuth/browser-only via the admin UI)',
|
|
87
|
+
builder: y => y
|
|
88
|
+
.command({
|
|
89
|
+
command: 'status',
|
|
90
|
+
describe: 'List all Webflow site connections for this account',
|
|
91
|
+
builder: yy => yy,
|
|
92
|
+
handler: statusHandler,
|
|
93
|
+
})
|
|
94
|
+
.command({
|
|
95
|
+
command: 'set <id>',
|
|
96
|
+
describe: 'Update an existing Webflow connection (apply-if-present, partial update)',
|
|
97
|
+
builder: yy => yy
|
|
98
|
+
.positional('id', { describe: 'Webflow config ID', type: 'number' })
|
|
99
|
+
.option('display-name', { describe: 'Display name for this connection', type: 'string' })
|
|
100
|
+
.option('enabled', { describe: 'Enable/disable this connection', type: 'boolean' })
|
|
101
|
+
.option('push-form-submissions', { describe: 'Push Webflow form submissions into Velaro', type: 'boolean' })
|
|
102
|
+
.option('defer-widget-load-seconds', { describe: 'Delay widget init by N seconds or until first interaction, whichever first. 0 = load immediately (default).', type: 'number' })
|
|
103
|
+
.option('require-cookie-consent', { describe: 'Wait for a window "consent-granted" event before initializing the widget', type: 'boolean' }),
|
|
104
|
+
handler: argv => setHandler({
|
|
105
|
+
id: argv.id,
|
|
106
|
+
displayName: argv.displayName,
|
|
107
|
+
enabled: argv.enabled,
|
|
108
|
+
pushFormSubmissions: argv.pushFormSubmissions,
|
|
109
|
+
deferWidgetLoadSeconds: argv.deferWidgetLoadSeconds,
|
|
110
|
+
requireCookieConsent: argv.requireCookieConsent,
|
|
111
|
+
}),
|
|
112
|
+
})
|
|
113
|
+
.command({
|
|
114
|
+
command: 'remove <id>',
|
|
115
|
+
describe: 'Disconnect a Webflow site (does not remove the already-injected widget)',
|
|
116
|
+
builder: yy => yy.positional('id', { describe: 'Webflow config ID', type: 'number' }),
|
|
117
|
+
handler: removeHandler,
|
|
118
|
+
})
|
|
119
|
+
.command({
|
|
120
|
+
command: 'test <id>',
|
|
121
|
+
describe: 'Test the Webflow connection with a live /v2/sites call',
|
|
122
|
+
builder: yy => yy.positional('id', { describe: 'Webflow config ID', type: 'number' }),
|
|
123
|
+
handler: testHandler,
|
|
124
|
+
})
|
|
125
|
+
.demandCommand(1, 'Specify a webflow subcommand.')
|
|
126
|
+
.strict(),
|
|
127
|
+
handler: () => {},
|
|
128
|
+
};
|
package/lib/commands/whoami.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { readConfig } from '../config.js';
|
|
2
|
-
|
|
3
|
-
export const whoamiCommand = {
|
|
4
|
-
command: 'whoami',
|
|
5
|
-
describe: 'Show current login and site info',
|
|
6
|
-
handler: () => {
|
|
7
|
-
const cfg
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
console.log(`
|
|
20
|
-
console.log(`
|
|
21
|
-
|
|
22
|
-
};
|
|
1
|
+
import { readConfig } from '../config.js';
|
|
2
|
+
|
|
3
|
+
export const whoamiCommand = {
|
|
4
|
+
command: 'whoami',
|
|
5
|
+
describe: 'Show current login and site info',
|
|
6
|
+
handler: () => {
|
|
7
|
+
const cfg = readConfig();
|
|
8
|
+
const env = cfg.activeEnv || 'prod';
|
|
9
|
+
const creds = cfg.envs?.[env];
|
|
10
|
+
|
|
11
|
+
if (!creds?.velaroToken) {
|
|
12
|
+
console.error(`Not logged in to ${env}. Run: velaro login${env === 'staging' ? ' --staging' : ''}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const expires = new Date(creds.velaroExpires);
|
|
17
|
+
const minLeft = Math.round((expires - Date.now()) / 60000);
|
|
18
|
+
|
|
19
|
+
console.log(`Env: ${env}`);
|
|
20
|
+
console.log(`User: ${creds.userName ?? '(unknown)'}`);
|
|
21
|
+
console.log(`Site ID: ${creds.siteId ?? '(unknown)'}`);
|
|
22
|
+
console.log(`API: ${creds.adminApiBase}`);
|
|
23
|
+
console.log(`Token: ${expires > new Date() ? `valid (expires in ${minLeft}m)` : 'EXPIRED (refresh token will be used on next command)'}`);
|
|
24
|
+
},
|
|
25
|
+
};
|