@velaro/cli 0.4.0 → 0.7.0
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 +7 -1
- package/lib/api.js +52 -48
- package/lib/commands/agent.js +50 -50
- package/lib/commands/article.js +120 -12
- package/lib/commands/check.js +163 -163
- package/lib/commands/deployment.js +107 -107
- package/lib/commands/env.js +45 -0
- package/lib/commands/index.js +212 -0
- package/lib/commands/ingest.js +31 -31
- package/lib/commands/kb.js +69 -1
- package/lib/commands/login.js +46 -20
- package/lib/commands/mcp-key.js +17 -10
- package/lib/commands/ops.js +173 -0
- package/lib/commands/site.js +62 -62
- package/lib/commands/status.js +24 -22
- package/lib/commands/team.js +144 -144
- package/lib/commands/update.js +47 -47
- package/lib/commands/whoami.js +22 -22
- package/lib/config.js +63 -8
- package/lib/oauth.js +135 -135
- package/lib/run.js +16 -16
- package/lib/subscription.js +39 -39
- package/lib/track.js +35 -35
- package/lib/update-check.js +64 -64
- package/package.json +2 -2
package/lib/commands/check.js
CHANGED
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* velaro check — full site health and configuration summary.
|
|
3
|
-
*
|
|
4
|
-
* Shows every feature area with:
|
|
5
|
-
* ✓ enabled and configured
|
|
6
|
-
* ⚠ enabled but nothing configured yet
|
|
7
|
-
* ✗ not on your plan
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { get } from '../api.js';
|
|
11
|
-
import { getSubscription } from '../subscription.js';
|
|
12
|
-
import { runCommand } from '../run.js';
|
|
13
|
-
|
|
14
|
-
const TICK = '✓';
|
|
15
|
-
const WARN = '⚠';
|
|
16
|
-
const CROSS = '✗';
|
|
17
|
-
|
|
18
|
-
export const checkCommand = {
|
|
19
|
-
command: 'check',
|
|
20
|
-
describe: 'Show full site configuration and health summary',
|
|
21
|
-
handler: runCommand(async () => {
|
|
22
|
-
const [sub, apiStatus] = await Promise.all([
|
|
23
|
-
getSubscription().catch(() => null),
|
|
24
|
-
get('/Status').catch(() => null),
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
if (!sub) {
|
|
28
|
-
console.error('Could not retrieve subscription. Check your login: velaro whoami');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const ok = (msg) => console.log(` ${TICK} ${msg}`);
|
|
33
|
-
const warn = (msg) => console.log(` ${WARN} ${msg}`);
|
|
34
|
-
const off = (msg) => console.log(` ${CROSS} ${msg}`);
|
|
35
|
-
|
|
36
|
-
console.log('\n── API Health ────────────────────────────────────────────────');
|
|
37
|
-
if (apiStatus) {
|
|
38
|
-
ok(`API reachable`);
|
|
39
|
-
} else {
|
|
40
|
-
warn('API did not respond to /Status');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── Deployments ─────────────────────────────────────────────────────────
|
|
44
|
-
console.log('\n── Deployments ───────────────────────────────────────────────');
|
|
45
|
-
try {
|
|
46
|
-
const [deps, teams] = await Promise.all([get('/Deployment'), get('/Teams/List')]);
|
|
47
|
-
const teamMap = Object.fromEntries((teams ?? []).map(t => [t.id, t.name]));
|
|
48
|
-
if (deps?.length) {
|
|
49
|
-
for (const d of deps) {
|
|
50
|
-
ok(`[${d.id}] "${d.displayName}" → team: ${teamMap[d.teamId] ?? `#${d.teamId}`} (embed key: ${d.deploymentId})`);
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
warn('No deployments — create one in the admin UI to get your embed snippet.');
|
|
54
|
-
}
|
|
55
|
-
} catch { warn('Could not load deployments'); }
|
|
56
|
-
|
|
57
|
-
// ── AI Bots ──────────────────────────────────────────────────────────────
|
|
58
|
-
console.log('\n── AI Bots ───────────────────────────────────────────────────');
|
|
59
|
-
if (sub.enableAI) {
|
|
60
|
-
try {
|
|
61
|
-
const bots = await get('/AIConfiguration/list');
|
|
62
|
-
if (bots?.length) {
|
|
63
|
-
for (const b of bots) ok(`[${b.id}] "${b.name}" model=${b.aiModel ?? 'default'}`);
|
|
64
|
-
} else {
|
|
65
|
-
warn('AI enabled but no bots configured. Run: velaro bot setup --name "My Bot"');
|
|
66
|
-
}
|
|
67
|
-
} catch { warn('Could not load bot list'); }
|
|
68
|
-
} else {
|
|
69
|
-
off('AI Bots — not on your plan (upgrade at velaro.com/pricing)');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ── Knowledge Base ───────────────────────────────────────────────────────
|
|
73
|
-
console.log('\n── Knowledge Base ────────────────────────────────────────────');
|
|
74
|
-
if (sub.enableKnowledgeBase) {
|
|
75
|
-
try {
|
|
76
|
-
const [qna, overrides] = await Promise.all([
|
|
77
|
-
get('/api/BotQnA').catch(() => []),
|
|
78
|
-
get('/Ticket/knowledge-overrides').catch(() => []),
|
|
79
|
-
]);
|
|
80
|
-
ok(`${qna?.length ?? 0} Q&A pair(s) · ${overrides?.length ?? 0} override(s)`);
|
|
81
|
-
} catch { warn('Could not load knowledge base'); }
|
|
82
|
-
} else {
|
|
83
|
-
off('Knowledge Base — not on your plan');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── Workflows ────────────────────────────────────────────────────────────
|
|
87
|
-
console.log('\n── Workflows ─────────────────────────────────────────────────');
|
|
88
|
-
if (sub.enableAutomation) {
|
|
89
|
-
try {
|
|
90
|
-
const workflows = (await get('/Workflows/List') ?? []).filter(w => !w.isTemplate);
|
|
91
|
-
const enabled = workflows.filter(w => w.enabled).length;
|
|
92
|
-
const disabled = workflows.length - enabled;
|
|
93
|
-
if (workflows.length) {
|
|
94
|
-
ok(`${workflows.length} workflow(s) · ${enabled} enabled · ${disabled} disabled`);
|
|
95
|
-
for (const w of workflows) {
|
|
96
|
-
const sym = w.enabled ? TICK : WARN;
|
|
97
|
-
console.log(` ${sym} [${w.id}] "${w.name}" (${w.enabled ? 'on' : 'off'})`);
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
warn('Automation enabled but no workflows configured.');
|
|
101
|
-
}
|
|
102
|
-
} catch { warn('Could not load workflows'); }
|
|
103
|
-
} else {
|
|
104
|
-
off('Workflows — not on your plan');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ── Routing Rules ────────────────────────────────────────────────────────
|
|
108
|
-
console.log('\n── Routing Rules ─────────────────────────────────────────────');
|
|
109
|
-
if (sub.enableWorkflowRules) {
|
|
110
|
-
try {
|
|
111
|
-
const rules = await get('/Rules/List');
|
|
112
|
-
if (rules?.length) {
|
|
113
|
-
ok(`${rules.length} routing rule(s) configured`);
|
|
114
|
-
} else {
|
|
115
|
-
warn('Routing rules enabled but none configured.');
|
|
116
|
-
}
|
|
117
|
-
} catch { warn('Could not load rules'); }
|
|
118
|
-
} else {
|
|
119
|
-
off('Routing Rules — not on your plan');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ── Channels ─────────────────────────────────────────────────────────────
|
|
123
|
-
console.log('\n── Channels ──────────────────────────────────────────────────');
|
|
124
|
-
const channels = [
|
|
125
|
-
['enableWeb', 'Web Chat'],
|
|
126
|
-
['enableSms', 'SMS'],
|
|
127
|
-
['enableEmail', 'Email'],
|
|
128
|
-
['enableWhatsapp', 'WhatsApp'],
|
|
129
|
-
['enableIvr', 'IVR / Voice'],
|
|
130
|
-
['enableFacebook', 'Facebook Messenger'],
|
|
131
|
-
['enableInstagram', 'Instagram'],
|
|
132
|
-
['enableRcs', 'RCS'],
|
|
133
|
-
];
|
|
134
|
-
for (const [flag, label] of channels) {
|
|
135
|
-
if (sub[flag]) ok(label); else off(`${label} — not on your plan`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── IVR detail ───────────────────────────────────────────────────────────
|
|
139
|
-
if (sub.enableIvr) {
|
|
140
|
-
try {
|
|
141
|
-
const ivr = await get('/api/ivr/config');
|
|
142
|
-
if (ivr?.phoneNumber) {
|
|
143
|
-
console.log(`\n── IVR Config ────────────────────────────────────────────────`);
|
|
144
|
-
ok(`Phone: ${ivr.phoneNumber}`);
|
|
145
|
-
if (ivr.bridgeNumber) ok(`Bridge: ${ivr.bridgeNumber}`);
|
|
146
|
-
else warn('No bridge (transfer) number set — agents cannot receive transferred calls.');
|
|
147
|
-
}
|
|
148
|
-
} catch { /* IVR config is optional */ }
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ── Agents ───────────────────────────────────────────────────────────────
|
|
152
|
-
console.log('\n── Agents ────────────────────────────────────────────────────');
|
|
153
|
-
try {
|
|
154
|
-
const agents = await get('/Users/List');
|
|
155
|
-
const online = (agents ?? []).filter(a => a.status === 'Available').length;
|
|
156
|
-
const away = (agents ?? []).filter(a => a.status === 'Away').length;
|
|
157
|
-
const offline = (agents ?? []).length - online - away;
|
|
158
|
-
ok(`${agents?.length ?? 0} agent(s) · ${online} online · ${away} away · ${offline} offline`);
|
|
159
|
-
} catch { warn('Could not load agents'); }
|
|
160
|
-
|
|
161
|
-
console.log('\n──────────────────────────────────────────────────────────────\n');
|
|
162
|
-
}),
|
|
163
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* velaro check — full site health and configuration summary.
|
|
3
|
+
*
|
|
4
|
+
* Shows every feature area with:
|
|
5
|
+
* ✓ enabled and configured
|
|
6
|
+
* ⚠ enabled but nothing configured yet
|
|
7
|
+
* ✗ not on your plan
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { get } from '../api.js';
|
|
11
|
+
import { getSubscription } from '../subscription.js';
|
|
12
|
+
import { runCommand } from '../run.js';
|
|
13
|
+
|
|
14
|
+
const TICK = '✓';
|
|
15
|
+
const WARN = '⚠';
|
|
16
|
+
const CROSS = '✗';
|
|
17
|
+
|
|
18
|
+
export const checkCommand = {
|
|
19
|
+
command: 'check',
|
|
20
|
+
describe: 'Show full site configuration and health summary',
|
|
21
|
+
handler: runCommand(async () => {
|
|
22
|
+
const [sub, apiStatus] = await Promise.all([
|
|
23
|
+
getSubscription().catch(() => null),
|
|
24
|
+
get('/Status').catch(() => null),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
if (!sub) {
|
|
28
|
+
console.error('Could not retrieve subscription. Check your login: velaro whoami');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ok = (msg) => console.log(` ${TICK} ${msg}`);
|
|
33
|
+
const warn = (msg) => console.log(` ${WARN} ${msg}`);
|
|
34
|
+
const off = (msg) => console.log(` ${CROSS} ${msg}`);
|
|
35
|
+
|
|
36
|
+
console.log('\n── API Health ────────────────────────────────────────────────');
|
|
37
|
+
if (apiStatus) {
|
|
38
|
+
ok(`API reachable`);
|
|
39
|
+
} else {
|
|
40
|
+
warn('API did not respond to /Status');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Deployments ─────────────────────────────────────────────────────────
|
|
44
|
+
console.log('\n── Deployments ───────────────────────────────────────────────');
|
|
45
|
+
try {
|
|
46
|
+
const [deps, teams] = await Promise.all([get('/Deployment'), get('/Teams/List')]);
|
|
47
|
+
const teamMap = Object.fromEntries((teams ?? []).map(t => [t.id, t.name]));
|
|
48
|
+
if (deps?.length) {
|
|
49
|
+
for (const d of deps) {
|
|
50
|
+
ok(`[${d.id}] "${d.displayName}" → team: ${teamMap[d.teamId] ?? `#${d.teamId}`} (embed key: ${d.deploymentId})`);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
warn('No deployments — create one in the admin UI to get your embed snippet.');
|
|
54
|
+
}
|
|
55
|
+
} catch { warn('Could not load deployments'); }
|
|
56
|
+
|
|
57
|
+
// ── AI Bots ──────────────────────────────────────────────────────────────
|
|
58
|
+
console.log('\n── AI Bots ───────────────────────────────────────────────────');
|
|
59
|
+
if (sub.enableAI) {
|
|
60
|
+
try {
|
|
61
|
+
const bots = await get('/AIConfiguration/list');
|
|
62
|
+
if (bots?.length) {
|
|
63
|
+
for (const b of bots) ok(`[${b.id}] "${b.name}" model=${b.aiModel ?? 'default'}`);
|
|
64
|
+
} else {
|
|
65
|
+
warn('AI enabled but no bots configured. Run: velaro bot setup --name "My Bot"');
|
|
66
|
+
}
|
|
67
|
+
} catch { warn('Could not load bot list'); }
|
|
68
|
+
} else {
|
|
69
|
+
off('AI Bots — not on your plan (upgrade at velaro.com/pricing)');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Knowledge Base ───────────────────────────────────────────────────────
|
|
73
|
+
console.log('\n── Knowledge Base ────────────────────────────────────────────');
|
|
74
|
+
if (sub.enableKnowledgeBase) {
|
|
75
|
+
try {
|
|
76
|
+
const [qna, overrides] = await Promise.all([
|
|
77
|
+
get('/api/BotQnA').catch(() => []),
|
|
78
|
+
get('/Ticket/knowledge-overrides').catch(() => []),
|
|
79
|
+
]);
|
|
80
|
+
ok(`${qna?.length ?? 0} Q&A pair(s) · ${overrides?.length ?? 0} override(s)`);
|
|
81
|
+
} catch { warn('Could not load knowledge base'); }
|
|
82
|
+
} else {
|
|
83
|
+
off('Knowledge Base — not on your plan');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Workflows ────────────────────────────────────────────────────────────
|
|
87
|
+
console.log('\n── Workflows ─────────────────────────────────────────────────');
|
|
88
|
+
if (sub.enableAutomation) {
|
|
89
|
+
try {
|
|
90
|
+
const workflows = (await get('/Workflows/List') ?? []).filter(w => !w.isTemplate);
|
|
91
|
+
const enabled = workflows.filter(w => w.enabled).length;
|
|
92
|
+
const disabled = workflows.length - enabled;
|
|
93
|
+
if (workflows.length) {
|
|
94
|
+
ok(`${workflows.length} workflow(s) · ${enabled} enabled · ${disabled} disabled`);
|
|
95
|
+
for (const w of workflows) {
|
|
96
|
+
const sym = w.enabled ? TICK : WARN;
|
|
97
|
+
console.log(` ${sym} [${w.id}] "${w.name}" (${w.enabled ? 'on' : 'off'})`);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
warn('Automation enabled but no workflows configured.');
|
|
101
|
+
}
|
|
102
|
+
} catch { warn('Could not load workflows'); }
|
|
103
|
+
} else {
|
|
104
|
+
off('Workflows — not on your plan');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Routing Rules ────────────────────────────────────────────────────────
|
|
108
|
+
console.log('\n── Routing Rules ─────────────────────────────────────────────');
|
|
109
|
+
if (sub.enableWorkflowRules) {
|
|
110
|
+
try {
|
|
111
|
+
const rules = await get('/Rules/List');
|
|
112
|
+
if (rules?.length) {
|
|
113
|
+
ok(`${rules.length} routing rule(s) configured`);
|
|
114
|
+
} else {
|
|
115
|
+
warn('Routing rules enabled but none configured.');
|
|
116
|
+
}
|
|
117
|
+
} catch { warn('Could not load rules'); }
|
|
118
|
+
} else {
|
|
119
|
+
off('Routing Rules — not on your plan');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Channels ─────────────────────────────────────────────────────────────
|
|
123
|
+
console.log('\n── Channels ──────────────────────────────────────────────────');
|
|
124
|
+
const channels = [
|
|
125
|
+
['enableWeb', 'Web Chat'],
|
|
126
|
+
['enableSms', 'SMS'],
|
|
127
|
+
['enableEmail', 'Email'],
|
|
128
|
+
['enableWhatsapp', 'WhatsApp'],
|
|
129
|
+
['enableIvr', 'IVR / Voice'],
|
|
130
|
+
['enableFacebook', 'Facebook Messenger'],
|
|
131
|
+
['enableInstagram', 'Instagram'],
|
|
132
|
+
['enableRcs', 'RCS'],
|
|
133
|
+
];
|
|
134
|
+
for (const [flag, label] of channels) {
|
|
135
|
+
if (sub[flag]) ok(label); else off(`${label} — not on your plan`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── IVR detail ───────────────────────────────────────────────────────────
|
|
139
|
+
if (sub.enableIvr) {
|
|
140
|
+
try {
|
|
141
|
+
const ivr = await get('/api/ivr/config');
|
|
142
|
+
if (ivr?.phoneNumber) {
|
|
143
|
+
console.log(`\n── IVR Config ────────────────────────────────────────────────`);
|
|
144
|
+
ok(`Phone: ${ivr.phoneNumber}`);
|
|
145
|
+
if (ivr.bridgeNumber) ok(`Bridge: ${ivr.bridgeNumber}`);
|
|
146
|
+
else warn('No bridge (transfer) number set — agents cannot receive transferred calls.');
|
|
147
|
+
}
|
|
148
|
+
} catch { /* IVR config is optional */ }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Agents ───────────────────────────────────────────────────────────────
|
|
152
|
+
console.log('\n── Agents ────────────────────────────────────────────────────');
|
|
153
|
+
try {
|
|
154
|
+
const agents = await get('/Users/List');
|
|
155
|
+
const online = (agents ?? []).filter(a => a.status === 'Available').length;
|
|
156
|
+
const away = (agents ?? []).filter(a => a.status === 'Away').length;
|
|
157
|
+
const offline = (agents ?? []).length - online - away;
|
|
158
|
+
ok(`${agents?.length ?? 0} agent(s) · ${online} online · ${away} away · ${offline} offline`);
|
|
159
|
+
} catch { warn('Could not load agents'); }
|
|
160
|
+
|
|
161
|
+
console.log('\n──────────────────────────────────────────────────────────────\n');
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import { get, post } from '../api.js';
|
|
2
|
-
import { runCommand } from '../run.js';
|
|
3
|
-
|
|
4
|
-
export const deploymentCommand = {
|
|
5
|
-
command: 'deployment <subcommand>',
|
|
6
|
-
describe: 'Manage chat widget deployments',
|
|
7
|
-
builder: (yargs) =>
|
|
8
|
-
yargs
|
|
9
|
-
.command(deploymentListCommand)
|
|
10
|
-
.command(deploymentRenameCommand)
|
|
11
|
-
.command(deploymentAssignCommand)
|
|
12
|
-
.demandCommand(1, 'Specify a subcommand: list, rename, assign-team'),
|
|
13
|
-
handler: () => {},
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// ── list ───────────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
const deploymentListCommand = {
|
|
19
|
-
command: 'list',
|
|
20
|
-
describe: 'List chat widget deployments',
|
|
21
|
-
handler: runCommand(async () => {
|
|
22
|
-
const [deployments, teams] = await Promise.all([
|
|
23
|
-
get('/Deployment'),
|
|
24
|
-
get('/Teams/List'),
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
if (!deployments?.length) {
|
|
28
|
-
console.log('No deployments found.');
|
|
29
|
-
console.log('\nA deployment is a chat widget embed — one per page or team you want to serve.');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const teamMap = Object.fromEntries((teams ?? []).map(t => [t.id, t.name]));
|
|
34
|
-
|
|
35
|
-
const rows = deployments.map(d => ({
|
|
36
|
-
id: `[${d.id}]`,
|
|
37
|
-
key: d.deploymentId ?? '—',
|
|
38
|
-
team: teamMap[d.teamId] ?? `team ${d.teamId}`,
|
|
39
|
-
name: d.displayName ?? '(unnamed)',
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
const idW = Math.max(...rows.map(r => r.id.length));
|
|
43
|
-
const keyW = Math.max('embed-key'.length, ...rows.map(r => r.key.length));
|
|
44
|
-
const teamW = Math.max('team'.length, ...rows.map(r => r.team.length));
|
|
45
|
-
|
|
46
|
-
console.log(`\n${'id'.padStart(idW)} ${'embed-key'.padEnd(keyW)} ${'team'.padEnd(teamW)} name`);
|
|
47
|
-
console.log(`${'-'.repeat(idW)} ${'-'.repeat(keyW)} ${'-'.repeat(teamW)} ----`);
|
|
48
|
-
for (const r of rows) {
|
|
49
|
-
console.log(`${r.id.padStart(idW)} ${r.key.padEnd(keyW)} ${r.team.padEnd(teamW)} ${r.name}`);
|
|
50
|
-
}
|
|
51
|
-
console.log(`\n${rows.length} deployment(s)`);
|
|
52
|
-
console.log('\nThe embed-key goes in your widget JavaScript snippet.');
|
|
53
|
-
}),
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// ── rename ─────────────────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
const deploymentRenameCommand = {
|
|
59
|
-
command: 'rename <id> <name>',
|
|
60
|
-
describe: 'Rename a deployment',
|
|
61
|
-
builder: (y) =>
|
|
62
|
-
y
|
|
63
|
-
.positional('id', { type: 'number', describe: 'Deployment numeric ID' })
|
|
64
|
-
.positional('name', { type: 'string', describe: 'New display name' }),
|
|
65
|
-
handler: runCommand(async (argv) => {
|
|
66
|
-
const dep = await get(`/Deployment/${argv.id}`);
|
|
67
|
-
if (!dep) {
|
|
68
|
-
console.error(`Deployment ${argv.id} not found.`);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
await post('/Deployment', { ...dep, id: argv.id, displayName: argv.name });
|
|
73
|
-
console.log(`Deployment ${argv.id} renamed to "${argv.name}".`);
|
|
74
|
-
}),
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// ── assign-team ────────────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
const deploymentAssignCommand = {
|
|
80
|
-
command: 'assign-team <id> <team-id>',
|
|
81
|
-
describe: 'Assign a team to a deployment',
|
|
82
|
-
builder: (y) =>
|
|
83
|
-
y
|
|
84
|
-
.positional('id', { type: 'number', describe: 'Deployment numeric ID' })
|
|
85
|
-
.positional('team-id', { type: 'number', describe: 'Team ID to assign' }),
|
|
86
|
-
handler: runCommand(async (argv) => {
|
|
87
|
-
const [dep, teams] = await Promise.all([
|
|
88
|
-
get(`/Deployment/${argv.id}`),
|
|
89
|
-
get('/Teams/List'),
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
if (!dep) {
|
|
93
|
-
console.error(`Deployment ${argv.id} not found.`);
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const team = (teams ?? []).find(t => t.id === argv['team-id']);
|
|
98
|
-
if (!team) {
|
|
99
|
-
console.error(`Team ${argv['team-id']} not found.`);
|
|
100
|
-
console.error('Run: velaro team list');
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
await post('/Deployment', { ...dep, id: argv.id, teamId: argv['team-id'] });
|
|
105
|
-
console.log(`Deployment ${argv.id} ("${dep.displayName}") assigned to team "${team.name}".`);
|
|
106
|
-
}),
|
|
107
|
-
};
|
|
1
|
+
import { get, post } from '../api.js';
|
|
2
|
+
import { runCommand } from '../run.js';
|
|
3
|
+
|
|
4
|
+
export const deploymentCommand = {
|
|
5
|
+
command: 'deployment <subcommand>',
|
|
6
|
+
describe: 'Manage chat widget deployments',
|
|
7
|
+
builder: (yargs) =>
|
|
8
|
+
yargs
|
|
9
|
+
.command(deploymentListCommand)
|
|
10
|
+
.command(deploymentRenameCommand)
|
|
11
|
+
.command(deploymentAssignCommand)
|
|
12
|
+
.demandCommand(1, 'Specify a subcommand: list, rename, assign-team'),
|
|
13
|
+
handler: () => {},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ── list ───────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const deploymentListCommand = {
|
|
19
|
+
command: 'list',
|
|
20
|
+
describe: 'List chat widget deployments',
|
|
21
|
+
handler: runCommand(async () => {
|
|
22
|
+
const [deployments, teams] = await Promise.all([
|
|
23
|
+
get('/Deployment'),
|
|
24
|
+
get('/Teams/List'),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
if (!deployments?.length) {
|
|
28
|
+
console.log('No deployments found.');
|
|
29
|
+
console.log('\nA deployment is a chat widget embed — one per page or team you want to serve.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const teamMap = Object.fromEntries((teams ?? []).map(t => [t.id, t.name]));
|
|
34
|
+
|
|
35
|
+
const rows = deployments.map(d => ({
|
|
36
|
+
id: `[${d.id}]`,
|
|
37
|
+
key: d.deploymentId ?? '—',
|
|
38
|
+
team: teamMap[d.teamId] ?? `team ${d.teamId}`,
|
|
39
|
+
name: d.displayName ?? '(unnamed)',
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const idW = Math.max(...rows.map(r => r.id.length));
|
|
43
|
+
const keyW = Math.max('embed-key'.length, ...rows.map(r => r.key.length));
|
|
44
|
+
const teamW = Math.max('team'.length, ...rows.map(r => r.team.length));
|
|
45
|
+
|
|
46
|
+
console.log(`\n${'id'.padStart(idW)} ${'embed-key'.padEnd(keyW)} ${'team'.padEnd(teamW)} name`);
|
|
47
|
+
console.log(`${'-'.repeat(idW)} ${'-'.repeat(keyW)} ${'-'.repeat(teamW)} ----`);
|
|
48
|
+
for (const r of rows) {
|
|
49
|
+
console.log(`${r.id.padStart(idW)} ${r.key.padEnd(keyW)} ${r.team.padEnd(teamW)} ${r.name}`);
|
|
50
|
+
}
|
|
51
|
+
console.log(`\n${rows.length} deployment(s)`);
|
|
52
|
+
console.log('\nThe embed-key goes in your widget JavaScript snippet.');
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ── rename ─────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
const deploymentRenameCommand = {
|
|
59
|
+
command: 'rename <id> <name>',
|
|
60
|
+
describe: 'Rename a deployment',
|
|
61
|
+
builder: (y) =>
|
|
62
|
+
y
|
|
63
|
+
.positional('id', { type: 'number', describe: 'Deployment numeric ID' })
|
|
64
|
+
.positional('name', { type: 'string', describe: 'New display name' }),
|
|
65
|
+
handler: runCommand(async (argv) => {
|
|
66
|
+
const dep = await get(`/Deployment/${argv.id}`);
|
|
67
|
+
if (!dep) {
|
|
68
|
+
console.error(`Deployment ${argv.id} not found.`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await post('/Deployment', { ...dep, id: argv.id, displayName: argv.name });
|
|
73
|
+
console.log(`Deployment ${argv.id} renamed to "${argv.name}".`);
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// ── assign-team ────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
const deploymentAssignCommand = {
|
|
80
|
+
command: 'assign-team <id> <team-id>',
|
|
81
|
+
describe: 'Assign a team to a deployment',
|
|
82
|
+
builder: (y) =>
|
|
83
|
+
y
|
|
84
|
+
.positional('id', { type: 'number', describe: 'Deployment numeric ID' })
|
|
85
|
+
.positional('team-id', { type: 'number', describe: 'Team ID to assign' }),
|
|
86
|
+
handler: runCommand(async (argv) => {
|
|
87
|
+
const [dep, teams] = await Promise.all([
|
|
88
|
+
get(`/Deployment/${argv.id}`),
|
|
89
|
+
get('/Teams/List'),
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
if (!dep) {
|
|
93
|
+
console.error(`Deployment ${argv.id} not found.`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const team = (teams ?? []).find(t => t.id === argv['team-id']);
|
|
98
|
+
if (!team) {
|
|
99
|
+
console.error(`Team ${argv['team-id']} not found.`);
|
|
100
|
+
console.error('Run: velaro team list');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await post('/Deployment', { ...dep, id: argv.id, teamId: argv['team-id'] });
|
|
105
|
+
console.log(`Deployment ${argv.id} ("${dep.displayName}") assigned to team "${team.name}".`);
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readConfig, setActiveEnv, getActiveEnv, ENVS } from '../config.js';
|
|
2
|
+
import { runCommand } from '../run.js';
|
|
3
|
+
|
|
4
|
+
export const envCommand = {
|
|
5
|
+
command: 'env [name]',
|
|
6
|
+
describe: 'Show or switch the active environment (prod/staging)',
|
|
7
|
+
builder: (y) =>
|
|
8
|
+
y.positional('name', {
|
|
9
|
+
describe: 'Environment to switch to: prod or staging',
|
|
10
|
+
type: 'string',
|
|
11
|
+
choices: ['prod', 'staging'],
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
handler: runCommand(async (argv) => {
|
|
15
|
+
if (argv.name) {
|
|
16
|
+
setActiveEnv(argv.name);
|
|
17
|
+
console.log(`Switched to ${argv.name}.`);
|
|
18
|
+
console.log(`All velaro commands now target: ${ENVS[argv.name].adminApiBase}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Show status of all environments
|
|
23
|
+
const cfg = readConfig();
|
|
24
|
+
const active = cfg.activeEnv || 'prod';
|
|
25
|
+
const envs = cfg.envs || {};
|
|
26
|
+
|
|
27
|
+
console.log('Velaro environments:\n');
|
|
28
|
+
for (const [name, urls] of Object.entries(ENVS)) {
|
|
29
|
+
const creds = envs[name];
|
|
30
|
+
const marker = name === active ? '▶ ' : ' ';
|
|
31
|
+
const status = creds?.velaroToken
|
|
32
|
+
? `logged in as ${creds.userName ?? 'unknown'} (site ${creds.siteId})`
|
|
33
|
+
: 'not logged in';
|
|
34
|
+
const expiry = creds?.velaroExpires
|
|
35
|
+
? new Date(creds.velaroExpires) > new Date() ? '' : ' ⚠ token expired'
|
|
36
|
+
: '';
|
|
37
|
+
console.log(`${marker}${name.padEnd(10)} ${status}${expiry}`);
|
|
38
|
+
console.log(` ${urls.adminApiBase}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`\nActive: ${active}`);
|
|
42
|
+
console.log('\nTo switch: velaro env staging | velaro env prod');
|
|
43
|
+
console.log('To log in: velaro login | velaro login --staging');
|
|
44
|
+
}),
|
|
45
|
+
};
|