@velaro/cli 1.4.77 → 1.4.78
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 +6 -0
- package/lib/api.js +2 -1
- package/lib/commands/actions.js +105 -0
- package/lib/commands/ai-config.js +13 -0
- package/lib/commands/knowledge-advisor.js +64 -0
- package/lib/commands/sap.js +101 -0
- package/package.json +1 -1
package/bin/velaro.js
CHANGED
|
@@ -10,6 +10,7 @@ import { botCommand } from '../lib/commands/bot.js';
|
|
|
10
10
|
import { indexCommand } from '../lib/commands/index.js';
|
|
11
11
|
import { ingestCommand } from '../lib/commands/ingest.js';
|
|
12
12
|
import { indexMoveCommand } from '../lib/commands/index-move.js';
|
|
13
|
+
import { knowledgeAdvisorCommand } from '../lib/commands/knowledge-advisor.js';
|
|
13
14
|
import { productCommand } from '../lib/commands/product.js';
|
|
14
15
|
import { mcpKeyCommand } from '../lib/commands/mcp-key.js';
|
|
15
16
|
import { opsCommand } from '../lib/commands/ops.js';
|
|
@@ -56,6 +57,8 @@ import { focusCommand } from '../lib/commands/focus.js';
|
|
|
56
57
|
import { triageCommand } from '../lib/commands/triage.js';
|
|
57
58
|
import { automationDraftCommand } from '../lib/commands/automation-draft.js';
|
|
58
59
|
import { magentoCommand } from '../lib/commands/magento.js';
|
|
60
|
+
import { sapCommand } from '../lib/commands/sap.js';
|
|
61
|
+
import { actionsCommand } from '../lib/commands/actions.js';
|
|
59
62
|
import { mondayCommand } from '../lib/commands/monday.js';
|
|
60
63
|
import { notionCommand } from '../lib/commands/notion.js';
|
|
61
64
|
import { callrailCommand } from '../lib/commands/callrail.js';
|
|
@@ -122,6 +125,7 @@ await yargs(hideBin(process.argv))
|
|
|
122
125
|
.command(indexCommand)
|
|
123
126
|
.command(ingestCommand)
|
|
124
127
|
.command(indexMoveCommand)
|
|
128
|
+
.command(knowledgeAdvisorCommand)
|
|
125
129
|
.command(productCommand)
|
|
126
130
|
.command(mcpKeyCommand)
|
|
127
131
|
.command(opsCommand)
|
|
@@ -170,6 +174,8 @@ await yargs(hideBin(process.argv))
|
|
|
170
174
|
.command(triageCommand)
|
|
171
175
|
.command(automationDraftCommand)
|
|
172
176
|
.command(magentoCommand)
|
|
177
|
+
.command(sapCommand)
|
|
178
|
+
.command(actionsCommand)
|
|
173
179
|
.command(mondayCommand)
|
|
174
180
|
.command(notionCommand)
|
|
175
181
|
.command(callrailCommand)
|
package/lib/api.js
CHANGED
|
@@ -60,7 +60,7 @@ export const post = (path, body) => request('POST', path, body);
|
|
|
60
60
|
export const put = (path, body) => request('PUT', path, body);
|
|
61
61
|
export const del = (path) => request('DELETE', path);
|
|
62
62
|
|
|
63
|
-
export async function messagingRequest(method, path, body, envOverride) {
|
|
63
|
+
export async function messagingRequest(method, path, body, envOverride, extraHeaders = {}) {
|
|
64
64
|
const creds = await getCredentials(envOverride);
|
|
65
65
|
const res = await fetch(joinUrl(creds.messagingApiBase, path), {
|
|
66
66
|
method,
|
|
@@ -72,6 +72,7 @@ export async function messagingRequest(method, path, body, envOverride) {
|
|
|
72
72
|
// trust boundary override. Still required: every messaging* CLI command was silently
|
|
73
73
|
// 401ing with "missing_site_id" until this was added.
|
|
74
74
|
...(creds.siteId ? { 'vmsg-siteId': String(creds.siteId) } : {}),
|
|
75
|
+
...extraHeaders,
|
|
75
76
|
},
|
|
76
77
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
77
78
|
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro actions — shared business actions (one definition, many surfaces)
|
|
3
|
+
*
|
|
4
|
+
* velaro actions list Registered business actions + their AI skill tool names
|
|
5
|
+
* velaro actions route "<text>" Which action does this request mean? (deterministic)
|
|
6
|
+
* velaro actions run <name> --json '<request>' Execute an action as the signed-in agent
|
|
7
|
+
* [--idempotency-key K] (writes: same key => same result, never a duplicate)
|
|
8
|
+
*
|
|
9
|
+
* Calls messaging GET /BusinessActions, POST /BusinessActions/Route and POST /BusinessActions/{name}/Execute.
|
|
10
|
+
* Site scope always comes from the signed-in identity, never from a flag.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { messagingRequest } from '../api.js';
|
|
14
|
+
|
|
15
|
+
const GRN = '\x1b[32m';
|
|
16
|
+
const RED = '\x1b[31m';
|
|
17
|
+
const DIM = '\x1b[2m';
|
|
18
|
+
const BOLD = '\x1b[1m';
|
|
19
|
+
const RST = '\x1b[0m';
|
|
20
|
+
|
|
21
|
+
async function listHandler(argv) {
|
|
22
|
+
try {
|
|
23
|
+
const rows = await messagingRequest('GET', '/BusinessActions');
|
|
24
|
+
if (argv.json) { console.log(JSON.stringify(rows, null, 2)); return; }
|
|
25
|
+
console.log(`\n${BOLD}${rows.length} business action(s)${RST}\n`);
|
|
26
|
+
for (const a of rows) {
|
|
27
|
+
const kind = a.isWrite ? `${RED}write${RST}` : `${GRN}read${RST}`;
|
|
28
|
+
console.log(` ${BOLD}${a.name}${RST} ${DIM}${a.displayName} | ${kind} | ${a.dataClass} | cache ${a.freshnessSeconds}s${RST}`);
|
|
29
|
+
if (a.skillToolNames?.length) console.log(` ${DIM}AI skills: ${a.skillToolNames.join(', ')}${RST}`);
|
|
30
|
+
}
|
|
31
|
+
console.log('');
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error(`\n${RED}✗ ${e.message}${RST}\n`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function routeHandler(argv) {
|
|
39
|
+
const text = argv.text;
|
|
40
|
+
try {
|
|
41
|
+
const r = await messagingRequest('POST', '/BusinessActions/Route', { text, max: argv.max });
|
|
42
|
+
if (argv.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
|
43
|
+
if (!r.matches?.length) { console.log('\nNo business action matches that request.\n'); return; }
|
|
44
|
+
console.log('');
|
|
45
|
+
for (const m of r.matches) {
|
|
46
|
+
console.log(` ${BOLD}${m.actionName}${RST} score ${m.score} ${DIM}matched: ${(m.matchedTerms ?? []).join(', ')}${RST}`);
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(`\n${RED}✗ ${e.message}${RST}\n`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runHandler(argv) {
|
|
56
|
+
let body;
|
|
57
|
+
try {
|
|
58
|
+
body = argv.json ? JSON.parse(argv.json) : {};
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.error(`${RED}✗ --json is not valid JSON: ${e.message}${RST}`);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const headers = argv['idempotency-key'] ? { 'Idempotency-Key': argv['idempotency-key'] } : {};
|
|
66
|
+
const r = await messagingRequest('POST', `/BusinessActions/${encodeURIComponent(argv.name)}/Execute`, body, undefined, headers);
|
|
67
|
+
console.log(JSON.stringify(r, null, 2));
|
|
68
|
+
if (!r.success) process.exitCode = 1;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(`\n${RED}✗ ${e.message}${RST}\n`);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const actionsCommand = {
|
|
76
|
+
command: 'actions <subcommand>',
|
|
77
|
+
describe: 'Shared business actions (pricing, quotes, ... served by Velaro catalog or a connected ERP)',
|
|
78
|
+
builder: y => y
|
|
79
|
+
.command({
|
|
80
|
+
command: 'list',
|
|
81
|
+
describe: 'List registered business actions',
|
|
82
|
+
builder: yy => yy.option('json', { type: 'boolean', default: false }),
|
|
83
|
+
handler: listHandler,
|
|
84
|
+
})
|
|
85
|
+
.command({
|
|
86
|
+
command: 'route <text>',
|
|
87
|
+
describe: 'Which business action does this request mean?',
|
|
88
|
+
builder: yy => yy
|
|
89
|
+
.positional('text', { describe: 'The request text', type: 'string' })
|
|
90
|
+
.option('max', { type: 'number', default: 3 })
|
|
91
|
+
.option('json', { type: 'boolean', default: false }),
|
|
92
|
+
handler: routeHandler,
|
|
93
|
+
})
|
|
94
|
+
.command({
|
|
95
|
+
command: 'run <name>',
|
|
96
|
+
describe: 'Execute a business action',
|
|
97
|
+
builder: yy => yy
|
|
98
|
+
.positional('name', { describe: 'Action name (see `velaro actions list`)', type: 'string' })
|
|
99
|
+
.option('json', { describe: 'Request body as JSON', type: 'string' })
|
|
100
|
+
.option('idempotency-key', { describe: 'For writes: repeat the same key to get the same result', type: 'string' }),
|
|
101
|
+
handler: runHandler,
|
|
102
|
+
})
|
|
103
|
+
.demandCommand(1),
|
|
104
|
+
handler: () => {},
|
|
105
|
+
};
|
|
@@ -194,6 +194,19 @@ const aiConfigSetChannelOverrideCommand = {
|
|
|
194
194
|
}),
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
+
const aiConfigSetReplyFormatCommand = {
|
|
198
|
+
command: 'set-reply-format <id>',
|
|
199
|
+
describe: 'Set reply format: global (plain text default) | plain | markdown',
|
|
200
|
+
builder: (y) =>
|
|
201
|
+
y
|
|
202
|
+
.positional('id', { type: 'number', describe: 'AI configuration ID' })
|
|
203
|
+
.option('mode', { type: 'string', choices: ['global', 'plain', 'markdown'], demandOption: true, describe: 'Reply format mode' }),
|
|
204
|
+
handler: runCommand(async (argv) => {
|
|
205
|
+
const r = await messagingPut(`/AIConfiguration/${argv.id}/reply-format`, { mode: argv.mode });
|
|
206
|
+
console.log(`Reply format set: ai-config=${argv.id} mode=${argv.mode} effective=${r?.effectivePlainText ? 'plain text' : 'markdown allowed'}`);
|
|
207
|
+
}),
|
|
208
|
+
};
|
|
209
|
+
|
|
197
210
|
// ── resolved-model ────────────────────────────────────────────────────────────
|
|
198
211
|
// Voice preview (GET avatar/voices/{voiceId}/preview) is intentionally UI-only: it streams binary
|
|
199
212
|
// audio for a browser <audio> element and has no useful CLI/MCP shape.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// velaro knowledge-advisor advise — tells you, per kind of data (manuals, catalog, price list, cross-reference
|
|
2
|
+
// sheet, photos, CAD), whether to embed all, embed some, use exact lookup or query live, exactly what to upload and a
|
|
3
|
+
// rough size estimate. Pure computation: nothing is read from or written to your site. Wraps velaro-messaging
|
|
4
|
+
// KnowledgeAdvisorController (POST KnowledgeAdvisor/advise). Numbers are estimates.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { messagingPost } from '../api.js';
|
|
7
|
+
import { runCommand } from '../run.js';
|
|
8
|
+
|
|
9
|
+
const n = (v) => (v ?? 0).toLocaleString('en-US');
|
|
10
|
+
|
|
11
|
+
export function sourcesFrom(argv) {
|
|
12
|
+
if (argv.file) {
|
|
13
|
+
const parsed = JSON.parse(fs.readFileSync(argv.file, 'utf8'));
|
|
14
|
+
const list = Array.isArray(parsed) ? parsed : parsed.sources;
|
|
15
|
+
if (!Array.isArray(list) || list.length === 0) throw new Error('The file must be a JSON array of sources or { "sources": [...] }.');
|
|
16
|
+
return list;
|
|
17
|
+
}
|
|
18
|
+
if (!argv.kind) throw new Error('Give --kind (for example manual_pdf) or --file sources.json.');
|
|
19
|
+
return [{
|
|
20
|
+
name: argv.name,
|
|
21
|
+
kind: argv.kind,
|
|
22
|
+
pages: argv.pages,
|
|
23
|
+
rows: argv.rows,
|
|
24
|
+
changeFrequency: argv['change-frequency'],
|
|
25
|
+
hasExactIdentifiers: !!argv['exact-ids'],
|
|
26
|
+
scanned: !!argv.scanned,
|
|
27
|
+
hasLiveSystem: !!argv['live-system'],
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function printAdvice(a) {
|
|
32
|
+
for (const s of a.sources ?? []) {
|
|
33
|
+
console.log(`\n${s.name} (${s.kind}): ${s.recommendation}`);
|
|
34
|
+
for (const w of s.why ?? []) console.log(` Why: ${w}`);
|
|
35
|
+
console.log(` What to upload: ${s.whatToUpload}`);
|
|
36
|
+
const e = s.estimate ?? {};
|
|
37
|
+
console.log(` Estimate: ${n(e.pages)} pages, ${n(e.chunks)} chunks`);
|
|
38
|
+
}
|
|
39
|
+
console.log(`\nTotal: about ${n(a.totalChunks)} chunks. Each answer retrieves about ${n(a.perAnswerRetrievalTokens)} tokens of your content. (${a.basis})`);
|
|
40
|
+
for (const note of a.notes ?? []) console.log(`Note: ${note}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const knowledgeAdvisorCommand = {
|
|
44
|
+
command: 'knowledge-advisor',
|
|
45
|
+
describe: 'Advise what to upload, embed or query live for your bot (estimates, nothing is changed)',
|
|
46
|
+
builder: (yargs) => yargs
|
|
47
|
+
.command('advise', 'Recommend embed all / embed some / exact lookup / live query per data source', (y) => y
|
|
48
|
+
.option('file', { type: 'string', describe: 'JSON file: an array of sources or { "sources": [...] }' })
|
|
49
|
+
.option('kind', { type: 'string', describe: 'manual_pdf | spec_sheet | policy_document | faq_or_help_pages | product_catalog | price_list | cross_reference_sheet | images_or_drawings | cad_files | other_documents' })
|
|
50
|
+
.option('name', { type: 'string' })
|
|
51
|
+
.option('pages', { type: 'number' })
|
|
52
|
+
.option('rows', { type: 'number' })
|
|
53
|
+
.option('change-frequency', { type: 'string', describe: 'static | monthly | weekly | daily | realtime' })
|
|
54
|
+
.option('exact-ids', { type: 'boolean', describe: 'answers depend on exact part numbers / SKUs' })
|
|
55
|
+
.option('scanned', { type: 'boolean', describe: 'the PDF is scanned images' })
|
|
56
|
+
.option('live-system', { type: 'boolean', describe: 'a connected store/ERP/CRM can be queried for this data' })
|
|
57
|
+
.option('json', { type: 'boolean' }),
|
|
58
|
+
runCommand(async (a) => {
|
|
59
|
+
const r = await messagingPost('/KnowledgeAdvisor/advise', { sources: sourcesFrom(a) });
|
|
60
|
+
if (a.json) console.log(JSON.stringify(r, null, 2)); else printAdvice(r);
|
|
61
|
+
}))
|
|
62
|
+
.demandCommand(1, 'Specify a subcommand: advise'),
|
|
63
|
+
handler: () => {},
|
|
64
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro sap — SAP integration diagnostics (S/4HANA, Business One, ECC via gateway)
|
|
3
|
+
*
|
|
4
|
+
* velaro sap diagnose --site-id <id> [--quick] Live read-only diagnostic: config, auth, per-capability probes
|
|
5
|
+
* velaro sap matrix --site-id <id> Capability matrix vs live probes (same data as diagnose)
|
|
6
|
+
*
|
|
7
|
+
* Calls messaging DiagController GET /diagnostics/test-sap?siteId=<id>&deep=<bool> (Velaro staff only). Read-only:
|
|
8
|
+
* nothing is created or changed in SAP, and a write capability is never reported as verified.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { messagingRequest } from '../api.js';
|
|
12
|
+
|
|
13
|
+
const GRN = '\x1b[32m';
|
|
14
|
+
const RED = '\x1b[31m';
|
|
15
|
+
const YEL = '\x1b[33m';
|
|
16
|
+
const DIM = '\x1b[2m';
|
|
17
|
+
const BOLD = '\x1b[1m';
|
|
18
|
+
const RST = '\x1b[0m';
|
|
19
|
+
|
|
20
|
+
const ICON = { pass: `${GRN}✓${RST}`, warn: `${YEL}!${RST}`, fail: `${RED}✗${RST}`, skip: `${DIM}-${RST}` };
|
|
21
|
+
|
|
22
|
+
async function fetchReport(argv, deep) {
|
|
23
|
+
const siteId = argv['site-id'];
|
|
24
|
+
if (!siteId) {
|
|
25
|
+
console.error(`${RED}✗ --site-id is required${RST}`);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const r = await messagingRequest('GET', `/diagnostics/test-sap?siteId=${encodeURIComponent(siteId)}&deep=${deep ? 'true' : 'false'}`);
|
|
31
|
+
return r?.report ?? r;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error(`\n${RED}✗ Diagnostic failed: ${e.message}${RST}\n`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printSteps(report) {
|
|
40
|
+
console.log(`\n${BOLD}SAP diagnostic — site ${report.siteId ?? report.SiteId}${RST} ${DIM}${report.product} / ${report.target}${RST}`);
|
|
41
|
+
console.log(` overall: ${String(report.overall).toUpperCase()}\n`);
|
|
42
|
+
for (const s of report.steps ?? []) {
|
|
43
|
+
const ms = s.latencyMs != null ? `${DIM} (${s.latencyMs}ms)${RST}` : '';
|
|
44
|
+
console.log(` ${ICON[s.status] ?? '?'} ${s.name}${ms}${s.detail ? ` ${DIM}${s.detail}${RST}` : ''}`);
|
|
45
|
+
if (s.hint) console.log(` ${DIM}→ ${s.hint}${RST}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function printCapabilities(report) {
|
|
50
|
+
const caps = report.capabilities ?? [];
|
|
51
|
+
if (!caps.length) return;
|
|
52
|
+
console.log(`\n${BOLD}Capabilities${RST} ${DIM}(documented = capability matrix; live = read-only probe now)${RST}\n`);
|
|
53
|
+
for (const c of caps) {
|
|
54
|
+
const live = c.liveOk === true ? `${GRN}live ok${RST}` : c.liveOk === false ? `${RED}live FAIL${RST}` : `${DIM}not probed${RST}`;
|
|
55
|
+
const proven = c.liveVerified ? `${GRN}verified${RST}` : `${DIM}unverified${RST}`;
|
|
56
|
+
console.log(` ${c.capability.padEnd(22)} ${String(c.documented).padEnd(28)} ${live} ${proven}`);
|
|
57
|
+
if (c.liveOk === false && c.hint) console.log(` ${DIM}→ ${c.hint}${RST}`);
|
|
58
|
+
}
|
|
59
|
+
console.log('');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function diagnoseHandler(argv) {
|
|
63
|
+
const report = await fetchReport(argv, !argv.quick);
|
|
64
|
+
if (!report) return;
|
|
65
|
+
if (argv.json) { console.log(JSON.stringify(report, null, 2)); return; }
|
|
66
|
+
printSteps(report);
|
|
67
|
+
printCapabilities(report);
|
|
68
|
+
if (report.overall === 'failed' || report.overall === 'not_configured') process.exitCode = 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function matrixHandler(argv) {
|
|
72
|
+
const report = await fetchReport(argv, true);
|
|
73
|
+
if (!report) return;
|
|
74
|
+
if (argv.json) { console.log(JSON.stringify(report.capabilities ?? [], null, 2)); return; }
|
|
75
|
+
printCapabilities(report);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const sapCommand = {
|
|
79
|
+
command: 'sap <subcommand>',
|
|
80
|
+
describe: 'SAP integration diagnostics (S/4HANA, Business One, ECC gateway)',
|
|
81
|
+
builder: y => y
|
|
82
|
+
.command({
|
|
83
|
+
command: 'diagnose',
|
|
84
|
+
describe: 'Live read-only diagnostic: configuration, authentication, per-capability probes',
|
|
85
|
+
builder: yy => yy
|
|
86
|
+
.option('site-id', { describe: 'Customer site id', type: 'number' })
|
|
87
|
+
.option('quick', { describe: 'Skip per-capability probes', type: 'boolean', default: false })
|
|
88
|
+
.option('json', { describe: 'Raw JSON output', type: 'boolean', default: false }),
|
|
89
|
+
handler: diagnoseHandler,
|
|
90
|
+
})
|
|
91
|
+
.command({
|
|
92
|
+
command: 'matrix',
|
|
93
|
+
describe: 'Capability matrix: documented support vs what a live probe proves',
|
|
94
|
+
builder: yy => yy
|
|
95
|
+
.option('site-id', { describe: 'Customer site id', type: 'number' })
|
|
96
|
+
.option('json', { describe: 'Raw JSON output', type: 'boolean', default: false }),
|
|
97
|
+
handler: matrixHandler,
|
|
98
|
+
})
|
|
99
|
+
.demandCommand(1),
|
|
100
|
+
handler: () => {},
|
|
101
|
+
};
|
package/package.json
CHANGED