@velaro/cli 1.4.71 → 1.4.72

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/bin/velaro.js CHANGED
@@ -41,10 +41,13 @@ import { bookingCommand } from '../lib/commands/booking.js';
41
41
  import { reportCommand } from '../lib/commands/report.js';
42
42
  import { scheduleAdherenceCommand } from '../lib/commands/schedule-adherence.js';
43
43
  import { experienceImpactCommand } from '../lib/commands/experience-impact.js';
44
+ import { reportBackfillCommand } from '../lib/commands/report-backfill.js';
45
+ import { staffCommand } from '../lib/commands/staff.js';
44
46
  import { reportExplainCommand } from '../lib/commands/report-explain.js';
45
47
  import { conversationEfficiencyCommand } from '../lib/commands/conversation-efficiency.js';
46
48
  import { dbCommand } from '../lib/commands/db.js';
47
49
  import { focusCommand } from '../lib/commands/focus.js';
50
+ import { triageCommand } from '../lib/commands/triage.js';
48
51
  import { automationDraftCommand } from '../lib/commands/automation-draft.js';
49
52
  import { magentoCommand } from '../lib/commands/magento.js';
50
53
  import { mondayCommand } from '../lib/commands/monday.js';
@@ -145,9 +148,12 @@ await yargs(hideBin(process.argv))
145
148
  .command(conversationEfficiencyCommand)
146
149
  .command(scheduleAdherenceCommand)
147
150
  .command(experienceImpactCommand)
151
+ .command(reportBackfillCommand)
152
+ .command({ command: 'staff', desc: 'Velaro staff: cross-site tools (backfill)', builder: staffCommand, handler: () => {} })
148
153
  .command(reportExplainCommand)
149
154
  .command(dbCommand)
150
155
  .command(focusCommand)
156
+ .command(triageCommand)
151
157
  .command(automationDraftCommand)
152
158
  .command(magentoCommand)
153
159
  .command(mondayCommand)
@@ -278,6 +278,32 @@ const draftPregenerationReportCommand = {
278
278
  },
279
279
  };
280
280
 
281
+ // ── triage economy report (customer) ──────────────────────────────────────────
282
+
283
+ export function renderTriageReport(r) {
284
+ const q = r.quality ?? {};
285
+ const usd = (n) => (n == null ? '—' : '$' + Number(n).toFixed(2));
286
+ return [
287
+ '\n' + BOLD + '── Triage Economy Report (' + (r.siteId != null ? 'site ' + r.siteId : 'platform') + ', ' + r.days + 'd) ──' + RESET,
288
+ ' Mode: ' + (r.effectiveMode ?? '—') + ' (source: ' + (r.effectiveModeSource ?? '—') + ')',
289
+ ' Triaged: ' + r.conversationsTriaged + ' Rules: ' + r.handledByRules + ' Haiku: ' + r.handledByHaiku + ' Hit rate: ' + r.rulesHitRatePercent + '%',
290
+ ' ' + GRN + 'Saved: ' + usd(r.estimatedUsdSaved) + RESET + ' Haiku calls avoided: ' + r.haikuCallsAvoided + ' Avg Haiku cost: ' + usd(r.avgHaikuCostUsd),
291
+ ' Latency: rules ' + (r.rulesLatencyMs ?? '—') + 'ms vs Haiku ' + (r.avgHaikuLatencyMs ?? '—') + 'ms',
292
+ ' Correction rate: rules ' + (q.rulesCorrectionRatePercent ?? '—') + '% (' + (q.rulesCorrections ?? 0) + ') vs Haiku ' + (q.haikuCorrectionRatePercent ?? '—') + '% (' + (q.haikuCorrections ?? 0) + ')',
293
+ ' Skill selection: ' + (r.skillSelectionImpact ?? 'unaffected') + (r.skillSelectionNote ? ' - ' + r.skillSelectionNote : '') + '\n',
294
+ ].join('\n');
295
+ }
296
+
297
+ const triageReportCommand = {
298
+ command: 'triage-report',
299
+ describe: 'Triage Economy report: dollars saved, rules hit rate, correction-rate delta vs Haiku',
300
+ builder: (y) => y.option('days', { type: 'number', default: 30 }).option('json', { type: 'boolean', default: false }),
301
+ handler: async (argv) => {
302
+ const r = await messagingGet('/Focus/Triage/EconomyReport?days=' + argv.days);
303
+ console.log(argv.json ? JSON.stringify(r, null, 2) : renderTriageReport(r));
304
+ },
305
+ };
306
+
281
307
  // ── top-level focus command ───────────────────────────────────────────────────
282
308
 
283
309
  export const focusCommand = {
@@ -292,6 +318,7 @@ export const focusCommand = {
292
318
  .command(triageRulesCommand)
293
319
  .command(setTriageRuleCommand)
294
320
  .command(draftPregenerationReportCommand)
321
+ .command(triageReportCommand)
295
322
  .demandCommand(1, 'Specify a focus subcommand.')
296
323
  .strict(),
297
324
  handler: () => {},
@@ -0,0 +1,23 @@
1
+ import { messagingPost } from '../api.js';
2
+ import { runCommand } from '../run.js';
3
+
4
+ // -- velaro report-backfill <type> --from --to [--apply] ---------------------------
5
+ // Backend: velaro-messaging POST /Reports/backfill/{type}?from&to&apply (site managers; site from auth).
6
+ // Preview by default ("would fill N"); --apply writes. Idempotent. Type "csat" fills survey ratings only.
7
+ export const reportBackfillCommand = {
8
+ command: 'report-backfill <type>',
9
+ describe: 'Backfill report data for your site (preview by default; --apply to write). Type: csat',
10
+ builder: (y) => y
11
+ .positional('type', { type: 'string', describe: 'Backfill type, e.g. csat' })
12
+ .option('from', { type: 'string', demandOption: true, describe: 'yyyy-MM-dd' })
13
+ .option('to', { type: 'string', demandOption: true, describe: 'yyyy-MM-dd' })
14
+ .option('apply', { type: 'boolean', default: false, describe: 'Write the changes (default: preview only)' })
15
+ .option('json', { type: 'boolean', default: false, describe: 'Raw JSON output' }),
16
+ handler: runCommand(async (argv) => {
17
+ const qs = new URLSearchParams({ from: argv.from, to: argv.to, apply: String(!!argv.apply) });
18
+ const r = await messagingPost(`/Reports/backfill/${encodeURIComponent(argv.type)}?${qs}`);
19
+ if (argv.json) { console.log(JSON.stringify(r, null, 2)); return; }
20
+ console.log(argv.apply ? '\nBackfill APPLIED' : '\nBackfill PREVIEW (nothing written; add --apply to write)');
21
+ console.log(JSON.stringify(r, null, 2));
22
+ }),
23
+ };
@@ -0,0 +1,31 @@
1
+ // velaro staff backfill --site <id> --type csat --from --to [--apply] -- Velaro staff only.
2
+ // Backend: messaging GET SuperAdmin/backfill-types, POST SuperAdmin/sites/{siteId}/backfill.
3
+ // Site is always explicit in the route, never derived from the caller's auth.
4
+ import { messagingGet, messagingPost } from '../api.js';
5
+
6
+ async function run(argv) {
7
+ if (argv.list) {
8
+ const t = await messagingGet('/SuperAdmin/backfill-types');
9
+ if (argv.json) { console.log(JSON.stringify(t, null, 2)); return; }
10
+ for (const x of t) console.log(`${x.type}: ${x.title} - ${x.description}`);
11
+ return;
12
+ }
13
+ if (!argv.site || !argv.type || !argv.from || !argv.to) throw new Error('--site, --type, --from and --to are required (or use --list).');
14
+ const r = await messagingPost(`/SuperAdmin/sites/${encodeURIComponent(argv.site)}/backfill`, { type: argv.type, from: argv.from, to: argv.to, apply: !!argv.apply });
15
+ if (argv.json) { console.log(JSON.stringify(r, null, 2)); return; }
16
+ console.log(argv.apply ? `\nBackfill APPLIED for site ${argv.site}` : `\nBackfill PREVIEW for site ${argv.site} (nothing written; add --apply)`);
17
+ console.log(JSON.stringify(r, null, 2));
18
+ }
19
+
20
+ export function staffCommand(yargs) {
21
+ return yargs
22
+ .command('backfill', 'Preview/apply a report-data backfill for any site (staff only)', (y) => y
23
+ .option('site', { type: 'number', describe: 'Target site id' })
24
+ .option('type', { type: 'string', describe: 'Backfill type, e.g. csat' })
25
+ .option('from', { type: 'string', describe: 'yyyy-MM-dd' })
26
+ .option('to', { type: 'string', describe: 'yyyy-MM-dd' })
27
+ .option('apply', { type: 'boolean', default: false, describe: 'Write (default preview)' })
28
+ .option('list', { type: 'boolean', default: false, describe: 'List backfill types' })
29
+ .option('json', { type: 'boolean', describe: 'Raw JSON' }), (a) => run(a).catch(e => { console.error(e.message); process.exit(1); }))
30
+ .demandCommand(1, 'Specify a subcommand: backfill');
31
+ }
@@ -0,0 +1,66 @@
1
+ // Staff-only (Velaro Administrator) Triage Economy controls: platform default/force mode,
2
+ // per-site effective modes, and the savings/quality report. Resolution is
3
+ // force ?? perSite ?? platformDefault ?? "economy". Skill: .claude/skills/triage-economy-report/SKILL.md
4
+ import { messagingGet, messagingPut } from '../api.js';
5
+ import { renderTriageReport } from './focus.js';
6
+
7
+ const MODES = ['off', 'economy', 'null'];
8
+ const toMode = (v) => (v === 'null' ? null : v);
9
+
10
+ const platformGet = {
11
+ command: 'get',
12
+ describe: 'Show platform forceMode / defaultMode',
13
+ builder: (y) => y,
14
+ handler: async () => console.log(JSON.stringify(await messagingGet('/superadmin/focus-triage/platform'), null, 2)),
15
+ };
16
+
17
+ const platformSet = {
18
+ command: 'set',
19
+ describe: 'Set platform defaultMode and/or forceMode (off|economy|null; omitted = unchanged)',
20
+ builder: (y) => y
21
+ .option('default', { type: 'string', choices: MODES, describe: 'Mode for sites with no own setting' })
22
+ .option('force', { type: 'string', choices: MODES, describe: 'Mode forced on ALL sites (null clears)' }),
23
+ handler: async (argv) => {
24
+ const body = {};
25
+ if (argv.default !== undefined) body.defaultMode = toMode(argv.default);
26
+ if (argv.force !== undefined) body.forceMode = toMode(argv.force);
27
+ if (!Object.keys(body).length) throw new Error('Provide --default and/or --force.');
28
+ console.log(JSON.stringify(await messagingPut('/superadmin/focus-triage/platform', body), null, 2));
29
+ },
30
+ };
31
+
32
+ const platformCommand = {
33
+ command: 'platform <action>',
34
+ describe: 'Platform-wide triage mode',
35
+ builder: (y) => y.command(platformGet).command(platformSet).demandCommand(1).strict(),
36
+ handler: () => {},
37
+ };
38
+
39
+ const sitesCommand = {
40
+ command: 'sites',
41
+ describe: 'Every site with effective triage mode and source (force|site|default)',
42
+ builder: (y) => y,
43
+ handler: async () => console.log(JSON.stringify(await messagingGet('/superadmin/focus-triage/sites'), null, 2)),
44
+ };
45
+
46
+ const reportCommand = {
47
+ command: 'report',
48
+ describe: 'Triage Economy report; no --site = platform-wide with per-site breakdown',
49
+ builder: (y) => y
50
+ .option('site', { type: 'number' })
51
+ .option('days', { type: 'number', default: 30 })
52
+ .option('json', { type: 'boolean', default: false }),
53
+ handler: async (argv) => {
54
+ const qs = new URLSearchParams({ days: String(argv.days) });
55
+ if (argv.site != null) qs.set('siteId', String(argv.site));
56
+ const r = await messagingGet('/superadmin/focus-triage/report?' + qs);
57
+ console.log(argv.json ? JSON.stringify(r, null, 2) : renderTriageReport(r));
58
+ },
59
+ };
60
+
61
+ export const triageCommand = {
62
+ command: 'triage <subcommand>',
63
+ describe: 'Staff: Triage Economy platform mode, per-site modes, and savings report (Velaro Administrator)',
64
+ builder: (y) => y.command(platformCommand).command(sitesCommand).command(reportCommand).demandCommand(1).strict(),
65
+ handler: () => {},
66
+ };
@@ -5,7 +5,7 @@
5
5
  "sources": "Work Schedules and presence, chat queue timing, missed chats, and CSAT from ticket replies and 1-5 survey ratings (questions tagged csat or star_rating).",
6
6
  "prerequisites": "Work Schedules for agents; a survey with a 1-5 CSAT or star question attached to post-chat or a workflow, or ticket CSAT requests turned on; chat traffic in range.",
7
7
  "interpret": "Weights: service level 30, adherence 25, CSAT 25, missed chats 20; a signal with no data drops out. CSAT needs 5+ responses to count. Response rate = rated survey submissions / all survey submissions. Actual agents below scheduled while missed chats rise = staffing gap.",
8
- "whyZero": "CSAT empty: no CSAT survey configured; survey exists but nobody answered; answers came in with no 1-5 rating question; fewer than 5 responses; or the range predates the survey. If older chats were surveyed but never counted, ask Velaro support to run a CSAT backfill. Adherence empty: no Work Schedules. Sample data never mixes with real data.",
8
+ "whyZero": "CSAT empty: no CSAT survey configured; survey exists but nobody answered; answers came in with no 1-5 rating question; fewer than 5 responses; or the range predates the survey. Surveyed but unrated? Managers run the CSAT backfill (survey ratings only, not ticket CSAT): preview, then apply, via the report banner, `velaro report-backfill csat`, or MCP report_backfill. Adherence empty: no Work Schedules.",
9
9
  "fix": "Add a 1-5 star or CSAT question to the post-chat survey, enable ticket CSAT requests, widen the range, then fix the weakest component.",
10
10
  "related": "Schedule Adherence, Service Level, Missed Chats, Surveys & NPS, Sentiment, Agent Performance"
11
11
  },
@@ -0,0 +1,18 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { renderTriageReport } from './commands/focus.js';
4
+
5
+ test('renderTriageReport shows dollars, hit rate, correction delta and skill note', () => {
6
+ const out = renderTriageReport({
7
+ siteId: 1032, days: 30, effectiveMode: 'economy', effectiveModeSource: 'site',
8
+ conversationsTriaged: 100, handledByRules: 60, handledByHaiku: 40, rulesHitRatePercent: 60,
9
+ haikuCallsAvoided: 60, estimatedUsdSaved: 1.5, avgHaikuCostUsd: 0.025,
10
+ rulesLatencyMs: 2, avgHaikuLatencyMs: 800,
11
+ quality: { rulesCorrections: 1, haikuCorrections: 2, rulesCorrectionRatePercent: 1.7, haikuCorrectionRatePercent: 5 },
12
+ skillSelectionImpact: 'none',
13
+ });
14
+ assert.match(out, /\$1\.50/);
15
+ assert.match(out, /Hit rate: 60%/);
16
+ assert.match(out, /rules 1\.7% .* vs Haiku 5%/);
17
+ assert.match(out, /Skill selection: none/);
18
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "1.4.71",
3
+ "version": "1.4.72",
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": {