@velaro/cli 1.2.0 → 1.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +161 -138
  2. package/bin/velaro.js +177 -62
  3. package/lib/api.js +91 -52
  4. package/lib/api.test.js +46 -0
  5. package/lib/banner.js +76 -0
  6. package/lib/commands/activity.js +133 -0
  7. package/lib/commands/acuity.js +66 -0
  8. package/lib/commands/agent.js +204 -50
  9. package/lib/commands/ai-config.js +193 -0
  10. package/lib/commands/ai-models.js +159 -0
  11. package/lib/commands/appointments.js +198 -0
  12. package/lib/commands/article.js +668 -388
  13. package/lib/commands/automation-draft.js +134 -0
  14. package/lib/commands/avatar.js +75 -0
  15. package/lib/commands/bigcommerce.js +50 -0
  16. package/lib/commands/billing-contacts.js +62 -0
  17. package/lib/commands/billing-email-preference.js +64 -0
  18. package/lib/commands/billing-subscription.js +265 -0
  19. package/lib/commands/billing.js +138 -0
  20. package/lib/commands/bot.js +141 -137
  21. package/lib/commands/bundle.js +168 -0
  22. package/lib/commands/calendly.js +62 -0
  23. package/lib/commands/callback.js +125 -0
  24. package/lib/commands/callrail.js +88 -0
  25. package/lib/commands/campaigns.js +44 -0
  26. package/lib/commands/case.js +102 -0
  27. package/lib/commands/check.js +163 -163
  28. package/lib/commands/compliance.js +229 -0
  29. package/lib/commands/conversation-efficiency.js +178 -0
  30. package/lib/commands/copilotstudio.js +114 -0
  31. package/lib/commands/coupon-grant.js +192 -0
  32. package/lib/commands/db.js +101 -0
  33. package/lib/commands/deployment.js +107 -107
  34. package/lib/commands/diagnostics.js +298 -0
  35. package/lib/commands/email-campaign.js +47 -0
  36. package/lib/commands/email-inbox.js +88 -0
  37. package/lib/commands/entitlement.js +176 -0
  38. package/lib/commands/env.js +45 -45
  39. package/lib/commands/feature-discovery.js +40 -0
  40. package/lib/commands/focus.js +278 -0
  41. package/lib/commands/index.js +38 -5
  42. package/lib/commands/ingest.js +31 -31
  43. package/lib/commands/inline-widget-config.js +126 -0
  44. package/lib/commands/integration.js +93 -0
  45. package/lib/commands/kb.js +450 -309
  46. package/lib/commands/login.js +86 -86
  47. package/lib/commands/logs.js +680 -0
  48. package/lib/commands/magento.js +210 -0
  49. package/lib/commands/mcp-key.js +188 -159
  50. package/lib/commands/migrate.js +134 -0
  51. package/lib/commands/migration-status.js +66 -0
  52. package/lib/commands/monday.js +137 -0
  53. package/lib/commands/netsuite.js +87 -0
  54. package/lib/commands/notifications.js +63 -0
  55. package/lib/commands/notion.js +70 -0
  56. package/lib/commands/ops.js +267 -173
  57. package/lib/commands/payment-recovery.js +170 -0
  58. package/lib/commands/pickup.js +172 -0
  59. package/lib/commands/pricing.js +132 -0
  60. package/lib/commands/product.js +55 -0
  61. package/lib/commands/recruiting.js +374 -0
  62. package/lib/commands/report.js +462 -0
  63. package/lib/commands/routing.js +304 -0
  64. package/lib/commands/rule.js +85 -85
  65. package/lib/commands/sharepoint.js +167 -0
  66. package/lib/commands/site-provision.js +68 -0
  67. package/lib/commands/site.js +62 -62
  68. package/lib/commands/sitesync.js +158 -0
  69. package/lib/commands/slack.js +64 -0
  70. package/lib/commands/squarespace.js +108 -0
  71. package/lib/commands/status.js +24 -24
  72. package/lib/commands/subscription.js +43 -0
  73. package/lib/commands/support.js +128 -0
  74. package/lib/commands/survey.js +216 -0
  75. package/lib/commands/team.js +144 -144
  76. package/lib/commands/teams-phone.js +131 -0
  77. package/lib/commands/teams.js +106 -0
  78. package/lib/commands/telephony.js +99 -0
  79. package/lib/commands/update.js +47 -47
  80. package/lib/commands/webflow.js +128 -0
  81. package/lib/commands/whoami.js +25 -22
  82. package/lib/commands/widget-container.js +152 -0
  83. package/lib/commands/woocommerce.js +240 -0
  84. package/lib/commands/workflow.js +233 -98
  85. package/lib/config.js +85 -83
  86. package/lib/kb-screenshot.js +320 -0
  87. package/lib/migrations/amscro.json +72 -0
  88. package/lib/migrations/azenta.json +68 -0
  89. package/lib/migrations/bluefire.json +49 -0
  90. package/lib/migrations/donaldson.json +75 -0
  91. package/lib/oauth.js +149 -135
  92. package/lib/run.js +21 -16
  93. package/lib/sharepoint-auth.js +138 -0
  94. package/lib/subscription.js +41 -39
  95. package/lib/track.js +35 -35
  96. package/lib/update-check.js +64 -64
  97. package/package.json +34 -19
  98. package/scripts/postinstall.js +12 -0
@@ -0,0 +1,198 @@
1
+ import { messagingGet, messagingPost, messagingPut } from '../api.js';
2
+ import { runCommand } from '../run.js';
3
+
4
+ const STATUS_LABELS = { 0: 'Scheduled', 1: 'Confirmed', 2: 'Completed', 3: 'NoShow', 4: 'Cancelled' };
5
+ function statusLabel(s) { return typeof s === 'number' ? (STATUS_LABELS[s] ?? String(s)) : s; }
6
+ function fmtDate(d) { return d ? new Date(d).toISOString().replace('T', ' ').slice(0, 16) : '-'; }
7
+
8
+ // ── velaro appointments ───────────────────────────────────────────────────────
9
+ // CLI surface for Velaro Appointments (multi-calendar text-to-book scheduling).
10
+ // Mirrors the appointments_* MCP tools. Endpoints live in velaro-messaging at
11
+ // /Appointments/* — they wrap FindOpenSlotsSkill / BookAppointmentSkill /
12
+ // ManageAppointmentSkill and apply the caller's siteId from JWT.
13
+ //
14
+ // Marketing page: /appointments (admin app) and marketing/landing-pages/appointments.html.
15
+
16
+ export const appointmentsCommand = {
17
+ command: 'appointments <subcommand>',
18
+ describe: 'Velaro Appointments — multi-calendar text-to-book scheduling',
19
+ builder: yargs => yargs
20
+ .command({
21
+ command: 'list-slots',
22
+ describe: 'Find open slots across every connected calendar (Outlook / MS Bookings / Calendly)',
23
+ builder: y => y
24
+ .option('from', { type: 'string', demandOption: true, describe: 'ISO date or datetime to check (YYYY-MM-DD)' })
25
+ .option('to', { type: 'string', demandOption: false, describe: 'Optional end date — repeats list-slots for each day in range' })
26
+ .option('duration', { type: 'number', default: 30, describe: 'Slot length in minutes (default 30)' }),
27
+ handler: runCommand(async argv => {
28
+ const from = argv.from;
29
+ const to = argv.to || argv.from;
30
+ const fromDate = new Date(from);
31
+ const toDate = new Date(to);
32
+ if (Number.isNaN(fromDate.getTime()) || Number.isNaN(toDate.getTime())) {
33
+ throw new Error('Invalid --from or --to. Use YYYY-MM-DD.');
34
+ }
35
+ const days = [];
36
+ for (let d = new Date(fromDate); d <= toDate; d.setUTCDate(d.getUTCDate() + 1)) {
37
+ days.push(d.toISOString().slice(0, 10));
38
+ }
39
+ for (const day of days) {
40
+ const data = await messagingPost('/Appointments/FindOpenSlots', {
41
+ date: day,
42
+ durationMinutes: argv.duration,
43
+ });
44
+ if (!data?.success) {
45
+ console.log(`\n${day}: ${data?.message ?? '(no slots)'}`);
46
+ continue;
47
+ }
48
+ console.log(`\n${day} (${data.duration_minutes ?? argv.duration}-min slots) — calendars: ${(data.connected_calendars ?? []).join(', ') || '(none)'}`);
49
+ for (const s of data.available_slots ?? []) console.log(` ${s}`);
50
+ }
51
+ console.log('');
52
+ }),
53
+ })
54
+ .command({
55
+ command: 'book',
56
+ describe: 'Book an appointment on the site\'s connected calendar',
57
+ builder: y => y
58
+ .option('start', { type: 'string', demandOption: true, describe: 'ISO-8601 start time (e.g. 2026-06-01T15:00:00Z)' })
59
+ .option('duration', { type: 'number', default: 30, describe: 'Duration in minutes' })
60
+ .option('summary', { type: 'string', demandOption: true, describe: 'Short title (e.g. "Cleaning - Jane Doe")' })
61
+ .option('email', { type: 'string', demandOption: false, describe: 'Attendee email' })
62
+ .option('name', { type: 'string', demandOption: false, describe: 'Attendee name' })
63
+ .option('location', { type: 'string', demandOption: false, describe: 'Physical or virtual location' })
64
+ .option('reason', { type: 'string', demandOption: false, describe: 'Short category (cleaning, consult, etc.)' }),
65
+ handler: runCommand(async argv => {
66
+ const data = await messagingPost('/Appointments/Book', {
67
+ start: argv.start,
68
+ durationMinutes: argv.duration,
69
+ summary: argv.summary,
70
+ attendeeEmail: argv.email ?? null,
71
+ attendeeName: argv.name ?? null,
72
+ location: argv.location ?? null,
73
+ reason: argv.reason ?? null,
74
+ });
75
+ if (!data?.success) {
76
+ throw new Error(data?.message ?? 'Book failed');
77
+ }
78
+ console.log(`\n✓ Booked via ${data.provider}`);
79
+ console.log(` event_id: ${data.event_id}`);
80
+ console.log(` start: ${data.start}`);
81
+ console.log(` end: ${data.end}`);
82
+ if (data.join_url) console.log(` join: ${data.join_url}`);
83
+ console.log('\n Hand the provider+event_id to "velaro appointments manage" to reschedule or cancel.\n');
84
+ }),
85
+ })
86
+ .command({
87
+ command: 'manage',
88
+ describe: 'Reschedule or cancel an existing appointment',
89
+ builder: y => y
90
+ .option('intent', { type: 'string', demandOption: true, choices: ['reschedule', 'cancel'], describe: 'reschedule or cancel' })
91
+ .option('provider', { type: 'string', demandOption: true, describe: 'Provider slug returned by book (outlook | msbookings | calendly)' })
92
+ .option('event-id', { type: 'string', demandOption: true, describe: 'Event id returned by book' })
93
+ .option('start', { type: 'string', demandOption: false, describe: 'New ISO-8601 start (required when intent=reschedule)' })
94
+ .option('duration', { type: 'number', demandOption: false, describe: 'New duration in minutes' })
95
+ .option('summary', { type: 'string', demandOption: false, describe: 'Optional new title' }),
96
+ handler: runCommand(async argv => {
97
+ if (argv.intent === 'reschedule' && !argv.start) {
98
+ throw new Error('--start is required when --intent=reschedule');
99
+ }
100
+ const data = await messagingPost('/Appointments/Manage', {
101
+ intent: argv.intent,
102
+ provider: argv.provider,
103
+ eventId: argv['event-id'],
104
+ start: argv.start ?? null,
105
+ durationMinutes: argv.duration ?? null,
106
+ summary: argv.summary ?? null,
107
+ });
108
+ if (!data?.success) {
109
+ throw new Error(data?.message ?? `${argv.intent} failed`);
110
+ }
111
+ console.log(`\n✓ ${data.message ?? `${argv.intent} ok`}`);
112
+ if (data.new_start) console.log(` new_start: ${data.new_start}`);
113
+ if (data.new_end) console.log(` new_end: ${data.new_end}`);
114
+ console.log('');
115
+ }),
116
+ })
117
+ .command({
118
+ command: 'install-template',
119
+ describe: 'Install the "Text-to-book appointments" starter workflow for the current site',
120
+ handler: runCommand(async () => {
121
+ const data = await messagingPost('/Workflows/InstallStarterTemplate', {
122
+ templateKey: 'appointments-text-to-book',
123
+ });
124
+ if (data?.workflowId) {
125
+ console.log(`\n✓ Installed "Text-to-book appointments" starter workflow → workflow id ${data.workflowId}`);
126
+ console.log(' Open Admin → Workflows to activate it.\n');
127
+ } else {
128
+ console.log('\n✓ Install request accepted.\n' + JSON.stringify(data, null, 2) + '\n');
129
+ }
130
+ }),
131
+ })
132
+ .command({
133
+ // Distinct from `book` (AI-skill booking via FindOpenSlotsSkill/BookAppointmentSkill) --
134
+ // this is the staff-facing "log a phone-in caller" path, backed by the first-party
135
+ // Appointment entity (server/Velaro.Messaging/Controllers/AppointmentsController.cs POST
136
+ // Appointments/Manual). Reminders (if enabled) are scheduled automatically by the backend.
137
+ command: 'manual-book',
138
+ describe: 'Log a phone-in appointment on behalf of a caller (distinct from `book` -- this is the staff-entry path, not the AI-skill path)',
139
+ builder: y => y
140
+ .option('name', { describe: 'Caller name', type: 'string' })
141
+ .option('phone', { describe: 'Caller phone number -- required', type: 'string', demandOption: true })
142
+ .option('email', { describe: 'Caller email', type: 'string' })
143
+ .option('start', { describe: 'Start time, ISO 8601 UTC -- required', type: 'string', demandOption: true })
144
+ .option('end', { describe: 'End time, ISO 8601 UTC -- required', type: 'string', demandOption: true })
145
+ .option('timezone', { describe: 'IANA timezone id (default America/Los_Angeles)', type: 'string' })
146
+ .option('notes', { describe: 'Free-text notes', type: 'string' })
147
+ .option('write-target', { describe: 'Which connected calendar this is associated with', type: 'string' }),
148
+ handler: runCommand(async (argv) => {
149
+ const result = await messagingPost('/Appointments/Manual', {
150
+ customerName: argv.name,
151
+ customerPhone: argv.phone,
152
+ customerEmail: argv.email,
153
+ startUtc: argv.start,
154
+ endUtc: argv.end,
155
+ timeZone: argv.timezone,
156
+ notes: argv.notes,
157
+ writeTargetSlug: argv.writeTarget,
158
+ });
159
+ console.log(`\n✓ Appointment logged (id ${result.id}) for ${result.customerName || result.customerPhone}.`);
160
+ console.log(` ${fmtDate(result.startUtc)} - ${fmtDate(result.endUtc)} (${result.timeZone})\n`);
161
+ }),
162
+ })
163
+ .command({
164
+ command: 'list-appointments',
165
+ describe: 'List logged appointments (bot-booked and phone-in), optionally filtered by date range',
166
+ builder: y => y
167
+ .option('from', { describe: 'Only include appointments starting at/after this ISO 8601 UTC timestamp', type: 'string' })
168
+ .option('to', { describe: 'Only include appointments starting at/before this ISO 8601 UTC timestamp', type: 'string' }),
169
+ handler: runCommand(async (argv) => {
170
+ const params = new URLSearchParams();
171
+ if (argv.from) params.set('fromUtc', argv.from);
172
+ if (argv.to) params.set('toUtc', argv.to);
173
+ const qs = params.toString();
174
+ const appointments = await messagingGet(`/Appointments${qs ? `?${qs}` : ''}`);
175
+ if (!Array.isArray(appointments) || !appointments.length) {
176
+ console.log('\n No appointments found.\n');
177
+ return;
178
+ }
179
+ console.log('\nAppointments\n');
180
+ for (const a of appointments) {
181
+ console.log(` id:${a.id} ${fmtDate(a.startUtc)} ${a.customerName || a.customerPhone} ${statusLabel(a.status)}`);
182
+ }
183
+ console.log('');
184
+ }),
185
+ })
186
+ .command({
187
+ command: 'set-status <appointmentId> <status>',
188
+ describe: 'Update a logged appointment\'s status (Scheduled | Confirmed | Completed | NoShow | Cancelled)',
189
+ builder: y => y
190
+ .positional('appointmentId', { describe: 'Appointment ID', type: 'number' })
191
+ .positional('status', { describe: 'New status', type: 'string' }),
192
+ handler: runCommand(async (argv) => {
193
+ const result = await messagingPut(`/Appointments/${argv.appointmentId}/status`, { status: argv.status });
194
+ console.log(`\n✓ Appointment ${argv.appointmentId} status set to ${statusLabel(result.status)}.\n`);
195
+ }),
196
+ })
197
+ .demandCommand(1, 'Specify a subcommand: list-slots | book | manage | install-template | manual-book | list-appointments | set-status'),
198
+ };