@velaro/cli 1.4.98 → 1.4.100

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
@@ -15,6 +15,7 @@ import { knowledgeAdvisorCommand } from '../lib/commands/knowledge-advisor.js';
15
15
  import { productCommand } from '../lib/commands/product.js';
16
16
  import { mcpKeyCommand } from '../lib/commands/mcp-key.js';
17
17
  import { opsCommand } from '../lib/commands/ops.js';
18
+ import { cobrowseCommand } from '../lib/commands/cobrowse.js';
18
19
  import { deployStateCommand } from '../lib/commands/deploy-state.js';
19
20
  import { checkCommand } from '../lib/commands/check.js';
20
21
  import { logsCommand } from '../lib/commands/logs.js';
@@ -26,6 +27,7 @@ import { statusCommand } from '../lib/commands/status.js';
26
27
  import { activityCommand } from '../lib/commands/activity.js';
27
28
  import { ruleCommand } from '../lib/commands/rule.js';
28
29
  import { teamCommand } from '../lib/commands/team.js';
30
+ import { dynamicsCommand } from '../lib/commands/dynamics.js';
29
31
  import { workflowCommand } from '../lib/commands/workflow.js';
30
32
  import { aiConfigCommand } from '../lib/commands/ai-config.js';
31
33
  import { deploymentCommand } from '../lib/commands/deployment.js';
@@ -39,6 +41,7 @@ import { calendlyCommand } from '../lib/commands/calendly.js';
39
41
  import { copilotStudioCommand } from '../lib/commands/copilotstudio.js';
40
42
  import { acuityCommand } from '../lib/commands/acuity.js';
41
43
  import { avatarCommand } from '../lib/commands/avatar.js';
44
+ import { imageAnalysisCommand } from '../lib/commands/image-analysis.js';
42
45
  import { integrationCommand } from '../lib/commands/integration.js';
43
46
  import { appointmentsCommand } from '../lib/commands/appointments.js';
44
47
  import { bookingCommand } from '../lib/commands/booking.js';
@@ -149,6 +152,7 @@ await yargs(hideBin(process.argv))
149
152
  .command(productCommand)
150
153
  .command(mcpKeyCommand)
151
154
  .command(opsCommand)
155
+ .command(cobrowseCommand)
152
156
  .command(deployStateCommand)
153
157
  .command(checkCommand)
154
158
  .command(logsCommand)
@@ -160,6 +164,7 @@ await yargs(hideBin(process.argv))
160
164
  .command(activityCommand)
161
165
  .command(ruleCommand)
162
166
  .command(teamCommand)
167
+ .command(dynamicsCommand)
163
168
  .command(workflowCommand)
164
169
  .command(aiConfigCommand)
165
170
  .command(deploymentCommand)
@@ -173,6 +178,7 @@ await yargs(hideBin(process.argv))
173
178
  .command(copilotStudioCommand)
174
179
  .command(acuityCommand)
175
180
  .command(avatarCommand)
181
+ .command(imageAnalysisCommand)
176
182
  .command(integrationCommand)
177
183
  .command(appointmentsCommand)
178
184
  .command(bookingCommand)
@@ -0,0 +1,90 @@
1
+ // Pure helpers for `velaro cobrowse ...` (kept free of I/O so they are unit-testable).
2
+ //
3
+ // The platform API (messaging: GET/PUT superadmin/sites/{siteId}/cobrowse...) has REPLACE semantics: every setting
4
+ // that is not sent goes back to the platform default. For a CLI that is a footgun, so `set` first reads the current
5
+ // raw settings and only overwrites what the operator named on the command line.
6
+
7
+ export const TRI_FIELDS = [
8
+ ['cues', 'cuesEnabled'],
9
+ ['formFill', 'formFillEnabled'],
10
+ ['strictMask', 'strictMaskMode'],
11
+ ['disableMobile', 'disableOnMobile'],
12
+ ];
13
+
14
+ /** "on" / "off" / "default" (also true/false) -> true / false / null. Throws on anything else. */
15
+ export function parseTri(value, flag) {
16
+ if (value === undefined) return undefined;
17
+ const v = String(value).trim().toLowerCase();
18
+ if (['on', 'true', 'yes', '1'].includes(v)) return true;
19
+ if (['off', 'false', 'no', '0'].includes(v)) return false;
20
+ if (['default', 'inherit', 'null', ''].includes(v)) return null;
21
+ throw new Error(`--${flag} must be on, off or default (got "${value}")`);
22
+ }
23
+
24
+ function parseIntOrDefault(value, flag) {
25
+ if (value === undefined) return undefined;
26
+ const v = String(value).trim().toLowerCase();
27
+ if (v === 'default' || v === '') return null;
28
+ const n = Number(v);
29
+ if (!Number.isInteger(n)) throw new Error(`--${flag} must be a whole number of minutes or "default" (got "${value}")`);
30
+ return n;
31
+ }
32
+
33
+ function parseTextOrDefault(value) {
34
+ if (value === undefined) return undefined;
35
+ return String(value).trim().toLowerCase() === 'default' ? null : String(value);
36
+ }
37
+
38
+ /**
39
+ * Merge operator flags over the site's current raw settings. Returns the full request body to PUT.
40
+ * Protected pages: "default" -> platform list, "none" -> no protected pages, otherwise a newline/comma list.
41
+ */
42
+ export function buildCobrowseUpdate(current, args) {
43
+ const body = { ...(current ?? {}) };
44
+ delete body.updatedAt;
45
+
46
+ for (const [flag, field] of TRI_FIELDS) {
47
+ const v = parseTri(args[flag], flag);
48
+ if (v !== undefined) body[field] = v;
49
+ }
50
+
51
+ if (args.protectedPages !== undefined) {
52
+ const raw = String(args.protectedPages).trim();
53
+ const lower = raw.toLowerCase();
54
+ body.blockedUrlPatterns = lower === 'default' ? null : lower === 'none' ? '' : raw.split(/[\n,]/).map(s => s.trim()).filter(Boolean).join('\n');
55
+ }
56
+
57
+ const max = parseIntOrDefault(args.maxMinutes, 'max-minutes');
58
+ if (max !== undefined) body.maxSessionMinutes = max;
59
+ const idle = parseIntOrDefault(args.idleMinutes, 'idle-minutes');
60
+ if (idle !== undefined) body.idleStopMinutes = idle;
61
+
62
+ const title = parseTextOrDefault(args.consentTitle);
63
+ if (title !== undefined) body.consentTitle = title;
64
+ const message = parseTextOrDefault(args.consentMessage);
65
+ if (message !== undefined) body.consentMessage = message;
66
+ const allow = parseTextOrDefault(args.consentAllow);
67
+ if (allow !== undefined) body.consentAllowLabel = allow;
68
+
69
+ const teams = parseTextOrDefault(args.teams);
70
+ if (teams !== undefined) body.allowedTeamIds = teams === 'none' ? null : teams;
71
+ const roles = parseTextOrDefault(args.roles);
72
+ if (roles !== undefined) body.allowedRoles = roles === 'none' ? null : roles;
73
+
74
+ return body;
75
+ }
76
+
77
+ export function describeEffective(e) {
78
+ if (!e) return [];
79
+ return [
80
+ ['Agent cues', e.cuesEnabled ? 'on' : 'off'],
81
+ ['Guided form fill', e.formFillEnabled ? 'on' : 'off'],
82
+ ['Strict mask mode', e.strictMask ? 'on' : 'off'],
83
+ ['Off on mobile', e.disableOnMobile ? 'yes' : 'no'],
84
+ ['Protected pages', (e.blockedUrlPatterns ?? []).length ? e.blockedUrlPatterns.join(' ') : 'none'],
85
+ ['Max session / idle stop', `${e.maxSessionMinutes} min / ${e.idleStopMinutes} min`],
86
+ ['Who can start', (e.allowedTeamIds ?? []).length || (e.allowedRoles ?? []).length
87
+ ? `teams [${(e.allowedTeamIds ?? []).join(', ')}] roles [${(e.allowedRoles ?? []).join(', ')}]`
88
+ : 'everyone with a seat'],
89
+ ];
90
+ }
@@ -0,0 +1,45 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { buildCobrowseUpdate, parseTri, describeEffective } from './cobrowse-settings.js';
4
+
5
+ test('parseTri maps on/off/default and rejects anything else', () => {
6
+ assert.equal(parseTri('on', 'cues'), true);
7
+ assert.equal(parseTri('OFF', 'cues'), false);
8
+ assert.equal(parseTri('default', 'cues'), null);
9
+ assert.equal(parseTri(undefined, 'cues'), undefined);
10
+ assert.throws(() => parseTri('maybe', 'cues'), /--cues must be on, off or default/);
11
+ });
12
+
13
+ test('set only changes the flags named; everything else is preserved (the API itself replaces)', () => {
14
+ const current = { cuesEnabled: true, strictMaskMode: true, maxSessionMinutes: 30, blockedUrlPatterns: '/a\n/b', updatedAt: '2026-10-15T00:00:00Z' };
15
+ const body = buildCobrowseUpdate(current, { formFill: 'on', maxMinutes: '60' });
16
+ assert.deepEqual(body, { cuesEnabled: true, strictMaskMode: true, maxSessionMinutes: 60, blockedUrlPatterns: '/a\n/b', formFillEnabled: true });
17
+ });
18
+
19
+ test('default clears a setting back to the platform default', () => {
20
+ const body = buildCobrowseUpdate({ cuesEnabled: false, maxSessionMinutes: 30 }, { cues: 'default', maxMinutes: 'default' });
21
+ assert.equal(body.cuesEnabled, null);
22
+ assert.equal(body.maxSessionMinutes, null);
23
+ });
24
+
25
+ test('protected pages: default / none / explicit list', () => {
26
+ assert.equal(buildCobrowseUpdate({}, { protectedPages: 'default' }).blockedUrlPatterns, null);
27
+ assert.equal(buildCobrowseUpdate({}, { protectedPages: 'none' }).blockedUrlPatterns, '');
28
+ assert.equal(buildCobrowseUpdate({}, { protectedPages: '/checkout, /portal/pay ' }).blockedUrlPatterns, '/checkout\n/portal/pay');
29
+ });
30
+
31
+ test('teams and roles accept "none"', () => {
32
+ const body = buildCobrowseUpdate({ allowedTeamIds: '1,2', allowedRoles: 'Manager' }, { teams: 'none', roles: 'Agent' });
33
+ assert.equal(body.allowedTeamIds, null);
34
+ assert.equal(body.allowedRoles, 'Agent');
35
+ });
36
+
37
+ test('non-integer minutes are rejected before anything is sent', () => {
38
+ assert.throws(() => buildCobrowseUpdate({}, { maxMinutes: 'soon' }), /whole number/);
39
+ });
40
+
41
+ test('describeEffective renders a readable summary', () => {
42
+ const rows = describeEffective({ cuesEnabled: true, formFillEnabled: false, strictMask: false, disableOnMobile: false, blockedUrlPatterns: ['/checkout'], maxSessionMinutes: 120, idleStopMinutes: 15, allowedTeamIds: [], allowedRoles: [] });
43
+ assert.ok(rows.some(([k, v]) => k === 'Protected pages' && v === '/checkout'));
44
+ assert.ok(rows.some(([k, v]) => k === 'Who can start' && v === 'everyone with a seat'));
45
+ });
@@ -4,14 +4,16 @@ import { runCommand } from '../run.js';
4
4
  const print = (data) => console.log(JSON.stringify(data, null, 2));
5
5
  const list = (v) => (v == null ? [] : String(v).split(',').map((s) => s.trim()).filter(Boolean));
6
6
 
7
- // velaro chat-topics <show|set|template|merge|rename|retire|group|group-suggestions|suggestions|accept|dismiss|
7
+ // velaro chat-topics <show|settings|set|template|merge|rename|retire|group|group-suggestions|suggestions|accept|dismiss|
8
8
  // reclassify-estimate|reclassify|analysis-estimate|analysis|proposal>
9
9
  // REST: messaging /ChatIntelligenceTopics/* (site comes from the CLI session, never an argument)
10
10
  export const chatIntelligenceTopicsCommand = {
11
11
  command: 'chat-topics <action>',
12
12
  describe: 'Chat Intelligence topics: edit the list, merge/rename/group (no AI cost), suggested topics, optional reclassify and analysis',
13
13
  builder: (y) => y
14
- .positional('action', { choices: ['show', 'set', 'template', 'merge', 'rename', 'retire', 'group', 'group-suggestions', 'suggestions', 'accept', 'dismiss', 'reclassify-estimate', 'reclassify', 'analysis-estimate', 'analysis', 'proposal'] })
14
+ .positional('action', { choices: ['show', 'settings', 'set', 'template', 'merge', 'rename', 'retire', 'group', 'group-suggestions', 'suggestions', 'accept', 'dismiss', 'reclassify-estimate', 'reclassify', 'analysis-estimate', 'analysis', 'proposal'] })
15
+ .option('multi-topic', { type: 'boolean', describe: 'settings: turn multi-topic tracking on (--multi-topic) or off (--no-multi-topic); omit to just show' })
16
+ .option('re-eval', { type: 'number', describe: 'settings: re-check after N new visitor messages (1-50)' })
15
17
  .option('topics', { type: 'string', describe: 'set: comma-separated topic list' })
16
18
  .option('industry', { type: 'string', describe: 'template: insurance|healthcare|retail|ecommerce|financial|saas|government|manufacturing' })
17
19
  .option('from', { type: 'string', describe: 'merge/rename/retire/group: comma-separated topics' })
@@ -29,6 +31,9 @@ export const chatIntelligenceTopicsCommand = {
29
31
  const ops = (op) => messagingPost(`${base}/mapping`, { ops: [{ op, from: list(a.from), to: a.to ?? null, group: a.group ?? null }] });
30
32
  switch (a.action) {
31
33
  case 'show': return print(await messagingGet(base));
34
+ case 'settings':
35
+ if (a.multiTopic == null && a.reEval == null) return print(await messagingGet(`${base}/settings`));
36
+ return print(await messagingPut(`${base}/settings`, { themeMultiTopicEnabled: a.multiTopic ?? null, themeReEvalEveryNMessages: a.reEval ?? null }));
32
37
  case 'set': return print(await messagingPut(base, { topics: list(a.topics) }));
33
38
  case 'template': return print(await messagingPost(`${base}/template`, { industry: a.industry }));
34
39
  case 'merge': case 'rename': case 'retire': case 'group': return print(await ops(a.action));
@@ -0,0 +1,99 @@
1
+ import { messagingGet, messagingPut } from '../api.js';
2
+ import { buildCobrowseUpdate, describeEffective } from '../cobrowse-settings.js';
3
+
4
+ // velaro cobrowse show|set|reset|sessions <siteId>
5
+ //
6
+ // Staff view and control of a site's Co-Browse configuration (what the plan entitles it to, what the site switched
7
+ // on, the effective values after platform defaults and hard caps, and recent session metadata). Goes through the
8
+ // messaging SuperAdmin endpoints, which share ONE validated service with the customer admin UI and the MCP tools,
9
+ // so the CLI cannot store anything the UI cannot, and every change lands in the site's Activity Log.
10
+
11
+ const RESET = '\x1b[0m';
12
+ const BOLD = '\x1b[1m';
13
+ const DIM = '\x1b[2m';
14
+ const GRN = '\x1b[32m';
15
+ const RED = '\x1b[31m';
16
+
17
+ function fmtDate(iso) {
18
+ if (!iso) return '-';
19
+ return new Date(iso).toLocaleString('en-US', { timeZone: 'America/Los_Angeles', dateStyle: 'short', timeStyle: 'short' });
20
+ }
21
+
22
+ const siteArg = (y) => y.positional('siteId', { type: 'number', describe: 'Site id' });
23
+
24
+ function printSite(data) {
25
+ const ent = data.entitlements ?? {};
26
+ const chip = (on) => (on ? `${GRN}yes${RESET}` : `${RED}no${RESET}`);
27
+ console.log(`\n${BOLD}Co-Browse - site ${data.siteId}${RESET}`);
28
+ console.log(` Entitled: Co-Browse ${chip(ent.coBrowse)} Plus cues ${chip(ent.cues)} Plus form fill ${chip(ent.formFill)} Plus redaction ${chip(ent.enhancedPiiRedaction)}`);
29
+ console.log(`\n${BOLD}Effective settings${RESET} ${DIM}(null in the raw settings = platform default)${RESET}`);
30
+ for (const [k, v] of describeEffective(data.effective)) console.log(` ${k.padEnd(26)} ${v}`);
31
+ }
32
+
33
+ const showCommand = {
34
+ command: 'show <siteId>',
35
+ describe: 'Show entitlement, settings and recent sessions for a site',
36
+ builder: siteArg,
37
+ handler: async (argv) => {
38
+ const data = await messagingGet(`/superadmin/sites/${argv.siteId}/cobrowse`);
39
+ if (argv.json) return console.log(JSON.stringify(data, null, 2));
40
+ printSite(data);
41
+ console.log(`\n${BOLD}Recent sessions${RESET} ${DIM}(metadata only, Pacific time)${RESET}`);
42
+ const rows = data.recentSessions ?? [];
43
+ if (rows.length === 0) console.log(' none');
44
+ for (const s of rows) {
45
+ console.log(` ${fmtDate(s.startedAt).padEnd(18)} conv #${String(s.conversationId).padEnd(8)} agent ${String(s.initiatedByUserId ?? '-').padEnd(7)} ${String(s.durationSeconds ?? '-').padStart(5)}s ${s.outcome ?? '-'}${s.endReason ? ` (${s.endReason})` : ''}`);
46
+ }
47
+ },
48
+ };
49
+
50
+ const setCommand = {
51
+ command: 'set <siteId>',
52
+ describe: 'Change Co-Browse settings for a site (only the flags you pass change)',
53
+ builder: (y) => siteArg(y)
54
+ .option('cues', { type: 'string', describe: 'Agent pointer/highlight/annotation (Plus): on | off | default' })
55
+ .option('form-fill', { type: 'string', describe: 'Guided form fill (Plus): on | off | default', alias: 'formFill' })
56
+ .option('strict-mask', { type: 'string', describe: 'Strict mask mode: on | off | default', alias: 'strictMask' })
57
+ .option('disable-mobile', { type: 'string', describe: 'Turn off on mobile: on | off | default', alias: 'disableMobile' })
58
+ .option('protected-pages', { type: 'string', describe: 'URL patterns (newline/comma separated), "default" or "none"', alias: 'protectedPages' })
59
+ .option('max-minutes', { type: 'string', describe: 'Max session minutes (5-240) or default', alias: 'maxMinutes' })
60
+ .option('idle-minutes', { type: 'string', describe: 'Idle auto-stop minutes (2-60) or default', alias: 'idleMinutes' })
61
+ .option('consent-title', { type: 'string', alias: 'consentTitle' })
62
+ .option('consent-message', { type: 'string', alias: 'consentMessage' })
63
+ .option('consent-allow', { type: 'string', alias: 'consentAllow' })
64
+ .option('teams', { type: 'string', describe: 'Comma separated team ids allowed to start, or "none"' })
65
+ .option('roles', { type: 'string', describe: 'Comma separated roles allowed to start (Administrator, Manager, Agent), or "none"' }),
66
+ handler: async (argv) => {
67
+ const current = await messagingGet(`/superadmin/sites/${argv.siteId}/cobrowse`);
68
+ const body = buildCobrowseUpdate(current.settings, argv);
69
+ const saved = await messagingPut(`/superadmin/sites/${argv.siteId}/cobrowse-settings`, body);
70
+ if (argv.json) return console.log(JSON.stringify(saved, null, 2));
71
+ console.log(`${GRN}Saved.${RESET} Effective settings now:`);
72
+ for (const [k, v] of describeEffective(saved.effective)) console.log(` ${k.padEnd(26)} ${v}`);
73
+ },
74
+ };
75
+
76
+ const resetCommand = {
77
+ command: 'reset <siteId>',
78
+ describe: 'Reset every Co-Browse setting for a site to the platform defaults',
79
+ builder: siteArg,
80
+ handler: async (argv) => {
81
+ const saved = await messagingPut(`/superadmin/sites/${argv.siteId}/cobrowse-settings`, {});
82
+ if (argv.json) return console.log(JSON.stringify(saved, null, 2));
83
+ console.log(`${GRN}Reset to platform defaults.${RESET}`);
84
+ for (const [k, v] of describeEffective(saved.effective)) console.log(` ${k.padEnd(26)} ${v}`);
85
+ },
86
+ };
87
+
88
+ export const cobrowseCommand = {
89
+ command: 'cobrowse <subcommand>',
90
+ describe: 'View and control a site\'s Co-Browse settings and session history',
91
+ builder: (y) => y
92
+ .option('json', { type: 'boolean', describe: 'Print raw JSON' })
93
+ .command(showCommand)
94
+ .command(setCommand)
95
+ .command(resetCommand)
96
+ .demandCommand(1, 'Specify a subcommand: show, set, reset')
97
+ .strict(),
98
+ handler: () => {},
99
+ };
@@ -0,0 +1,44 @@
1
+ import { request } from '../api.js';
2
+ import { runCommand } from '../run.js';
3
+
4
+ // ── velaro dynamics team-overrides ────────────────────────────────────────────
5
+ // Per-team (group) override of Dynamics field mappings + activity type.
6
+ // Effective value = team override ?? the connection's own setting ("apply to all groups" default).
7
+ // Site is resolved server-side from the caller's credentials, never passed by the client.
8
+
9
+ const base = (config) => `/Dynamics/Configs/${config}/team-overrides`;
10
+
11
+ export const dynamicsCommand = {
12
+ command: 'dynamics <subcommand>',
13
+ describe: 'Dynamics 365 integration settings',
14
+ builder: yargs => yargs
15
+ .command({
16
+ command: 'team-overrides <action>',
17
+ describe: 'Per-team overrides of field mappings / activity type (get | set | reset)',
18
+ builder: y => y
19
+ .positional('action', { choices: ['get', 'set', 'reset'] })
20
+ .option('config', { type: 'number', demandOption: true, describe: 'Dynamics config id (velaro dynamics config list in the admin UI)' })
21
+ .option('team', { type: 'number', describe: 'Team id (required for set/reset; see: velaro team list)' })
22
+ .option('activity', { type: 'string', describe: 'Activity logical name (e.g. phonecall, task). Empty string = inherit the default.' })
23
+ .option('mappings', { type: 'string', describe: 'customFieldMappingsJson (JSON array). Empty string = inherit the default.' }),
24
+ handler: runCommand(async argv => {
25
+ if (argv.action === 'get') {
26
+ const data = await request('GET', base(argv.config));
27
+ console.log(JSON.stringify(data, null, 2));
28
+ return;
29
+ }
30
+ if (!argv.team) throw new Error('--team is required for set/reset');
31
+ if (argv.action === 'reset') {
32
+ console.log(JSON.stringify(await request('DELETE', `${base(argv.config)}/${argv.team}`), null, 2));
33
+ return;
34
+ }
35
+ const body = {};
36
+ if (argv.activity !== undefined) body.activityLogicalName = argv.activity;
37
+ if (argv.mappings !== undefined) body.customFieldMappingsJson = argv.mappings;
38
+ if (!Object.keys(body).length) throw new Error('Pass --activity and/or --mappings (omitted fields are left unchanged).');
39
+ console.log(JSON.stringify(await request('PUT', `${base(argv.config)}/${argv.team}`, body), null, 2));
40
+ }),
41
+ })
42
+ .demandCommand(1, 'Specify a subcommand: team-overrides'),
43
+ handler: () => {},
44
+ };
@@ -0,0 +1,27 @@
1
+ import { request } from '../api.js';
2
+ import { runCommand } from '../run.js';
3
+
4
+ // velaro image-analysis limits --site N
5
+ // Shows the Image Analysis (analyze_image) add-on cap and usage for any site (Velaro admin only).
6
+ // To change the cap: velaro support set-limit --site N --key MaxImageAnalysesPerMonth --value 500
7
+ export const imageAnalysisCommand = {
8
+ command: 'image-analysis <subcommand>',
9
+ describe: 'Image Analysis add-on limits and usage (Velaro admin only)',
10
+ builder: yargs => yargs
11
+ .command({
12
+ command: 'limits',
13
+ describe: 'Show monthly cap, usage this month and per-chat cap for a site',
14
+ builder: y => y.option('site', { type: 'number', demandOption: true, describe: 'Site ID' }),
15
+ handler: runCommand(async argv => {
16
+ const d = await request('GET', `/ImageAnalysisLimits/superadmin?siteId=${argv.site}`);
17
+ const ent = d.entitled === null ? 'unknown' : d.entitled ? 'yes' : 'no';
18
+ console.log(`\nSite ${argv.site}: Image Analysis`);
19
+ console.log(` Entitled: ${ent}`);
20
+ console.log(` Monthly cap: ${d.monthlyCap} (effective ${d.effectiveMonthlyCap}, hard cap, no automatic overage)`);
21
+ console.log(` Used this month: ${d.usedThisMonth === null ? 'unknown' : d.usedThisMonth}`);
22
+ console.log(` Per-chat cap: ${d.perConversationCap}`);
23
+ console.log(` Extra block size: ${d.blockSize}\n`);
24
+ }),
25
+ })
26
+ .demandCommand(1),
27
+ };
@@ -108,9 +108,10 @@ export const qualityCommand = {
108
108
  export const chatIntelligenceThemesCommand = {
109
109
  command: 'chat-intelligence',
110
110
  describe: 'Chat Intelligence themes, optionally per website/team',
111
- builder: (y) => scope(y.option('from', { type: 'string' }).option('to', { type: 'string' }).option('group-by', { type: 'string', default: 'month' })),
111
+ builder: (y) => scope(y.option('from', { type: 'string' }).option('to', { type: 'string' }).option('group-by', { type: 'string', default: 'month' }).option('include-secondary', { type: 'boolean', default: false, describe: 'Also show secondary topics (needs the site multi-topic setting on)' })),
112
112
  handler: runCommand(async (a) => {
113
113
  const qs = new URLSearchParams({ groupBy: a.groupBy });
114
+ if (a.includeSecondary) qs.set('includeSecondary', 'true');
114
115
  if (a.from) qs.set('startDate', a.from);
115
116
  if (a.to) qs.set('endDate', a.to);
116
117
  if (a.deployment != null) qs.set('deploymentId', String(a.deployment));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "1.4.98",
3
+ "version": "1.4.100",
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": {