@velaro/cli 1.4.79 → 1.4.81

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
@@ -92,6 +92,7 @@ import { supportCommand } from '../lib/commands/support.js';
92
92
  import { routingCommand } from '../lib/commands/routing.js';
93
93
  import { diagnosticsCommand } from '../lib/commands/diagnostics.js';
94
94
  import { aiSpendCommand } from '../lib/commands/ai-spend.js';
95
+ import { statusPageCommand } from '../lib/commands/status-page.js';
95
96
  import { emailInboxCommand } from '../lib/commands/email-inbox.js';
96
97
  import { callbackCommand } from '../lib/commands/callback.js';
97
98
  import { caseCommand } from '../lib/commands/case.js';
@@ -209,6 +210,7 @@ await yargs(hideBin(process.argv))
209
210
  .command(routingCommand)
210
211
  .command(diagnosticsCommand)
211
212
  .command(aiSpendCommand)
213
+ .command(statusPageCommand)
212
214
  .command(emailInboxCommand)
213
215
  .command(callbackCommand)
214
216
  .command(caseCommand)
@@ -106,6 +106,48 @@ const topicsCommand = {
106
106
  }),
107
107
  };
108
108
 
109
+ const categoriesCommand = {
110
+ command: 'categories',
111
+ describe: 'List KB categories (topics hang under a category)',
112
+ handler: runCommand(async () => {
113
+ const data = await adminRequest('GET', '/api/kb/categories');
114
+ const cats = data?.categories ?? [];
115
+ if (!cats.length) { console.log('No categories found.'); return; }
116
+ for (const c of cats) console.log(` [${c.id}] ${c.name} (priority ${c.displayPriority})`);
117
+ }),
118
+ };
119
+
120
+ const categoryCreateCommand = {
121
+ command: 'category-create',
122
+ describe: 'Create a KB category (seed the v20 topic tree before pushing articles)',
123
+ builder: (y) =>
124
+ y
125
+ .option('name', { type: 'string', demandOption: true, describe: 'Category name' })
126
+ .option('priority', { type: 'number', default: 0, describe: 'Display priority (lower sorts first)' })
127
+ .option('icon', { type: 'string', describe: 'Optional icon name' }),
128
+ handler: runCommand(async (argv) => {
129
+ const c = await adminRequest('POST', '/api/kb/categories', { name: argv.name, displayPriority: argv.priority, icon: argv.icon });
130
+ console.log(`Category created: [${c.id}] ${c.name}`);
131
+ }),
132
+ };
133
+
134
+ const topicCreateCommand = {
135
+ command: 'topic-create',
136
+ describe: 'Create a KB topic under a category and print its ID for `article push --topic-id`',
137
+ builder: (y) =>
138
+ y
139
+ .option('name', { type: 'string', demandOption: true, describe: 'Topic name' })
140
+ .option('slug', { type: 'string', demandOption: true, describe: 'URL slug' })
141
+ .option('category-id', { type: 'string', describe: 'Category ID from `kb article categories`' })
142
+ .option('priority', { type: 'number', default: 0, describe: 'Display priority (lower sorts first)' }),
143
+ handler: runCommand(async (argv) => {
144
+ const t = await adminRequest('POST', '/api/kb/topics', {
145
+ name: argv.name, slug: argv.slug, categoryId: argv.categoryId, displayPriority: argv.priority,
146
+ });
147
+ console.log(`Topic created: [${t.id}] ${t.name} (slug ${t.slug})`);
148
+ }),
149
+ };
150
+
109
151
  // ── push ──────────────────────────────────────────────────────────────────────
110
152
 
111
153
  const pushCommand = {
@@ -500,6 +542,9 @@ export const articleCommand = {
500
542
  .command(listCommand)
501
543
  .command(getCommand)
502
544
  .command(topicsCommand)
545
+ .command(categoriesCommand)
546
+ .command(categoryCreateCommand)
547
+ .command(topicCreateCommand)
503
548
  .command(pushCommand)
504
549
  .command(previewCommand)
505
550
  .command(deleteCommand)
@@ -96,6 +96,18 @@ async function backfill() {
96
96
  console.log(`✅ Backfill: ${result.inserted} inserted, ${result.skipped} already existed, ${result.unknownPlan} skipped (unknown plan).`);
97
97
  }
98
98
 
99
+ async function backfillSapTiers(args) {
100
+ const result = await request('POST', `Entitlements/admin/backfill-sap-tiers?dryRun=${args.apply ? 'false' : 'true'}`);
101
+ if (result.error) { console.error(`❌ ${result.error}`); process.exit(1); }
102
+ console.log(`${args.apply ? '✅ APPLIED' : '🔍 DRY RUN'}: ${JSON.stringify(result, null, 2)}`);
103
+ }
104
+
105
+ async function backfillLegacyWriteTiers(args) {
106
+ const result = await request('POST', `Entitlements/admin/backfill-legacy-write-tiers?dryRun=${args.apply ? 'false' : 'true'}`);
107
+ if (result.error) { console.error(`❌ ${result.error}`); process.exit(1); }
108
+ console.log(`${args.apply ? '✅ APPLIED' : '🔍 DRY RUN'}: ${JSON.stringify(result, null, 2)}`);
109
+ }
110
+
99
111
  async function reconcile(args) {
100
112
  if (!args.site) {
101
113
  console.error('Usage: velaro entitlement reconcile --site 1032');
@@ -175,6 +187,8 @@ export function entitlementCommand(yargs) {
175
187
  , a => removeOverride(a))
176
188
  .command('seed', 'Seed the EntitlementFeature registry (idempotent, superadmin only)', () => {}, () => seed())
177
189
  .command('seed-plans', 'Seed PlanEntitlement rows from live PackageVersions in messaging DB (idempotent, superadmin only)', () => {}, () => seedPlans())
190
+ .command('backfill-sap-tiers', 'Grant the four SAP add-on packages to existing SAP sites (dry run unless --apply; superadmin only)', (y) => y.option('apply', { type: 'boolean', default: false, describe: 'Write the grants (default is a dry run)' }), (a) => backfillSapTiers(a))
191
+ .command('backfill-legacy-write-tiers', 'Grant EnableServiceNowWrite/AccelaWrite/QuickBooksWrite to existing sites after the OR-gate sweep (dry run unless --apply; superadmin only)', (y) => y.option('apply', { type: 'boolean', default: false, describe: 'Write the grants (default is a dry run)' }), (a) => backfillLegacyWriteTiers(a))
178
192
  .command('backfill', 'Backfill SiteSubscription rows from active Subscription rows in messaging DB (idempotent, superadmin only)', () => {}, () => backfill())
179
193
  .command('reconcile', 'Diff Subscription.cs vs Entitlements API for a site — zero deltas = ready for cutover', y => y
180
194
  .option('site', { type: 'number', demandOption: true, desc: 'Site ID' })
@@ -195,5 +209,5 @@ export function entitlementCommand(yargs) {
195
209
  .option('key', { type: 'string', demandOption: true, desc: 'Feature key, e.g. EnableIvr' })
196
210
  .option('rollback', { type: 'boolean', desc: 'Pass to roll back this flag to Subscription.cs (default: go live)' })
197
211
  , a => setCutover(a))
198
- .demandCommand(1, 'Specify a subcommand: get | list-features | set-override | remove-override | seed | seed-plans | backfill | reconcile | comp | set-lock | list-cutover | set-cutover');
212
+ .demandCommand(1, 'Specify a subcommand: get | list-features | set-override | remove-override | seed | seed-plans | backfill | backfill-sap-tiers | backfill-legacy-write-tiers | reconcile | comp | set-lock | list-cutover | set-cutover');
199
213
  }
@@ -0,0 +1,95 @@
1
+ import { messagingRequest } from '../api.js';
2
+
3
+ const COMPONENTS = 'Messaging, Voice & Calls, AI Assistant, Database, API, Integrations, Email Campaigns, Velaro Sign, Velaro Forms, Agent Console, Core API, Chat Engagement';
4
+ const TOKENS = 'AffectedServices is a comma-separated list of component names (' + COMPONENTS + ') plus optional uptime tokens: "counts-uptime" forces the incident to count against uptime, "no-uptime-impact" forces it not to. Default: only a core chat outage counts.';
5
+
6
+ const listCommand = {
7
+ command: 'list',
8
+ describe: 'List status incidents (SuperAdmin, velaro-messaging GET /superadmin/status-incidents)',
9
+ builder: (y) => y.option('filter', { type: 'string', default: 'active', choices: ['active', 'resolved', 'all'] }).option('json', { type: 'boolean', default: false }),
10
+ handler: async (argv) => {
11
+ const rows = await messagingRequest('GET', `/superadmin/status-incidents?filter=${argv.filter}`);
12
+ if (argv.json) { console.log(JSON.stringify(rows, null, 2)); return; }
13
+ for (const r of rows ?? []) console.log(`#${r.id} ${r.status} ${r.incidentType}/${r.severity} ${r.title} [${r.affectedServices ?? ''}]`);
14
+ },
15
+ };
16
+
17
+ const createCommand = {
18
+ command: 'create',
19
+ describe: 'Create a status incident. ' + TOKENS,
20
+ builder: (y) => y
21
+ .option('title', { type: 'string', demandOption: true })
22
+ .option('description', { type: 'string', default: '' })
23
+ .option('status', { type: 'string', default: 'Investigating', describe: 'Investigating, Identified, Monitoring, Resolved' })
24
+ .option('type', { type: 'string', default: 'Incident', describe: 'Incident or Maintenance' })
25
+ .option('severity', { type: 'string', default: 'Minor', describe: 'Minor or Major' })
26
+ .option('services', { type: 'string', default: '', describe: 'AffectedServices CSV. ' + TOKENS })
27
+ .option('regions', { type: 'string', default: '' })
28
+ .option('exclude-from-uptime', { type: 'boolean', default: false })
29
+ .option('hidden', { type: 'boolean', default: false, describe: 'Hide from the public page' }),
30
+ handler: async (argv) => {
31
+ const r = await messagingRequest('POST', '/superadmin/status-incidents', {
32
+ title: argv.title, description: argv.description, status: argv.status, incidentType: argv.type, severity: argv.severity,
33
+ affectedServices: argv.services, affectedRegions: argv.regions, excludeFromUptime: argv.excludeFromUptime, isVisibleToCustomers: !argv.hidden,
34
+ });
35
+ console.log(`Created incident #${r.id}: ${r.title}`);
36
+ },
37
+ };
38
+
39
+ const updateCommand = {
40
+ command: 'update <id>',
41
+ describe: 'Update a status incident (pass every field you want kept). ' + TOKENS,
42
+ builder: (y) => y
43
+ .positional('id', { type: 'number' })
44
+ .option('title', { type: 'string' }).option('description', { type: 'string' })
45
+ .option('status', { type: 'string' }).option('type', { type: 'string' }).option('severity', { type: 'string' })
46
+ .option('services', { type: 'string', describe: 'AffectedServices CSV. ' + TOKENS })
47
+ .option('regions', { type: 'string' })
48
+ .option('end-time', { type: 'string', describe: 'ISO timestamp the incident ended' })
49
+ .option('exclude-from-uptime', { type: 'boolean' })
50
+ .option('hidden', { type: 'boolean' }),
51
+ handler: async (argv) => {
52
+ const body = {
53
+ title: argv.title, description: argv.description, status: argv.status, incidentType: argv.type, severity: argv.severity,
54
+ affectedServices: argv.services, affectedRegions: argv.regions, endTime: argv.endTime,
55
+ excludeFromUptime: argv.excludeFromUptime ?? false, isVisibleToCustomers: argv.hidden === undefined ? true : !argv.hidden,
56
+ };
57
+ const r = await messagingRequest('PUT', `/superadmin/status-incidents/${argv.id}`, body);
58
+ console.log(`Updated incident #${r?.id ?? argv.id}`);
59
+ },
60
+ };
61
+
62
+ const deleteCommand = {
63
+ command: 'delete <id>',
64
+ describe: 'Delete a status incident or clear an override',
65
+ builder: (y) => y.positional('id', { type: 'number' }),
66
+ handler: async (argv) => {
67
+ await messagingRequest('DELETE', `/superadmin/status-incidents/${argv.id}`);
68
+ console.log(`Deleted incident #${argv.id}`);
69
+ },
70
+ };
71
+
72
+ const overrideCommand = {
73
+ command: 'override',
74
+ describe: 'Force a public status page component to a status (POST /superadmin/status-overrides). Components: ' + COMPONENTS + '. Clear with: velaro status-page delete <id>',
75
+ builder: (y) => y
76
+ .option('component', { type: 'string', demandOption: true, describe: COMPONENTS })
77
+ .option('status', { type: 'string', demandOption: true, choices: ['operational', 'degraded', 'outage', 'maintenance'] })
78
+ .option('note', { type: 'string', default: '' })
79
+ .option('expires-at', { type: 'string', describe: 'ISO timestamp the override auto-clears' })
80
+ .option('counts-against-uptime', { type: 'boolean', describe: 'true adds counts-uptime, false adds no-uptime-impact; omit for default' }),
81
+ handler: async (argv) => {
82
+ const r = await messagingRequest('POST', '/superadmin/status-overrides', {
83
+ component: argv.component, status: argv.status, note: argv.note, expiresAt: argv.expiresAt ?? null, countsAgainstUptime: argv.countsAgainstUptime ?? null,
84
+ });
85
+ console.log(`Override #${r.id} created: ${r.title}`);
86
+ },
87
+ };
88
+
89
+ export const statusPageCommand = {
90
+ command: 'status-page <subcommand>',
91
+ describe: 'Public status page incidents and component overrides (SuperAdmin only)',
92
+ builder: (y) => y.command(listCommand).command(createCommand).command(updateCommand).command(deleteCommand).command(overrideCommand)
93
+ .demandCommand(1, 'Specify a subcommand: list, create, update, delete, override').strict(),
94
+ handler: () => {},
95
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "1.4.79",
3
+ "version": "1.4.81",
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": {