@velaro/cli 1.4.92 → 1.4.93

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.
@@ -1,4 +1,4 @@
1
- import { get, post, messagingGet, messagingPost, getCredentials } from '../api.js';
1
+ import { get, post, messagingGet, messagingPost, messagingPut, getCredentials } from '../api.js';
2
2
  import { runCommand } from '../run.js';
3
3
 
4
4
  const VALID_STATUSES = ['Available', 'Away', 'Offline'];
@@ -97,6 +97,18 @@ export const agentCommand = {
97
97
  console.log(` ${result.message ?? ''}`);
98
98
  }),
99
99
  })
100
+ .command({
101
+ command: 'set-roles',
102
+ describe: 'Set roles (Administrator/Manager/Agent) of an EXISTING user on a site (Velaro staff only) -- makes E2E/QA test logins admin-capable',
103
+ builder: (y) => y
104
+ .option('site-id', { type: 'number', demandOption: true, describe: 'Target site ID' })
105
+ .option('email', { type: 'string', demandOption: true, describe: 'Existing user email' })
106
+ .option('roles', { type: 'array', demandOption: true, describe: 'e.g. --roles Administrator' }),
107
+ handler: runCommand(async (argv) => {
108
+ const r = await messagingPut(`/SupportTools/sites/${argv['site-id']}/agents/roles`, { email: argv.email, roles: argv.roles.map(String) });
109
+ console.log(`OK: ${r.email} roles ${r.before || '(none)'} -> ${r.roles.join(', ')}`);
110
+ }),
111
+ })
100
112
  .command({
101
113
  command: 'set-status',
102
114
  describe: 'Set an agent\'s availability status (admin only)',
@@ -1,7 +1,7 @@
1
- import { writeFileSync } from 'fs';
1
+ import { readFileSync, writeFileSync } from 'fs';
2
2
  import { messagingGet, messagingPost, messagingPut, messagingDel, getCredentials } from '../api.js';
3
3
  import { runCommand } from '../run.js';
4
- import { applicationIdPath, buildApplySettingsBody, buildBotSettingsBody, buildHostedBody, buildJobPayBody, buildRenewBody, jobLifecyclePath, jobPayPath } from '../recruiting-apply.js';
4
+ import { buildAssignBody, buildNoteBody, buildPipelineBody, buildRetentionBody, buildStageBody, applicationIdPath, buildApplySettingsBody, buildBotSettingsBody, buildHostedBody, buildJobPayBody, buildRenewBody, jobLifecyclePath, jobPayPath } from '../recruiting-apply.js';
5
5
 
6
6
  // -- velaro recruiting --------------------------------------------------------
7
7
  // CLI surface for the Recruiting Bot: job-feed sync (JSON/XML/RSS/Indeed/
@@ -22,6 +22,9 @@ import { applicationIdPath, buildApplySettingsBody, buildBotSettingsBody, buildH
22
22
  // GET Recruiting/Applications -> candidateApplication[]
23
23
  // GET Recruiting/Applications/AtsFailures; POST Recruiting/Applications/{id}/AtsRetry; PUT Recruiting/ApplySettings { publicApplyEnabled?, autoCreateCaseTypeId?, defaultListingDays? }; GET Recruiting/Applications/{id}/Resume (binary)
24
24
  // GET Recruiting/Applications/ExportEeoc -> text/csv (Professional tier+, EnableRecruitingBotEeoc)
25
+ // GET/PUT Recruiting/Pipeline { stages:[{key,label,color?,applicantVisible?,applicantLabel?}] } ; PUT Recruiting/Applications/{id}/Stage { stage, note? }
26
+ // GET Recruiting/Applications/{id}/History ; POST .../Notes { note } ; PUT .../Assign { assignedTo }
27
+ // GET/PUT Recruiting/Retention ; POST Recruiting/Retention/Run?dryRun= -> recruiter pipeline and retention (package job-board-pipeline; 403 job_board_pipeline_not_entitled without it)
25
28
 
26
29
  const BOLD = '\x1b[1m';
27
30
  const DIM = '\x1b[2m';
@@ -470,10 +473,100 @@ async function sourcesAddListingHandler(argv) {
470
473
  }
471
474
 
472
475
  async function pipelineMoveHandler(argv) {
473
- const app = await messagingPut(`/Recruiting/Applications/${argv.applicationId}/Stage`, { stage: argv.stage });
476
+ const app = await messagingPut(applicationIdPath(argv.applicationId, 'Stage'), buildStageBody(argv.stage, argv.note));
474
477
  console.log(`\n${GRN}OK${RST} Application ${app.id} moved to stage "${app.pipelineStage}".\n`);
475
478
  }
476
479
 
480
+ function printPipeline(r) {
481
+ console.log(`\n${BOLD}Job Board pipeline${RST}`);
482
+ console.log(` Package: ${r.entitled ? `${GRN}included${RST}` : 'not included (default stages; history, notes, assignment, stage editing and retention are off)'}`);
483
+ console.log(` Source: ${r.source === 'caseType' ? `case type ${r.caseTypeId}` : 'default list'}`);
484
+ for (const s of r.stages ?? []) {
485
+ const seen = s.applicantVisible ? `applicants see "${s.applicantLabel || s.label}"` : 'applicants see "In review"';
486
+ console.log(` ${s.key.padEnd(16)} ${String(s.label).padEnd(20)} ${DIM}${seen}${s.color ? `, ${s.color}` : ''}${RST}`);
487
+ }
488
+ console.log('');
489
+ }
490
+
491
+ async function pipelineGetHandler() {
492
+ printPipeline(await messagingGet('/Recruiting/Pipeline'));
493
+ }
494
+
495
+ async function pipelineSetHandler(argv) {
496
+ let raw = argv.stagesJson;
497
+ if (!raw && argv.file) raw = readFileSync(argv.file, 'utf8');
498
+ if (!raw) throw new Error('Pass --stages-json \'[{"key":"Applied","label":"Applied"}]\' or --file stages.json.');
499
+ const r = await messagingPut('/Recruiting/Pipeline', buildPipelineBody(raw));
500
+ console.log(`\n${GRN}OK${RST} Pipeline saved.`);
501
+ printPipeline(r);
502
+ }
503
+
504
+ // The API returns UTC without a zone designator; show Pacific Time like every other user-facing timestamp.
505
+ function pacific(raw) {
506
+ if (!raw) return '';
507
+ const d = new Date(/(Z|[+-]\d{2}:?\d{2})$/i.test(raw) ? raw : `${raw}Z`);
508
+ return Number.isNaN(d.getTime()) ? String(raw) : `${d.toLocaleString('en-US', { timeZone: 'America/Los_Angeles', dateStyle: 'medium', timeStyle: 'short' })} PT`;
509
+ }
510
+
511
+ async function applicationHistoryHandler(argv) {
512
+ const r = await messagingGet(applicationIdPath(argv.applicationId, 'History'));
513
+ const events = r.events ?? [];
514
+ if (!events.length) {
515
+ console.log('\n No history yet.\n');
516
+ return;
517
+ }
518
+ console.log(`\n${BOLD}Application ${argv.applicationId} history${RST} (newest first, Pacific Time)\n`);
519
+ for (const e of events) {
520
+ // An Assign event carries the new recruiter in note (empty = unassigned); toStage is just the stage at the time.
521
+ const what = e.kind === 'Note' ? 'note' : e.kind === 'Assign' ? (e.note ? `assigned to ${e.note}` : 'unassigned') : `${e.fromStage ?? '-'} -> ${e.toStage ?? '?'}`;
522
+ console.log(` ${pacific(e.at)} ${String(e.actorType).padEnd(8)} ${e.actorId ?? ''} ${what}${e.note && e.kind !== 'Assign' ? ` "${e.note}"` : ''}`);
523
+ }
524
+ console.log('');
525
+ }
526
+
527
+ async function applicationNoteHandler(argv) {
528
+ await messagingPost(applicationIdPath(argv.applicationId, 'Notes'), buildNoteBody(argv.text));
529
+ console.log(`\n${GRN}OK${RST} Note added to application ${argv.applicationId}.\n`);
530
+ }
531
+
532
+ async function applicationAssignHandler(argv) {
533
+ const app = await messagingPut(applicationIdPath(argv.applicationId, 'Assign'), buildAssignBody(argv.who));
534
+ console.log(`\n${GRN}OK${RST} Application ${app.id} ${app.assignedTo ? `assigned to ${app.assignedTo}` : 'unassigned'}.\n`);
535
+ }
536
+
537
+ function printRetention(r) {
538
+ console.log(`\n${BOLD}Applicant record retention${RST}`);
539
+ console.log(` Automatic retention: ${r.enabled ? `${GRN}on${RST}` : 'off'}${r.enabled && r.dryRun ? ' (count only, nothing is deleted)' : ''}`);
540
+ console.log(` Legal hold: ${r.legalHold ? `${RED}ON, all deletion stopped${RST}` : 'off'}`);
541
+ console.log(` Federal contractor: ${r.isFederalContractor ? 'yes' : 'no'}`);
542
+ console.log(` Retention days: ${r.retentionDays} (legal minimum ${r.floorDays}, applied ${r.effectiveDays})`);
543
+ if (r.lastRunAt) console.log(` Last run: ${pacific(r.lastRunAt)}, ${r.lastRunDeleted} deleted, ${r.lastRunWouldDelete} would have been deleted`);
544
+ console.log(` ${DIM}Records are never deleted before 1 year (2 years for federal contractors). Counts only.${RST}\n`);
545
+ }
546
+
547
+ async function retentionHandler(argv) {
548
+ const action = argv.action ?? 'get';
549
+ if (action === 'get') return printRetention(await messagingGet('/Recruiting/Retention'));
550
+ if (action === 'set') {
551
+ // Arming real deletion, or lifting a legal hold, is destructive: refuse unless --yes.
552
+ if ((argv.dryRun === false || argv.legalHold === false) && !argv.yes) {
553
+ throw new Error('This can start real deletion of applicant records or lift a legal hold. Preview first with `velaro recruiting retention run`, then repeat with --yes.');
554
+ }
555
+ const r = await messagingPut('/Recruiting/Retention', buildRetentionBody({ enabled: argv.enabled, dryRun: argv.dryRun, days: argv.days, federalContractor: argv.federalContractor, legalHold: argv.legalHold }));
556
+ console.log(`\n${GRN}OK${RST} Retention settings saved.`);
557
+ return printRetention(r);
558
+ }
559
+ // run: previews by default (nothing deleted); --execute --yes performs a real run.
560
+ if (argv.execute && !argv.yes) throw new Error('--execute permanently deletes applicant records past retention. Preview first (omit --execute), then repeat with --execute --yes.');
561
+ const r = await messagingPost(`/Recruiting/Retention/Run?dryRun=${argv.execute ? 'false' : 'true'}`, {});
562
+ console.log(`\n${BOLD}Retention run${RST} ${r.dryRun ? '(preview, nothing deleted)' : ''}`);
563
+ console.log(` Due: ${r.due}`);
564
+ console.log(` Deleted: ${r.deleted}`);
565
+ console.log(` Resumes deleted: ${r.resumesDeleted}`);
566
+ console.log(` Held by legal hold: ${r.skippedLegalHold}`);
567
+ console.log(` Legal minimum: ${r.floorDays} days\n`);
568
+ }
569
+
477
570
  const sourcesCommand = {
478
571
  command: 'sources <subcommand>',
479
572
  describe: 'Manage named job posting sources for a multi-employer job board (EnableJobBoardMultiEmployer)',
@@ -541,16 +634,74 @@ const pipelineCommand = {
541
634
  builder: (y) => y
542
635
  .command({
543
636
  command: 'move <applicationId> <stage>',
544
- describe: 'Move a candidate application to a new pipeline stage (Applied|Screened|Interview|Offer|Hired|Rejected)',
637
+ describe: 'Move a candidate application to a new pipeline stage (default stages: Applied|Screened|Interview|Offer|Hired|Rejected)',
545
638
  builder: (y2) => y2
546
639
  .positional('applicationId', { type: 'number' })
547
- .positional('stage', { type: 'string' }),
640
+ .positional('stage', { type: 'string' })
641
+ .option('note', { describe: 'Note recorded in the stage history', type: 'string' }),
548
642
  handler: runCommand(pipelineMoveHandler),
549
643
  })
550
- .demandCommand(1, 'Specify a pipeline subcommand: move'),
644
+ .command({ command: 'get', describe: 'Show the pipeline stages (the default list when the Job board pipeline package is not included)', handler: runCommand(pipelineGetHandler) })
645
+ .command({
646
+ command: 'set',
647
+ describe: 'Replace the pipeline stages (needs the Job board pipeline package)',
648
+ builder: (y2) => y2
649
+ .option('stages-json', { describe: 'JSON array: [{"key","label","color"?,"applicantVisible"?,"applicantLabel"?}]', type: 'string' })
650
+ .option('file', { describe: 'Read the stages JSON from a file', type: 'string' }),
651
+ handler: runCommand(pipelineSetHandler),
652
+ })
653
+ .demandCommand(1, 'Specify a pipeline subcommand: get | set | move'),
654
+ handler: () => {},
655
+ };
656
+
657
+ // `recruiting application ...` (singular): per-application stage, history, notes and assignment.
658
+ const applicationCommand = {
659
+ command: 'application <subcommand>',
660
+ describe: 'One application: move stage, show history, add a note, assign a recruiter',
661
+ builder: (y) => y
662
+ .command({
663
+ command: 'stage <applicationId> <stage>',
664
+ describe: 'Move an application to a stage, with an optional note',
665
+ builder: (y2) => y2.positional('applicationId', { type: 'number' }).positional('stage', { type: 'string' }).option('note', { describe: 'Note recorded in the stage history', type: 'string' }),
666
+ handler: runCommand(pipelineMoveHandler),
667
+ })
668
+ .command({
669
+ command: 'history <applicationId>',
670
+ describe: 'Show the stage, note and assignment history, newest first (needs the Job board pipeline package)',
671
+ builder: (y2) => y2.positional('applicationId', { type: 'number' }),
672
+ handler: runCommand(applicationHistoryHandler),
673
+ })
674
+ .command({
675
+ command: 'note <applicationId> <text>',
676
+ describe: 'Add an internal note (never shown to the applicant; needs the Job board pipeline package)',
677
+ builder: (y2) => y2.positional('applicationId', { type: 'number' }).positional('text', { type: 'string' }),
678
+ handler: runCommand(applicationNoteHandler),
679
+ })
680
+ .command({
681
+ command: 'assign <applicationId> <who>',
682
+ describe: 'Assign a recruiter (name or email; "none" clears; needs the Job board pipeline package)',
683
+ builder: (y2) => y2.positional('applicationId', { type: 'number' }).positional('who', { type: 'string' }),
684
+ handler: runCommand(applicationAssignHandler),
685
+ })
686
+ .demandCommand(1, 'Specify an application subcommand: stage | history | note | assign'),
551
687
  handler: () => {},
552
688
  };
553
689
 
690
+ const retentionCommand = {
691
+ command: 'retention [action]',
692
+ describe: 'Applicant record retention: show (default), set, or run a preview (needs the Job board pipeline package; set and run need an Administrator)',
693
+ builder: (y) => y
694
+ .positional('action', { describe: 'get | set | run', type: 'string', choices: ['get', 'set', 'run'], default: 'get' })
695
+ .option('enabled', { describe: 'set: turn automatic retention on or off', type: 'boolean' })
696
+ .option('dry-run', { describe: 'set: count only, never delete', type: 'boolean' })
697
+ .option('days', { describe: 'set: days to keep records (below the legal minimum the minimum applies)', type: 'number' })
698
+ .option('federal-contractor', { describe: 'set: federal contractor (2 year minimum)', type: 'boolean' })
699
+ .option('legal-hold', { describe: 'set: stop all deletion', type: 'boolean' })
700
+ .option('execute', { describe: 'run: really delete due records (default is a preview that deletes nothing)', type: 'boolean' })
701
+ .option('yes', { describe: 'Confirm a deleting run, or set --dry-run=false / --legal-hold=false', type: 'boolean' }),
702
+ handler: runCommand(retentionHandler),
703
+ };
704
+
554
705
  export const recruitingCommand = {
555
706
  command: 'recruiting <subcommand>',
556
707
  describe: 'Recruiting Bot: sync job feeds, manage listings, applications, config, and multi-employer job board sources',
@@ -562,7 +713,9 @@ export const recruitingCommand = {
562
713
  .command(applicationsCommand)
563
714
  .command(sourcesCommand)
564
715
  .command(pipelineCommand)
565
- .demandCommand(1, 'Specify a recruiting subcommand: sync | config | jobs | hosted | applications | sources | pipeline')
716
+ .command(applicationCommand)
717
+ .command(retentionCommand)
718
+ .demandCommand(1, 'Specify a recruiting subcommand: sync | config | jobs | hosted | applications | application | sources | pipeline | retention')
566
719
  .strict(),
567
720
  handler: () => {},
568
721
  };
@@ -111,3 +111,69 @@ export function buildBotSettingsBody(o = {}) {
111
111
  if (!Object.keys(body).length) throw new Error('Specify at least one bot setting.');
112
112
  return body;
113
113
  }
114
+
115
+ // ── Job Board Stage 4: recruiter pipeline, history, notes, assignment, retention (package job-board-pipeline) ──
116
+
117
+ // Body for PUT Recruiting/Applications/{id}/Stage. note is optional (recorded in the stage history).
118
+ export function buildStageBody(stage, note) {
119
+ const s = String(stage ?? '').trim();
120
+ if (!s) throw new Error('stage is required.');
121
+ const body = { stage: s };
122
+ if (note !== undefined && String(note).trim()) {
123
+ if (String(note).length > 2000) throw new Error('note must be 2000 characters or fewer.');
124
+ body.note = String(note).trim();
125
+ }
126
+ return body;
127
+ }
128
+
129
+ // Body for POST Recruiting/Applications/{id}/Notes.
130
+ export function buildNoteBody(note) {
131
+ const t = String(note ?? '').trim();
132
+ if (!t) throw new Error('note is required.');
133
+ if (t.length > 2000) throw new Error('note must be 2000 characters or fewer.');
134
+ return { note: t };
135
+ }
136
+
137
+ // Body for PUT Recruiting/Applications/{id}/Assign. "none" or "" clears the assignment.
138
+ export function buildAssignBody(who) {
139
+ const t = String(who ?? '').trim();
140
+ if (t.length > 200) throw new Error('assignedTo must be 200 characters or fewer.');
141
+ return { assignedTo: !t || t.toLowerCase() === 'none' ? null : t };
142
+ }
143
+
144
+ // Body for PUT Recruiting/Pipeline from a JSON array of stages: [{ key, label, color?, applicantVisible?, applicantLabel? }].
145
+ // The server also validates (1-12 stages, unique keys; removing a stage that holds applications is refused with a 400).
146
+ export function buildPipelineBody(stagesJson) {
147
+ let stages;
148
+ try {
149
+ stages = typeof stagesJson === 'string' ? JSON.parse(stagesJson) : stagesJson;
150
+ } catch (e) {
151
+ throw new Error(`stages must be valid JSON: ${e.message}`);
152
+ }
153
+ if (!Array.isArray(stages) || stages.length < 1 || stages.length > 12) throw new Error('stages must be a JSON array of 1 to 12 stages.');
154
+ const keys = new Set();
155
+ for (const s of stages) {
156
+ if (!s || typeof s !== 'object' || typeof s.key !== 'string' || !s.key.trim() || s.key.length > 64) throw new Error('every stage needs a key (up to 64 characters).');
157
+ if (typeof s.label !== 'string' || !s.label.trim()) throw new Error(`stage ${s.key} needs a label.`);
158
+ if (keys.has(s.key)) throw new Error(`duplicate stage key: ${s.key}.`);
159
+ keys.add(s.key);
160
+ }
161
+ return { stages };
162
+ }
163
+
164
+ // Partial body for PUT Recruiting/Retention (apply-if-present). A day count below the legal floor is accepted by the server,
165
+ // which then applies the floor (365 days, or 730 for federal contractors; 29 CFR 1602.14).
166
+ export function buildRetentionBody({ enabled, dryRun, days, federalContractor, legalHold } = {}) {
167
+ const body = {};
168
+ if (enabled !== undefined) body.enabled = Boolean(enabled);
169
+ if (dryRun !== undefined) body.dryRun = Boolean(dryRun);
170
+ if (federalContractor !== undefined) body.isFederalContractor = Boolean(federalContractor);
171
+ if (legalHold !== undefined) body.legalHold = Boolean(legalHold);
172
+ if (days !== undefined) {
173
+ const n = Number(days);
174
+ if (!Number.isInteger(n) || n < 1 || n > 36500) throw new Error('days must be a whole number from 1 to 36500.');
175
+ body.retentionDays = n;
176
+ }
177
+ if (!Object.keys(body).length) throw new Error('Specify at least one retention setting.');
178
+ return body;
179
+ }
@@ -1,6 +1,6 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { applicationIdPath, buildApplySettingsBody, buildBotSettingsBody, buildHostedBody, buildJobPayBody, buildRenewBody, jobLifecyclePath, jobPayPath } from './recruiting-apply.js';
3
+ import { buildAssignBody, buildNoteBody, buildPipelineBody, buildRetentionBody, buildStageBody, applicationIdPath, buildApplySettingsBody, buildBotSettingsBody, buildHostedBody, buildJobPayBody, buildRenewBody, jobLifecyclePath, jobPayPath } from './recruiting-apply.js';
4
4
 
5
5
  test('job lifecycle paths validate id and action', () => {
6
6
  assert.equal(jobLifecyclePath(7, 'close'), '/Recruiting/Jobs/7/close');
@@ -76,3 +76,40 @@ test('hosted body is apply-if-present; no flags means read-only', () => {
76
76
  assert.deepEqual(buildHostedBody({ enabled: false }), { enabled: false });
77
77
  assert.deepEqual(buildHostedBody({ regenerateLink: true }), { regenerateSlug: true });
78
78
  });
79
+
80
+ // -- Job Board pipeline (Stage 4) ---------------------------------------------
81
+
82
+ test('stage body carries an optional trimmed note', () => {
83
+ assert.deepEqual(buildStageBody('Interview'), { stage: 'Interview' });
84
+ assert.deepEqual(buildStageBody(' Offer ', ' good fit '), { stage: 'Offer', note: 'good fit' });
85
+ assert.deepEqual(buildStageBody('Offer', ' '), { stage: 'Offer' });
86
+ assert.throws(() => buildStageBody(''));
87
+ assert.throws(() => buildStageBody('Offer', 'x'.repeat(2001)));
88
+ });
89
+
90
+ test('note and assign bodies validate', () => {
91
+ assert.deepEqual(buildNoteBody(' hello '), { note: 'hello' });
92
+ assert.throws(() => buildNoteBody(' '));
93
+ assert.deepEqual(buildAssignBody('pat@acme.com'), { assignedTo: 'pat@acme.com' });
94
+ assert.deepEqual(buildAssignBody('none'), { assignedTo: null });
95
+ assert.deepEqual(buildAssignBody(''), { assignedTo: null });
96
+ assert.throws(() => buildAssignBody('x'.repeat(201)));
97
+ });
98
+
99
+ test('pipeline body validates the stage list client side', () => {
100
+ const ok = [{ key: 'Applied', label: 'Applied' }, { key: 'Hired', label: 'Hired', applicantVisible: true }];
101
+ assert.deepEqual(buildPipelineBody(JSON.stringify(ok)), { stages: ok });
102
+ assert.throws(() => buildPipelineBody('{'), /valid JSON/);
103
+ assert.throws(() => buildPipelineBody('[]'));
104
+ assert.throws(() => buildPipelineBody(JSON.stringify([{ key: 'A', label: 'A' }, { key: 'A', label: 'B' }])), /duplicate/);
105
+ assert.throws(() => buildPipelineBody(JSON.stringify([{ key: 'A' }])), /label/);
106
+ assert.throws(() => buildPipelineBody(JSON.stringify(Array.from({ length: 13 }, (_, i) => ({ key: `K${i}`, label: 'x' })))));
107
+ });
108
+
109
+ test('retention body is apply-if-present and range-checked', () => {
110
+ assert.deepEqual(buildRetentionBody({ legalHold: true }), { legalHold: true });
111
+ assert.deepEqual(buildRetentionBody({ enabled: false, dryRun: true, days: '400', federalContractor: true }), { enabled: false, dryRun: true, retentionDays: 400, isFederalContractor: true });
112
+ assert.throws(() => buildRetentionBody({}));
113
+ assert.throws(() => buildRetentionBody({ days: 0 }));
114
+ assert.throws(() => buildRetentionBody({ days: 1.5 }));
115
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "1.4.92",
3
+ "version": "1.4.93",
4
4
  "description": "Velaro Workspace v20 — command-line interface for managing bots, knowledge base ingestion, MCP API keys, search indexes, and ops health.",
5
5
  "type": "module",
6
6
  "bin": {