@velaro/cli 0.7.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.
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,374 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { messagingGet, messagingPost, messagingPut, messagingDel, getCredentials } from '../api.js';
3
+ import { runCommand } from '../run.js';
4
+
5
+ // -- velaro recruiting --------------------------------------------------------
6
+ // CLI surface for the Recruiting Bot: job-feed sync (JSON/XML/RSS/Indeed/
7
+ // SuccessFactors/HTML), job listing management, and site config. siteId is
8
+ // applied server-side from the vmsg-siteId header (see cli/lib/api.js
9
+ // messagingRequest), never a CLI arg, matching every other messaging
10
+ // command in this file.
11
+ //
12
+ // Backend contract: server/Velaro.Messaging/Controllers/RecruitingController.cs
13
+ // GET Recruiting/Config -> config
14
+ // PUT Recruiting/Config { jobsFeedUrl, feedType?, recruiterEmail?, companyName?, companyDescription?, isEnabled } -> config
15
+ // POST Recruiting/Sync -> { success, error, added, updated, total }
16
+ // GET Recruiting/Jobs?search=&location=&department= -> jobListing[]
17
+ // DELETE Recruiting/Jobs/{id} -> { success }
18
+ // GET Recruiting/Applications -> candidateApplication[]
19
+ // GET Recruiting/Applications/ExportEeoc -> text/csv (Professional tier+, EnableRecruitingBotEeoc)
20
+
21
+ const BOLD = '\x1b[1m';
22
+ const DIM = '\x1b[2m';
23
+ const GRN = '\x1b[32m';
24
+ const RED = '\x1b[31m';
25
+ const RST = '\x1b[0m';
26
+
27
+ async function syncHandler() {
28
+ const result = await messagingPost('/Recruiting/Sync', {});
29
+ if (!result.success) {
30
+ console.log(`\n${RED}FAILED${RST} Recruiting Bot sync failed: ${result.error || 'unknown error'}\n`);
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+ console.log(`\n${GRN}OK${RST} Recruiting Bot job feed synced.`);
35
+ console.log(` Added: ${result.added}`);
36
+ console.log(` Updated: ${result.updated}`);
37
+ console.log(` Total: ${result.total}\n`);
38
+ }
39
+
40
+ async function configGetHandler() {
41
+ const config = await messagingGet('/Recruiting/Config');
42
+ console.log(`\n${BOLD}Recruiting Bot config${RST} (id ${config.id})`);
43
+ console.log(` Enabled: ${config.isEnabled ? `${GRN}yes${RST}` : 'no'}`);
44
+ console.log(` Feed URL: ${config.jobsFeedUrl || '(not set)'}`);
45
+ console.log(` Feed type: ${config.feedType || 'auto'}`);
46
+ console.log(` Recruiter: ${config.recruiterEmail || '(not set)'}`);
47
+ console.log(` Company: ${config.companyName || '(not set)'}`);
48
+ console.log('');
49
+ }
50
+
51
+ async function configSetHandler(argv) {
52
+ const existing = await messagingGet('/Recruiting/Config');
53
+ const body = {
54
+ jobsFeedUrl: argv.feedUrl ?? existing.jobsFeedUrl,
55
+ feedType: argv.feedType ?? existing.feedType,
56
+ recruiterEmail: argv.recruiterEmail ?? existing.recruiterEmail,
57
+ companyName: argv.companyName ?? existing.companyName,
58
+ companyDescription: argv.companyDescription ?? existing.companyDescription,
59
+ isEnabled: argv.enabled ?? existing.isEnabled,
60
+ };
61
+ const config = await messagingPut('/Recruiting/Config', body);
62
+ console.log(`\n${GRN}OK${RST} Recruiting Bot config updated (id ${config.id}).\n`);
63
+ }
64
+
65
+ async function jobsListHandler(argv) {
66
+ const jobs = await messagingGet(`/Recruiting/Jobs${buildQuery(argv)}`);
67
+ if (!Array.isArray(jobs) || !jobs.length) {
68
+ console.log('\n No job listings found.\n');
69
+ return;
70
+ }
71
+ console.log(`\n${BOLD}Recruiting Bot job listings${RST} (${jobs.length})\n`);
72
+ for (const j of jobs) {
73
+ console.log(` ${DIM}#${j.id}${RST} ${j.title} ${DIM}${j.department || '-'} / ${j.location || '-'}${RST}`);
74
+ }
75
+ console.log('');
76
+ }
77
+
78
+ function buildQuery(argv) {
79
+ const params = new URLSearchParams();
80
+ if (argv.search) params.set('search', argv.search);
81
+ if (argv.location) params.set('location', argv.location);
82
+ if (argv.department) params.set('department', argv.department);
83
+ const qs = params.toString();
84
+ return qs ? `?${qs}` : '';
85
+ }
86
+
87
+ async function jobsDeleteHandler(argv) {
88
+ await messagingDel(`/Recruiting/Jobs/${argv.jobId}`);
89
+ console.log(`\n${GRN}OK${RST} Job listing ${argv.jobId} deleted.\n`);
90
+ }
91
+
92
+ async function applicationsListHandler() {
93
+ const apps = await messagingGet('/Recruiting/Applications');
94
+ if (!Array.isArray(apps) || !apps.length) {
95
+ console.log('\n No candidate applications found.\n');
96
+ return;
97
+ }
98
+ console.log(`\n${BOLD}Recruiting Bot applications${RST} (${apps.length})\n`);
99
+ for (const a of apps) {
100
+ console.log(` ${DIM}#${a.id}${RST} ${a.candidateName} ${DIM}${a.roleInterest || '-'} ${a.submittedAt}${RST}`);
101
+ }
102
+ console.log('');
103
+ }
104
+
105
+ // Recruiting/Applications/ExportEeoc returns text/csv, not JSON -- messagingGet() always
106
+ // JSON.parse()s the body, so this fetches the raw response directly using the same
107
+ // messagingApiBase + vmsg-siteId header convention instead of going through messagingGet.
108
+ async function applicationsExportEeocHandler(argv) {
109
+ const creds = await getCredentials();
110
+ const res = await fetch(`${creds.messagingApiBase.replace(/\/+$/, '')}/Recruiting/Applications/ExportEeoc`, {
111
+ headers: {
112
+ Authorization: `Bearer ${creds.velaroToken}`,
113
+ ...(creds.siteId ? { 'vmsg-siteId': String(creds.siteId) } : {}),
114
+ },
115
+ });
116
+ if (!res.ok) {
117
+ const text = await res.text().catch(() => '');
118
+ throw new Error(`GET /Recruiting/Applications/ExportEeoc -> ${res.status}: ${text.slice(0, 300)}`);
119
+ }
120
+ const csv = await res.text();
121
+ const outPath = argv.out || `eeoc-export-${Date.now()}.csv`;
122
+ writeFileSync(outPath, csv);
123
+ console.log(`\n${GRN}OK${RST} EEOC export saved to ${outPath}\n`);
124
+ }
125
+
126
+ const syncCommand = {
127
+ command: 'sync',
128
+ describe: 'Manually trigger a Recruiting Bot job-feed sync for this site',
129
+ handler: runCommand(syncHandler),
130
+ };
131
+
132
+ const configCommand = {
133
+ command: 'config <subcommand>',
134
+ describe: 'Get or set this site\'s Recruiting Bot config',
135
+ builder: (y) => y
136
+ .command({
137
+ command: 'get',
138
+ describe: 'Show this site\'s Recruiting Bot config',
139
+ handler: runCommand(configGetHandler),
140
+ })
141
+ .command({
142
+ command: 'set',
143
+ describe: 'Update this site\'s Recruiting Bot config (apply-if-present, omit a flag to leave it unchanged)',
144
+ builder: (y2) => y2
145
+ .option('feed-url', { describe: 'URL of the jobs feed to sync from (JSON/XML/RSS/SuccessFactors/HTML)', type: 'string' })
146
+ .option('feed-type', { describe: 'Feed type hint: auto | json | xml | successfactors | workday | html', type: 'string' })
147
+ .option('recruiter-email', { describe: 'Email to notify when a candidate submits interest via the bot', type: 'string' })
148
+ .option('company-name', { describe: 'Company name shown to candidates', type: 'string' })
149
+ .option('company-description',{ describe: 'Short company description shown to candidates', type: 'string' })
150
+ .option('enabled', { describe: 'Enable/disable the Recruiting Bot for this site', type: 'boolean' }),
151
+ handler: runCommand(configSetHandler),
152
+ })
153
+ .demandCommand(1, 'Specify a config subcommand: get | set'),
154
+ handler: () => {},
155
+ };
156
+
157
+ const jobsCommand = {
158
+ command: 'jobs <subcommand>',
159
+ describe: 'List or remove synced job listings',
160
+ builder: (y) => y
161
+ .command({
162
+ command: 'list',
163
+ describe: 'List synced job listings, optionally filtered',
164
+ builder: (y2) => y2
165
+ .option('search', { describe: 'Free-text search across title/description', type: 'string' })
166
+ .option('location', { describe: 'Filter by location', type: 'string' })
167
+ .option('department', { describe: 'Filter by department', type: 'string' }),
168
+ handler: runCommand(jobsListHandler),
169
+ })
170
+ .command({
171
+ command: 'delete <jobId>',
172
+ describe: 'Delete a synced job listing',
173
+ builder: (y2) => y2.positional('jobId', { describe: 'Job listing ID', type: 'number' }),
174
+ handler: runCommand(jobsDeleteHandler),
175
+ })
176
+ .demandCommand(1, 'Specify a jobs subcommand: list | delete'),
177
+ handler: () => {},
178
+ };
179
+
180
+ const applicationsCommand = {
181
+ command: 'applications <subcommand>',
182
+ describe: 'List candidate applications or export EEOC voluntary self-ID data',
183
+ builder: (y) => y
184
+ .command({
185
+ command: 'list',
186
+ describe: 'List candidate applications submitted via the Recruiting Bot',
187
+ handler: runCommand(applicationsListHandler),
188
+ })
189
+ .command({
190
+ command: 'export-eeoc',
191
+ describe: 'Export voluntary EEOC self-ID responses to a CSV file (Professional tier+)',
192
+ builder: (y2) => y2.option('out', { describe: 'Output CSV file path (default: eeoc-export-<timestamp>.csv)', type: 'string' }),
193
+ handler: runCommand(applicationsExportEeocHandler),
194
+ })
195
+ .demandCommand(1, 'Specify an applications subcommand: list | export-eeoc'),
196
+ handler: () => {},
197
+ };
198
+
199
+ // -- Multi-employer job board: named posting sources -------------------------
200
+ // Backend contract (server/Velaro.Messaging/Controllers/RecruitingController.cs, gated on
201
+ // EnableRecruitingBot + EnableJobBoardMultiEmployer):
202
+ // GET Recruiting/Sources -> jobPostingSource[]
203
+ // POST Recruiting/Sources { name, employerName?, sourceType?, jobsFeedUrl?, feedType?, fieldsJson?, isActive? } -> jobPostingSource
204
+ // PUT Recruiting/Sources/{id} (apply-if-present) -> jobPostingSource
205
+ // DELETE Recruiting/Sources/{id} -> { success }
206
+ // POST Recruiting/Sources/{id}/Sync -> { success, error, added, updated, total }
207
+ // POST Recruiting/Sources/{id}/Listings { title, department?, location?, description?, applyUrl?, employmentType?, employerName?, customFieldValuesJson? } -> jobListing
208
+ // PUT Recruiting/Applications/{id}/Stage { stage } -> candidateApplication
209
+
210
+ async function sourcesListHandler() {
211
+ const sources = await messagingGet('/Recruiting/Sources');
212
+ if (!Array.isArray(sources) || !sources.length) {
213
+ console.log('\n No job posting sources found.\n');
214
+ return;
215
+ }
216
+ console.log(`\n${BOLD}Job posting sources${RST} (${sources.length})\n`);
217
+ for (const s of sources) {
218
+ console.log(` ${DIM}#${s.id}${RST} ${s.name} ${DIM}${s.sourceType} / ${s.employerName || '-'} / active=${s.isActive}${RST}`);
219
+ }
220
+ console.log('');
221
+ }
222
+
223
+ async function sourcesCreateHandler(argv) {
224
+ const source = await messagingPost('/Recruiting/Sources', {
225
+ name: argv.name,
226
+ employerName: argv.employerName,
227
+ sourceType: argv.sourceType || 'feed',
228
+ jobsFeedUrl: argv.feedUrl,
229
+ feedType: argv.feedType || 'auto',
230
+ fieldsJson: argv.fieldsJson,
231
+ });
232
+ console.log(`\n${GRN}OK${RST} Job posting source created (id ${source.id}).\n`);
233
+ }
234
+
235
+ async function sourcesUpdateHandler(argv) {
236
+ const body = {};
237
+ if (argv.name !== undefined) body.name = argv.name;
238
+ if (argv.employerName !== undefined) body.employerName = argv.employerName;
239
+ if (argv.sourceType !== undefined) body.sourceType = argv.sourceType;
240
+ if (argv.feedUrl !== undefined) body.jobsFeedUrl = argv.feedUrl;
241
+ if (argv.feedType !== undefined) body.feedType = argv.feedType;
242
+ if (argv.fieldsJson !== undefined) body.fieldsJson = argv.fieldsJson;
243
+ if (argv.active !== undefined) body.isActive = argv.active;
244
+ const source = await messagingPut(`/Recruiting/Sources/${argv.sourceId}`, body);
245
+ console.log(`\n${GRN}OK${RST} Job posting source ${source.id} updated.\n`);
246
+ }
247
+
248
+ async function sourcesDeleteHandler(argv) {
249
+ await messagingDel(`/Recruiting/Sources/${argv.sourceId}`);
250
+ console.log(`\n${GRN}OK${RST} Job posting source ${argv.sourceId} deleted/deactivated.\n`);
251
+ }
252
+
253
+ async function sourcesSyncHandler(argv) {
254
+ const result = await messagingPost(`/Recruiting/Sources/${argv.sourceId}/Sync`, {});
255
+ if (!result.success) {
256
+ console.log(`\n${RED}FAILED${RST} Source sync failed: ${result.error || 'unknown error'}\n`);
257
+ process.exitCode = 1;
258
+ return;
259
+ }
260
+ console.log(`\n${GRN}OK${RST} Source ${argv.sourceId} synced.`);
261
+ console.log(` Added: ${result.added}`);
262
+ console.log(` Updated: ${result.updated}`);
263
+ console.log(` Total: ${result.total}\n`);
264
+ }
265
+
266
+ async function sourcesAddListingHandler(argv) {
267
+ const listing = await messagingPost(`/Recruiting/Sources/${argv.sourceId}/Listings`, {
268
+ title: argv.title,
269
+ department: argv.department,
270
+ location: argv.location,
271
+ description: argv.description,
272
+ applyUrl: argv.applyUrl,
273
+ employmentType: argv.employmentType,
274
+ employerName: argv.employerName,
275
+ });
276
+ console.log(`\n${GRN}OK${RST} Manual job listing created (id ${listing.id}).\n`);
277
+ }
278
+
279
+ async function pipelineMoveHandler(argv) {
280
+ const app = await messagingPut(`/Recruiting/Applications/${argv.applicationId}/Stage`, { stage: argv.stage });
281
+ console.log(`\n${GRN}OK${RST} Application ${app.id} moved to stage "${app.pipelineStage}".\n`);
282
+ }
283
+
284
+ const sourcesCommand = {
285
+ command: 'sources <subcommand>',
286
+ describe: 'Manage named job posting sources for a multi-employer job board (EnableJobBoardMultiEmployer)',
287
+ builder: (y) => y
288
+ .command({ command: 'list', describe: 'List job posting sources', handler: runCommand(sourcesListHandler) })
289
+ .command({
290
+ command: 'create',
291
+ describe: 'Create a new named job posting source',
292
+ builder: (y2) => y2
293
+ .option('name', { describe: 'Display name for this source', type: 'string', demandOption: true })
294
+ .option('employer-name', { describe: 'Public-facing employer/agency name', type: 'string' })
295
+ .option('source-type', { describe: 'feed | manual', type: 'string' })
296
+ .option('feed-url', { describe: 'Feed URL (source-type=feed)', type: 'string' })
297
+ .option('feed-type', { describe: 'auto | json | xml | successfactors | workday', type: 'string' })
298
+ .option('fields-json', { describe: 'JSON array of custom field definitions for this source', type: 'string' }),
299
+ handler: runCommand(sourcesCreateHandler),
300
+ })
301
+ .command({
302
+ command: 'update <sourceId>',
303
+ describe: 'Update a job posting source (apply-if-present, omit a flag to leave it unchanged)',
304
+ builder: (y2) => y2
305
+ .positional('sourceId', { describe: 'Job posting source ID', type: 'number' })
306
+ .option('name', { type: 'string' })
307
+ .option('employer-name', { type: 'string' })
308
+ .option('source-type', { type: 'string' })
309
+ .option('feed-url', { type: 'string' })
310
+ .option('feed-type', { type: 'string' })
311
+ .option('fields-json', { type: 'string' })
312
+ .option('active', { type: 'boolean' }),
313
+ handler: runCommand(sourcesUpdateHandler),
314
+ })
315
+ .command({
316
+ command: 'delete <sourceId>',
317
+ describe: 'Delete (or deactivate, if it has listings) a job posting source',
318
+ builder: (y2) => y2.positional('sourceId', { type: 'number' }),
319
+ handler: runCommand(sourcesDeleteHandler),
320
+ })
321
+ .command({
322
+ command: 'sync <sourceId>',
323
+ describe: 'Manually trigger a sync for one job posting source',
324
+ builder: (y2) => y2.positional('sourceId', { type: 'number' }),
325
+ handler: runCommand(sourcesSyncHandler),
326
+ })
327
+ .command({
328
+ command: 'add-listing <sourceId>',
329
+ describe: 'Manually add a job listing to a source (employer submission / admin entry)',
330
+ builder: (y2) => y2
331
+ .positional('sourceId', { describe: 'Job posting source ID', type: 'number' })
332
+ .option('title', { type: 'string', demandOption: true })
333
+ .option('department', { type: 'string' })
334
+ .option('location', { type: 'string' })
335
+ .option('description', { type: 'string' })
336
+ .option('apply-url', { type: 'string' })
337
+ .option('employment-type', { type: 'string' })
338
+ .option('employer-name', { type: 'string' }),
339
+ handler: runCommand(sourcesAddListingHandler),
340
+ })
341
+ .demandCommand(1, 'Specify a sources subcommand: list | create | update | delete | sync | add-listing'),
342
+ handler: () => {},
343
+ };
344
+
345
+ const pipelineCommand = {
346
+ command: 'pipeline <subcommand>',
347
+ describe: 'HR/agency-side applicant pipeline (Kanban board) for candidate applications',
348
+ builder: (y) => y
349
+ .command({
350
+ command: 'move <applicationId> <stage>',
351
+ describe: 'Move a candidate application to a new pipeline stage (Applied|Screened|Interview|Offer|Hired|Rejected)',
352
+ builder: (y2) => y2
353
+ .positional('applicationId', { type: 'number' })
354
+ .positional('stage', { type: 'string' }),
355
+ handler: runCommand(pipelineMoveHandler),
356
+ })
357
+ .demandCommand(1, 'Specify a pipeline subcommand: move'),
358
+ handler: () => {},
359
+ };
360
+
361
+ export const recruitingCommand = {
362
+ command: 'recruiting <subcommand>',
363
+ describe: 'Recruiting Bot: sync job feeds, manage listings, applications, config, and multi-employer job board sources',
364
+ builder: (y) => y
365
+ .command(syncCommand)
366
+ .command(configCommand)
367
+ .command(jobsCommand)
368
+ .command(applicationsCommand)
369
+ .command(sourcesCommand)
370
+ .command(pipelineCommand)
371
+ .demandCommand(1, 'Specify a recruiting subcommand: sync | config | jobs | applications | sources | pipeline')
372
+ .strict(),
373
+ handler: () => {},
374
+ };