@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.
- package/README.md +161 -138
- package/bin/velaro.js +177 -62
- package/lib/api.js +91 -52
- package/lib/api.test.js +46 -0
- package/lib/banner.js +76 -0
- package/lib/commands/activity.js +133 -0
- package/lib/commands/acuity.js +66 -0
- package/lib/commands/agent.js +204 -50
- package/lib/commands/ai-config.js +193 -0
- package/lib/commands/ai-models.js +159 -0
- package/lib/commands/appointments.js +198 -0
- package/lib/commands/article.js +668 -388
- package/lib/commands/automation-draft.js +134 -0
- package/lib/commands/avatar.js +75 -0
- package/lib/commands/bigcommerce.js +50 -0
- package/lib/commands/billing-contacts.js +62 -0
- package/lib/commands/billing-email-preference.js +64 -0
- package/lib/commands/billing-subscription.js +265 -0
- package/lib/commands/billing.js +138 -0
- package/lib/commands/bot.js +141 -137
- package/lib/commands/bundle.js +168 -0
- package/lib/commands/calendly.js +62 -0
- package/lib/commands/callback.js +125 -0
- package/lib/commands/callrail.js +88 -0
- package/lib/commands/campaigns.js +44 -0
- package/lib/commands/case.js +102 -0
- package/lib/commands/check.js +163 -163
- package/lib/commands/compliance.js +229 -0
- package/lib/commands/conversation-efficiency.js +178 -0
- package/lib/commands/copilotstudio.js +114 -0
- package/lib/commands/coupon-grant.js +192 -0
- package/lib/commands/db.js +101 -0
- package/lib/commands/deployment.js +107 -107
- package/lib/commands/diagnostics.js +298 -0
- package/lib/commands/email-campaign.js +47 -0
- package/lib/commands/email-inbox.js +88 -0
- package/lib/commands/entitlement.js +176 -0
- package/lib/commands/env.js +45 -45
- package/lib/commands/feature-discovery.js +40 -0
- package/lib/commands/focus.js +278 -0
- package/lib/commands/index.js +38 -5
- package/lib/commands/ingest.js +31 -31
- package/lib/commands/inline-widget-config.js +126 -0
- package/lib/commands/integration.js +93 -0
- package/lib/commands/kb.js +450 -309
- package/lib/commands/login.js +86 -86
- package/lib/commands/logs.js +680 -0
- package/lib/commands/magento.js +210 -0
- package/lib/commands/mcp-key.js +188 -159
- package/lib/commands/migrate.js +134 -0
- package/lib/commands/migration-status.js +66 -0
- package/lib/commands/monday.js +137 -0
- package/lib/commands/netsuite.js +87 -0
- package/lib/commands/notifications.js +63 -0
- package/lib/commands/notion.js +70 -0
- package/lib/commands/ops.js +267 -173
- package/lib/commands/payment-recovery.js +170 -0
- package/lib/commands/pickup.js +172 -0
- package/lib/commands/pricing.js +132 -0
- package/lib/commands/product.js +55 -0
- package/lib/commands/recruiting.js +374 -0
- package/lib/commands/report.js +462 -0
- package/lib/commands/routing.js +304 -0
- package/lib/commands/rule.js +85 -85
- package/lib/commands/sharepoint.js +167 -0
- package/lib/commands/site-provision.js +68 -0
- package/lib/commands/site.js +62 -62
- package/lib/commands/sitesync.js +158 -0
- package/lib/commands/slack.js +64 -0
- package/lib/commands/squarespace.js +108 -0
- package/lib/commands/status.js +24 -24
- package/lib/commands/subscription.js +43 -0
- package/lib/commands/support.js +128 -0
- package/lib/commands/survey.js +216 -0
- package/lib/commands/team.js +144 -144
- package/lib/commands/teams-phone.js +131 -0
- package/lib/commands/teams.js +106 -0
- package/lib/commands/telephony.js +99 -0
- package/lib/commands/update.js +47 -47
- package/lib/commands/webflow.js +128 -0
- package/lib/commands/whoami.js +25 -22
- package/lib/commands/widget-container.js +152 -0
- package/lib/commands/woocommerce.js +240 -0
- package/lib/commands/workflow.js +233 -98
- package/lib/config.js +85 -83
- package/lib/kb-screenshot.js +320 -0
- package/lib/migrations/amscro.json +72 -0
- package/lib/migrations/azenta.json +68 -0
- package/lib/migrations/bluefire.json +49 -0
- package/lib/migrations/donaldson.json +75 -0
- package/lib/oauth.js +149 -135
- package/lib/run.js +21 -16
- package/lib/sharepoint-auth.js +138 -0
- package/lib/subscription.js +41 -39
- package/lib/track.js +35 -35
- package/lib/update-check.js +64 -64
- package/package.json +34 -19
- package/scripts/postinstall.js +12 -0
package/lib/commands/kb.js
CHANGED
|
@@ -1,309 +1,450 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import { basename } from 'path';
|
|
3
|
-
import { get, post, del } from '../api.js';
|
|
4
|
-
import { requireFeature } from '../subscription.js';
|
|
5
|
-
import { runCommand } from '../run.js';
|
|
6
|
-
import { articleCommand } from './article.js';
|
|
7
|
-
|
|
8
|
-
export const kbCommand = {
|
|
9
|
-
command: 'kb <subcommand>',
|
|
10
|
-
describe: 'Manage knowledge base — articles, Q&A pairs, bot overrides, and custom content',
|
|
11
|
-
builder: (yargs) =>
|
|
12
|
-
yargs
|
|
13
|
-
.command(articleCommand)
|
|
14
|
-
.command(qnaCommand)
|
|
15
|
-
.command(overrideCommand)
|
|
16
|
-
.command(contentCommand)
|
|
17
|
-
.command(reindexCommand)
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
.command({
|
|
293
|
-
command: '
|
|
294
|
-
describe: '
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
await
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { basename } from 'path';
|
|
3
|
+
import { get, post, del, messagingGet, messagingPost, messagingDel } from '../api.js';
|
|
4
|
+
import { requireFeature } from '../subscription.js';
|
|
5
|
+
import { runCommand } from '../run.js';
|
|
6
|
+
import { articleCommand } from './article.js';
|
|
7
|
+
|
|
8
|
+
export const kbCommand = {
|
|
9
|
+
command: 'kb <subcommand>',
|
|
10
|
+
describe: 'Manage knowledge base — articles, Q&A pairs, bot overrides, and custom content',
|
|
11
|
+
builder: (yargs) =>
|
|
12
|
+
yargs
|
|
13
|
+
.command(articleCommand)
|
|
14
|
+
.command(qnaCommand)
|
|
15
|
+
.command(overrideCommand)
|
|
16
|
+
.command(contentCommand)
|
|
17
|
+
.command(reindexCommand)
|
|
18
|
+
.command(settingsCommand)
|
|
19
|
+
.command(statusCommand)
|
|
20
|
+
.command(swapConnectionCommand)
|
|
21
|
+
.command(tenantCommand)
|
|
22
|
+
.demandCommand(1, 'Specify a subcommand: article, qna, override, content, reindex, settings, status, swap-connection, tenant'),
|
|
23
|
+
handler: () => {},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
27
|
+
// Tenant — the KB tenant linked to the current site (one per site, created once).
|
|
28
|
+
// Built 2026-08-25 after a KB tenant-creation 500 on staging had to be diagnosed
|
|
29
|
+
// entirely via ad-hoc curl + browser — there was no CLI way to even check whether
|
|
30
|
+
// a site already has a linked tenant before calling `kb tenant create`.
|
|
31
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const tenantCommand = {
|
|
34
|
+
command: 'tenant <subcommand>',
|
|
35
|
+
describe: 'Manage the KB tenant linked to the current site (one per site)',
|
|
36
|
+
builder: (yargs) =>
|
|
37
|
+
yargs
|
|
38
|
+
.command({
|
|
39
|
+
command: 'current',
|
|
40
|
+
describe: 'Show the KB tenant linked to the current site, if any',
|
|
41
|
+
handler: runCommand(async () => {
|
|
42
|
+
const t = await get('/api/kb/tenants/current');
|
|
43
|
+
console.log(JSON.stringify(t, null, 2));
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
.command({
|
|
47
|
+
command: 'create',
|
|
48
|
+
describe: 'Create a new KB tenant for the current site (fails with a clear error if one already exists)',
|
|
49
|
+
builder: (y) =>
|
|
50
|
+
y.option('company-name', { type: 'string', demandOption: true, describe: 'Company name shown as the KB title' }),
|
|
51
|
+
handler: runCommand(async (argv) => {
|
|
52
|
+
const result = await post('/api/kb/tenants', { companyName: argv['company-name'] });
|
|
53
|
+
console.log(JSON.stringify(result, null, 2));
|
|
54
|
+
}),
|
|
55
|
+
})
|
|
56
|
+
.command({
|
|
57
|
+
command: 'unlink',
|
|
58
|
+
describe: 'Unlink the current site from its KB tenant WITHOUT deleting any data (company name, subdomain, articles, topics all preserved) — use before `kb tenant create` when the linked tenant is stale/missing from the connected KB database. Reversible via a Velaro-admin relink.',
|
|
59
|
+
handler: runCommand(async () => {
|
|
60
|
+
const result = await del('/api/kb/tenants/unlink');
|
|
61
|
+
console.log(JSON.stringify(result, null, 2));
|
|
62
|
+
}),
|
|
63
|
+
})
|
|
64
|
+
.demandCommand(1, 'Specify: current, create, or unlink'),
|
|
65
|
+
handler: () => {},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
// Reindex — re-embed all KB articles with the current embedding model
|
|
70
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const reindexCommand = {
|
|
73
|
+
command: 'reindex',
|
|
74
|
+
describe: 'Re-embed KB articles or scraper/ingestion content (use after embedding model upgrade)',
|
|
75
|
+
builder: (y) =>
|
|
76
|
+
y
|
|
77
|
+
.option('all', {
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
describe: 'Reindex ALL sites (Velaro staff only)',
|
|
80
|
+
default: false,
|
|
81
|
+
})
|
|
82
|
+
.option('site', {
|
|
83
|
+
type: 'number',
|
|
84
|
+
describe: 'Reindex a specific site by ID (Velaro staff only)',
|
|
85
|
+
})
|
|
86
|
+
.option('ingestion', {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
describe: 'Reindex scraper/ingestion Azure Search indexers instead of KB articles',
|
|
89
|
+
default: false,
|
|
90
|
+
}),
|
|
91
|
+
handler: runCommand(async (argv) => {
|
|
92
|
+
if (argv.ingestion) {
|
|
93
|
+
if (argv.all) {
|
|
94
|
+
console.log('Starting bulk ingestion reindex for all sites — runs in background.');
|
|
95
|
+
console.log('Watch server logs ([ReindexAllIngestion]) for progress.\n');
|
|
96
|
+
const result = await post('/DatabaseTool/ReindexAllIngestionIndexes', {});
|
|
97
|
+
console.log(result.message ?? 'Queued.');
|
|
98
|
+
} else if (argv.site) {
|
|
99
|
+
console.log(`Reindexing scraper/ingestion index for site ${argv.site}…`);
|
|
100
|
+
const result = await post(`/DatabaseTool/ReindexIngestionIndex/${argv.site}`, {});
|
|
101
|
+
if (result.success) {
|
|
102
|
+
console.log(`✅ Triggered ${result.indexersRun} indexer(s) — re-processing in background.`);
|
|
103
|
+
} else {
|
|
104
|
+
console.error(`❌ Failed: ${result.message}`);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.error('--ingestion requires --site <id> or --all (Velaro staff only)');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
} else if (argv.all) {
|
|
111
|
+
console.log('Starting bulk KB reindex for all sites — this runs in the background.');
|
|
112
|
+
console.log('Watch server logs ([ReindexAllKb]) for progress.\n');
|
|
113
|
+
const result = await post('/DatabaseTool/ReindexAllKb', {});
|
|
114
|
+
console.log(result.message ?? 'Queued.');
|
|
115
|
+
} else if (argv.site) {
|
|
116
|
+
console.log(`Reindexing KB articles for site ${argv.site}…`);
|
|
117
|
+
const result = await messagingPost(`/Scraper/superadmin/kb/reindex/${argv.site}`, {});
|
|
118
|
+
if (result?.queued) {
|
|
119
|
+
console.log(`✅ Queued — full re-ingest running in background for site ${argv.site}.`);
|
|
120
|
+
} else {
|
|
121
|
+
console.error(`❌ Failed: ${result?.error ?? 'unknown error'}`);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.log("Reindexing your site's KB articles\u2026");
|
|
125
|
+
const result = await post('/KBSearchIndex/reindex', {});
|
|
126
|
+
if (result.success === false) {
|
|
127
|
+
console.error(`❌ Failed: ${result.message}`);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(`✅ Done — ${result.articles ?? result.count ?? 'all'} articles reindexed.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
136
|
+
// Status — which physical DB this environment's KB is actually connected to
|
|
137
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
const statusCommand = {
|
|
140
|
+
command: 'status',
|
|
141
|
+
describe: 'Show KB connection diagnostics: which server/database this environment is actually using (KBConnectionHolder can point at either the primary or alternate DB — this shows which one is live right now)',
|
|
142
|
+
handler: runCommand(async () => {
|
|
143
|
+
const s = await get('/api/kb/status');
|
|
144
|
+
console.log(JSON.stringify(s, null, 2));
|
|
145
|
+
}),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const swapConnectionCommand = {
|
|
149
|
+
command: 'swap-connection',
|
|
150
|
+
describe: 'Toggle which physical database this environment\'s KB is connected to (primary vs. alternate, per KBConnectionHolder). Built 2026-08-18 after staging was found silently connected to PRODUCTION\'s KB database (KnowledgeBaseConnectionString, not _Staging) with no CLI/API way to check or fix it except a raw REST call.',
|
|
151
|
+
builder: (y) =>
|
|
152
|
+
y
|
|
153
|
+
.option('use-alternate', { type: 'boolean', demandOption: true, describe: 'true = use the alternate (KnowledgeBaseConnectionString_Staging) DB; false = use primary' })
|
|
154
|
+
.option('yes', { type: 'boolean', default: false, describe: 'Skip the confirmation prompt (for scripting)' }),
|
|
155
|
+
handler: runCommand(async (argv) => {
|
|
156
|
+
const before = await get('/api/kb/status');
|
|
157
|
+
console.log(`Currently connected to: ${before.server} / ${before.database} (alternate: ${before.isAlternate})`);
|
|
158
|
+
|
|
159
|
+
if (!argv.yes) {
|
|
160
|
+
console.log(`\nAbout to switch to ${argv['use-alternate'] ? 'ALTERNATE' : 'PRIMARY'}. Re-run with --yes to confirm.`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const result = await post('/DatabaseTool/KBSwapConnection', { useAlternate: argv['use-alternate'] });
|
|
165
|
+
console.log(`✅ Switched. Now connected to: ${result.current.server} / ${result.current.database} (alternate: ${result.current.isAlternate})`);
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
170
|
+
// Settings — branding, domain, layout, SEO/analytics
|
|
171
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
const settingsCommand = {
|
|
174
|
+
command: 'settings <subcommand>',
|
|
175
|
+
describe: 'View or update Knowledge Base settings (branding, domain, layout, SEO/analytics)',
|
|
176
|
+
builder: (yargs) =>
|
|
177
|
+
yargs
|
|
178
|
+
.command({
|
|
179
|
+
command: 'get',
|
|
180
|
+
describe: 'Show current KB settings',
|
|
181
|
+
handler: runCommand(async () => {
|
|
182
|
+
const s = await get('/api/kb/settings');
|
|
183
|
+
console.log(JSON.stringify(s, null, 2));
|
|
184
|
+
}),
|
|
185
|
+
})
|
|
186
|
+
.command({
|
|
187
|
+
command: 'set',
|
|
188
|
+
describe: 'Update KB settings. Fetches current settings first and merges — unspecified flags keep their current value.',
|
|
189
|
+
builder: (y) =>
|
|
190
|
+
y
|
|
191
|
+
.option('theme-color', { type: 'string', describe: 'Hex color, e.g. "#4c75ab"' })
|
|
192
|
+
.option('theme-preset', { type: 'string', describe: 'Curated preset id (informational only — theme-color is what renders)' })
|
|
193
|
+
.option('header-text', { type: 'string' })
|
|
194
|
+
.option('domain', { type: 'string', describe: 'Custom domain, e.g. help.acme.com. Pass "" to clear.' })
|
|
195
|
+
.option('topic-layout', { type: 'string', choices: ['grid', 'list'] })
|
|
196
|
+
.option('article-layout', { type: 'string', choices: ['table', 'cards'] })
|
|
197
|
+
.option('meta-description', { type: 'string', describe: 'Default SEO meta description (max 300 chars)' })
|
|
198
|
+
.option('ga4-id', { type: 'string', describe: 'Google Analytics 4 Measurement ID, format G-XXXXXXXXXX' }),
|
|
199
|
+
handler: runCommand(async (argv) => {
|
|
200
|
+
const current = await get('/api/kb/settings');
|
|
201
|
+
const merged = {
|
|
202
|
+
companyName: current.companyName ?? '',
|
|
203
|
+
themeColor: argv['theme-color'] ?? current.themeColor ?? '',
|
|
204
|
+
themePreset: argv['theme-preset'] ?? current.themePreset ?? null,
|
|
205
|
+
headerText: argv['header-text'] ?? current.headerText ?? '',
|
|
206
|
+
returnLinkText: current.returnLinkText ?? '',
|
|
207
|
+
returnLinkUrl: current.returnLinkUrl ?? '',
|
|
208
|
+
logoUrl: current.logoUrl ?? '',
|
|
209
|
+
domain: argv.domain ?? current.domain ?? '',
|
|
210
|
+
topicLayout: argv['topic-layout'] ?? current.topicLayout ?? 'grid',
|
|
211
|
+
articleLayout: argv['article-layout'] ?? current.articleLayout ?? 'table',
|
|
212
|
+
showLastUpdated: current.showLastUpdated ?? true,
|
|
213
|
+
enableLiveChat: current.enableLiveChat ?? false,
|
|
214
|
+
liveChatGroupId: current.liveChatGroupId ?? 0,
|
|
215
|
+
enableKbBot: current.enableKbBot ?? false,
|
|
216
|
+
kbChatWindowId: current.kbChatWindowId ?? 0,
|
|
217
|
+
seoMetaDescription: argv['meta-description'] ?? current.seoMetaDescription ?? '',
|
|
218
|
+
ga4MeasurementId: argv['ga4-id'] ?? current.ga4MeasurementId ?? '',
|
|
219
|
+
};
|
|
220
|
+
await post('/api/kb/settings', merged);
|
|
221
|
+
console.log('✅ KB settings updated.');
|
|
222
|
+
}),
|
|
223
|
+
})
|
|
224
|
+
.demandCommand(1, 'Specify: get or set'),
|
|
225
|
+
handler: () => {},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
// Q&A
|
|
230
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
const qnaCommand = {
|
|
233
|
+
command: 'qna <subcommand>',
|
|
234
|
+
describe: 'Manage Q&A knowledge pairs',
|
|
235
|
+
builder: (yargs) =>
|
|
236
|
+
yargs
|
|
237
|
+
.command({
|
|
238
|
+
command: 'list',
|
|
239
|
+
describe: 'List Q&A pairs (site-wide -- BotQnA has no per-bot scoping)',
|
|
240
|
+
handler: runCommand(async () => {
|
|
241
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
242
|
+
const items = await messagingGet('/BotQnA');
|
|
243
|
+
if (!items?.length) { console.log('No Q&A pairs found.'); return; }
|
|
244
|
+
|
|
245
|
+
console.log(`\nFound ${items.length} Q&A pair(s):\n`);
|
|
246
|
+
for (const item of items) {
|
|
247
|
+
console.log(` [${item.id}] Q: ${truncate(item.question, 70)}`);
|
|
248
|
+
console.log(` A: ${truncate(item.answer, 70)}\n`);
|
|
249
|
+
}
|
|
250
|
+
}),
|
|
251
|
+
})
|
|
252
|
+
.command({
|
|
253
|
+
command: 'add',
|
|
254
|
+
describe: 'Add a Q&A pair',
|
|
255
|
+
builder: (y) =>
|
|
256
|
+
y
|
|
257
|
+
.option('question', { type: 'string', demandOption: true, describe: 'Question text' })
|
|
258
|
+
.option('answer', { type: 'string', demandOption: true, describe: 'Answer text' }),
|
|
259
|
+
handler: runCommand(async (argv) => {
|
|
260
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
261
|
+
// BotQnAItem has no per-bot column -- pairs are site-wide, not scoped to one bot.
|
|
262
|
+
const result = await messagingPost('/BotQnA', {
|
|
263
|
+
question: argv.question,
|
|
264
|
+
answer: argv.answer,
|
|
265
|
+
});
|
|
266
|
+
console.log(`Q&A pair added: [${result.id}]`);
|
|
267
|
+
}),
|
|
268
|
+
})
|
|
269
|
+
.command({
|
|
270
|
+
command: 'delete <id>',
|
|
271
|
+
describe: 'Delete a Q&A pair',
|
|
272
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Q&A pair ID' }),
|
|
273
|
+
handler: runCommand(async (argv) => {
|
|
274
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
275
|
+
await messagingDel(`/BotQnA/${argv.id}`);
|
|
276
|
+
console.log(`Q&A pair ${argv.id} deleted.`);
|
|
277
|
+
}),
|
|
278
|
+
})
|
|
279
|
+
.demandCommand(1, 'Specify a subcommand: list, add, delete'),
|
|
280
|
+
handler: () => {},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
284
|
+
// Overrides — authoritative facts that take priority over KB search
|
|
285
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
const overrideCommand = {
|
|
288
|
+
command: 'override <subcommand>',
|
|
289
|
+
describe: 'Manage bot knowledge overrides (authoritative facts)',
|
|
290
|
+
builder: (yargs) =>
|
|
291
|
+
yargs
|
|
292
|
+
.command({
|
|
293
|
+
command: 'list',
|
|
294
|
+
describe: 'List knowledge overrides',
|
|
295
|
+
handler: runCommand(async () => {
|
|
296
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
297
|
+
const items = await get('/Ticket/knowledge-overrides');
|
|
298
|
+
if (!items?.length) { console.log('No overrides found.'); return; }
|
|
299
|
+
|
|
300
|
+
console.log(`\nFound ${items.length} override(s):\n`);
|
|
301
|
+
for (const item of items) {
|
|
302
|
+
const status = item.isActive ? 'active ' : 'inactive';
|
|
303
|
+
const expiry = item.expiresAt ? ` expires ${new Date(item.expiresAt).toLocaleDateString()}` : '';
|
|
304
|
+
console.log(` [${item.id}] ${status} ${truncate(item.title ?? item.content, 60)}${expiry}`);
|
|
305
|
+
}
|
|
306
|
+
}),
|
|
307
|
+
})
|
|
308
|
+
.command({
|
|
309
|
+
command: 'add',
|
|
310
|
+
describe: 'Add a knowledge override',
|
|
311
|
+
builder: (y) =>
|
|
312
|
+
y
|
|
313
|
+
.option('title', { type: 'string', demandOption: true, describe: 'Override title' })
|
|
314
|
+
.option('content', { type: 'string', demandOption: true, describe: 'Authoritative content' })
|
|
315
|
+
.option('expires', { type: 'string', describe: 'Expiry date (ISO 8601, e.g. 2026-12-31)' }),
|
|
316
|
+
handler: runCommand(async (argv) => {
|
|
317
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
318
|
+
const payload = { title: argv.title, content: argv.content, isActive: true };
|
|
319
|
+
if (argv.expires) payload.expiresAt = new Date(argv.expires).toISOString();
|
|
320
|
+
const result = await post('/Ticket/knowledge-overrides', payload);
|
|
321
|
+
console.log(`Override added: [${result.id}] "${argv.title}"`);
|
|
322
|
+
}),
|
|
323
|
+
})
|
|
324
|
+
.command({
|
|
325
|
+
command: 'delete <id>',
|
|
326
|
+
describe: 'Delete a knowledge override',
|
|
327
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Override ID' }),
|
|
328
|
+
handler: runCommand(async (argv) => {
|
|
329
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
330
|
+
await del(`/Ticket/knowledge-overrides/${argv.id}`);
|
|
331
|
+
console.log(`Override ${argv.id} deleted.`);
|
|
332
|
+
}),
|
|
333
|
+
})
|
|
334
|
+
.demandCommand(1, 'Specify a subcommand: list, add, delete'),
|
|
335
|
+
handler: () => {},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
339
|
+
// Custom content — push arbitrary text/files into the site's KB index
|
|
340
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
341
|
+
// Use --index to target a specific logical index (e.g. "tri-tool").
|
|
342
|
+
// Omit to push to the site's default index.
|
|
343
|
+
|
|
344
|
+
const contentCommand = {
|
|
345
|
+
command: 'content <subcommand>',
|
|
346
|
+
describe: 'Push custom text or files into the knowledge base index',
|
|
347
|
+
builder: (yargs) =>
|
|
348
|
+
yargs
|
|
349
|
+
.command({
|
|
350
|
+
command: 'ingest',
|
|
351
|
+
describe: 'Push a file or text snippet into the KB index',
|
|
352
|
+
builder: (y) =>
|
|
353
|
+
y
|
|
354
|
+
.option('file', {
|
|
355
|
+
describe: 'Path to a .txt or .md file to ingest',
|
|
356
|
+
type: 'string',
|
|
357
|
+
})
|
|
358
|
+
.option('text', {
|
|
359
|
+
describe: 'Inline text content to ingest (alternative to --file)',
|
|
360
|
+
type: 'string',
|
|
361
|
+
})
|
|
362
|
+
.option('content-id', {
|
|
363
|
+
describe: 'Stable ID for this document (used for dedup/updates). Defaults to filename.',
|
|
364
|
+
type: 'string',
|
|
365
|
+
})
|
|
366
|
+
.option('title', {
|
|
367
|
+
describe: 'Document title shown when bot attributes this content. Defaults to filename.',
|
|
368
|
+
type: 'string',
|
|
369
|
+
})
|
|
370
|
+
.option('source-url', {
|
|
371
|
+
describe: 'Canonical URL to show when bot cites this content',
|
|
372
|
+
type: 'string',
|
|
373
|
+
})
|
|
374
|
+
.option('index', {
|
|
375
|
+
describe: 'Logical index name to push content into (e.g. "tri-tool"). Defaults to site primary index.',
|
|
376
|
+
type: 'string',
|
|
377
|
+
})
|
|
378
|
+
.check((argv) => {
|
|
379
|
+
if (!argv.file && !argv.text) throw new Error('Provide either --file or --text');
|
|
380
|
+
if (argv.file && argv.text) throw new Error('Use --file or --text, not both');
|
|
381
|
+
return true;
|
|
382
|
+
}),
|
|
383
|
+
handler: runCommand(async (argv) => {
|
|
384
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
385
|
+
|
|
386
|
+
let text, defaultId, defaultTitle;
|
|
387
|
+
if (argv.file) {
|
|
388
|
+
text = readFileSync(argv.file, 'utf8');
|
|
389
|
+
defaultId = basename(argv.file);
|
|
390
|
+
defaultTitle = basename(argv.file);
|
|
391
|
+
} else {
|
|
392
|
+
text = argv.text;
|
|
393
|
+
defaultId = `inline-${Date.now()}`;
|
|
394
|
+
defaultTitle = defaultId;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const contentId = argv['content-id'] || defaultId;
|
|
398
|
+
const title = argv.title || defaultTitle;
|
|
399
|
+
|
|
400
|
+
const result = await messagingPost('/AzureIndexes/IngestContent', {
|
|
401
|
+
contentId,
|
|
402
|
+
title,
|
|
403
|
+
text,
|
|
404
|
+
sourceUrl: argv['source-url'] || undefined,
|
|
405
|
+
indexName: argv.index || undefined,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (result.unchanged) {
|
|
409
|
+
console.log(`Unchanged: "${title}" — content hash matches, no re-embedding needed.`);
|
|
410
|
+
} else {
|
|
411
|
+
console.log(`Indexed: "${title}" [${contentId}]`);
|
|
412
|
+
console.log(` ${result.chunks} chunk(s) embedded and searchable immediately.`);
|
|
413
|
+
}
|
|
414
|
+
}),
|
|
415
|
+
})
|
|
416
|
+
.command({
|
|
417
|
+
command: 'list',
|
|
418
|
+
describe: 'List all custom content documents in the KB index',
|
|
419
|
+
handler: runCommand(async () => {
|
|
420
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
421
|
+
const items = await messagingGet('/AzureIndexes/IngestContent');
|
|
422
|
+
if (!items?.length) { console.log('No custom content found.'); return; }
|
|
423
|
+
|
|
424
|
+
console.log(`\nFound ${items.length} document(s):\n`);
|
|
425
|
+
for (const item of items) {
|
|
426
|
+
const indexed = item.lastIndexedAt ? new Date(item.lastIndexedAt).toLocaleDateString() : 'never';
|
|
427
|
+
const status = item.lastError ? ` error: ${item.lastError}` : '';
|
|
428
|
+
console.log(` [${item.contentId}] chunks=${item.chunkCount ?? '?'} indexed=${indexed}${status}`);
|
|
429
|
+
if (item.title && item.title !== item.contentId) console.log(` "${item.title}"`);
|
|
430
|
+
}
|
|
431
|
+
}),
|
|
432
|
+
})
|
|
433
|
+
.command({
|
|
434
|
+
command: 'remove <content-id>',
|
|
435
|
+
describe: 'Remove a custom content document from the KB index',
|
|
436
|
+
builder: (y) => y.positional('content-id', { type: 'string', describe: 'Content ID to remove' }),
|
|
437
|
+
handler: runCommand(async (argv) => {
|
|
438
|
+
await requireFeature('enableKnowledgeBase', 'Knowledge Base');
|
|
439
|
+
await messagingDel(`/AzureIndexes/IngestContent/${encodeURIComponent(argv['content-id'])}`);
|
|
440
|
+
console.log(`Removed "${argv['content-id']}" from the index.`);
|
|
441
|
+
}),
|
|
442
|
+
})
|
|
443
|
+
.demandCommand(1, 'Specify a subcommand: ingest, list, remove'),
|
|
444
|
+
handler: () => {},
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
function truncate(str, max) {
|
|
448
|
+
if (!str) return '—';
|
|
449
|
+
return str.length <= max ? str : str.slice(0, max - 1) + '…';
|
|
450
|
+
}
|