@velaro/cli 0.2.0 → 0.4.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 +56 -56
- package/lib/commands/article.js +280 -0
- package/lib/commands/bot.js +137 -137
- package/lib/commands/kb.js +241 -132
- package/lib/commands/login.js +60 -59
- package/lib/commands/mcp-key.js +152 -63
- package/lib/commands/rule.js +85 -85
- package/lib/commands/workflow.js +98 -98
- package/lib/config.js +3 -1
- package/lib/oauth.js +2 -1
- package/lib/run.js +6 -1
- package/lib/track.js +35 -0
- package/package.json +1 -1
package/lib/commands/mcp-key.js
CHANGED
|
@@ -1,63 +1,152 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
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
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { get, post, del } from '../api.js';
|
|
5
|
+
import { runCommand } from '../run.js';
|
|
6
|
+
|
|
7
|
+
export const mcpKeyCommand = {
|
|
8
|
+
command: 'mcp-key <subcommand>',
|
|
9
|
+
describe: 'Manage MCP API keys (vel_live_* keys for AI skill access)',
|
|
10
|
+
builder: (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.command(mcpKeyListCommand)
|
|
13
|
+
.command(mcpKeyCreateCommand)
|
|
14
|
+
.command(mcpKeyRevokeCommand)
|
|
15
|
+
.command(mcpKeyRotateCommand)
|
|
16
|
+
.command(mcpKeyInstallCommand)
|
|
17
|
+
.demandCommand(1, 'Specify a subcommand: list, create, revoke, rotate, install'),
|
|
18
|
+
handler: () => {},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const mcpKeyListCommand = {
|
|
22
|
+
command: 'list',
|
|
23
|
+
describe: 'List MCP API keys for your site',
|
|
24
|
+
handler: runCommand(async () => {
|
|
25
|
+
const keys = await get('/McpApiKeys');
|
|
26
|
+
if (!keys?.length) { console.log('No MCP keys found.'); return; }
|
|
27
|
+
console.log(`Found ${keys.length} key(s):\n`);
|
|
28
|
+
for (const k of keys) {
|
|
29
|
+
const used = k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleDateString() : 'never';
|
|
30
|
+
const expires = k.expiresAt ? ` expires ${new Date(k.expiresAt).toLocaleDateString()}` : '';
|
|
31
|
+
console.log(` [${k.id}] ${k.label} prefix=${k.keyPrefix} ${k.isActive ? 'active' : 'revoked'} last used=${used}${expires}`);
|
|
32
|
+
}
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const mcpKeyCreateCommand = {
|
|
37
|
+
command: 'create',
|
|
38
|
+
describe: 'Create a new MCP API key',
|
|
39
|
+
builder: (y) =>
|
|
40
|
+
y
|
|
41
|
+
.option('label', {
|
|
42
|
+
describe: 'Human-readable label (e.g. "Claude Code")',
|
|
43
|
+
type: 'string',
|
|
44
|
+
demandOption: true,
|
|
45
|
+
})
|
|
46
|
+
.option('expires', {
|
|
47
|
+
describe: 'Expiry date in ISO 8601 format (e.g. 2027-01-01)',
|
|
48
|
+
type: 'string',
|
|
49
|
+
}),
|
|
50
|
+
|
|
51
|
+
handler: runCommand(async (argv) => {
|
|
52
|
+
const payload = { label: argv.label };
|
|
53
|
+
if (argv.expires) payload.expiresAt = new Date(argv.expires).toISOString();
|
|
54
|
+
|
|
55
|
+
const result = await post('/McpApiKeys', payload);
|
|
56
|
+
console.log(`MCP key created: ${result.label}`);
|
|
57
|
+
console.log(`\n Key: ${result.rawKey}`);
|
|
58
|
+
console.log(`\n ${result.warning ?? 'Copy this key now — it will not be shown again.'}`);
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const mcpKeyRevokeCommand = {
|
|
63
|
+
command: 'revoke <id>',
|
|
64
|
+
describe: 'Revoke an MCP API key by ID',
|
|
65
|
+
handler: runCommand(async (argv) => {
|
|
66
|
+
await del(`/McpApiKeys/${argv.id}`);
|
|
67
|
+
console.log(`Key ${argv.id} revoked.`);
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const mcpKeyInstallCommand = {
|
|
72
|
+
command: 'install',
|
|
73
|
+
describe: 'Create an MCP key and write it to ~/.claude/settings.json automatically',
|
|
74
|
+
builder: (y) =>
|
|
75
|
+
y
|
|
76
|
+
.option('label', {
|
|
77
|
+
describe: 'Label for the key',
|
|
78
|
+
type: 'string',
|
|
79
|
+
default: 'Claude Code',
|
|
80
|
+
})
|
|
81
|
+
.option('api', {
|
|
82
|
+
describe: 'Velaro API base URL (defaults to production)',
|
|
83
|
+
type: 'string',
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
handler: runCommand(async (argv) => {
|
|
87
|
+
const payload = { label: argv.label };
|
|
88
|
+
const result = await post('/McpApiKeys', payload);
|
|
89
|
+
const rawKey = result.rawKey;
|
|
90
|
+
|
|
91
|
+
// Write to ~/.claude/settings.json
|
|
92
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
93
|
+
let settings = {};
|
|
94
|
+
if (fs.existsSync(settingsPath)) {
|
|
95
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const apiBase = argv.api || 'https://api-admin-us-east.velaro.com';
|
|
99
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
100
|
+
settings.mcpServers.velaro = {
|
|
101
|
+
command: 'npx',
|
|
102
|
+
args: ['-y', '@velaro/mcp-server'],
|
|
103
|
+
env: {
|
|
104
|
+
VELARO_MCP_KEY: rawKey,
|
|
105
|
+
VELARO_ADMIN_API: apiBase,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
110
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
111
|
+
|
|
112
|
+
console.log(`✅ MCP key created: ${result.label} (${result.keyPrefix}...)`);
|
|
113
|
+
console.log(`✅ Written to: ${settingsPath}`);
|
|
114
|
+
console.log(`\nRestart Claude Code to pick up the new MCP server.`);
|
|
115
|
+
console.log(`\nTo verify: run 'velaro mcp-key list' or check Settings → Developer → MCP Keys in the admin portal.`);
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const mcpKeyRotateCommand = {
|
|
120
|
+
command: 'rotate <id>',
|
|
121
|
+
describe: 'Revoke an existing key and issue a replacement in one step',
|
|
122
|
+
builder: (y) =>
|
|
123
|
+
y
|
|
124
|
+
.positional('id', { type: 'number', describe: 'ID of the key to revoke (get it from mcp-key list)' })
|
|
125
|
+
.option('label', {
|
|
126
|
+
describe: 'Label for the replacement key (defaults to the old key\'s label)',
|
|
127
|
+
type: 'string',
|
|
128
|
+
})
|
|
129
|
+
.option('expires', {
|
|
130
|
+
describe: 'Expiry date for the new key in ISO 8601 format (e.g. 2027-01-01)',
|
|
131
|
+
type: 'string',
|
|
132
|
+
}),
|
|
133
|
+
|
|
134
|
+
handler: runCommand(async (argv) => {
|
|
135
|
+
const keys = await get('/McpApiKeys');
|
|
136
|
+
const old = keys?.find(k => k.id === argv.id);
|
|
137
|
+
if (!old) throw new Error(`Key ID ${argv.id} not found. Run 'velaro mcp-key list' to see your keys.`);
|
|
138
|
+
|
|
139
|
+
const label = argv.label || old.label;
|
|
140
|
+
|
|
141
|
+
await del(`/McpApiKeys/${argv.id}`);
|
|
142
|
+
console.log(`Revoked: [${argv.id}] ${old.label} (${old.keyPrefix}...)`);
|
|
143
|
+
|
|
144
|
+
const payload = { label };
|
|
145
|
+
if (argv.expires) payload.expiresAt = new Date(argv.expires).toISOString();
|
|
146
|
+
const result = await post('/McpApiKeys', payload);
|
|
147
|
+
|
|
148
|
+
console.log(`Replaced: [${result.id}] ${result.label}`);
|
|
149
|
+
console.log(`\n New key: ${result.rawKey}`);
|
|
150
|
+
console.log(`\n ${result.warning ?? 'Copy this key now — it will not be shown again.'}`);
|
|
151
|
+
}),
|
|
152
|
+
};
|
package/lib/commands/rule.js
CHANGED
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
import { get, post, del } from '../api.js';
|
|
2
|
-
import { requireFeature } from '../subscription.js';
|
|
3
|
-
import { runCommand } from '../run.js';
|
|
4
|
-
|
|
5
|
-
export const ruleCommand = {
|
|
6
|
-
command: 'rule <subcommand>',
|
|
7
|
-
describe: 'Manage routing rules',
|
|
8
|
-
builder: (yargs) =>
|
|
9
|
-
yargs
|
|
10
|
-
.command(ruleListCommand)
|
|
11
|
-
.command(ruleCreateCommand)
|
|
12
|
-
.command(ruleDeleteCommand)
|
|
13
|
-
.demandCommand(1, 'Specify a subcommand: list, create, delete'),
|
|
14
|
-
handler: () => {},
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// ── list ───────────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
const ruleListCommand = {
|
|
20
|
-
command: 'list',
|
|
21
|
-
describe: 'List all routing rules',
|
|
22
|
-
handler: runCommand(async () => {
|
|
23
|
-
await requireFeature('enableWorkflowRules', 'Routing Rules');
|
|
24
|
-
|
|
25
|
-
const rules = await get('/Rules/List');
|
|
26
|
-
if (!rules?.length) { console.log('No routing rules found.'); return; }
|
|
27
|
-
|
|
28
|
-
const sorted = [...rules].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
29
|
-
const idW = Math.max(...sorted.map(r => String(r.id).length + 2));
|
|
30
|
-
const priW = Math.max('pri'.length, ...sorted.map(r => String(r.priority ?? 0).length));
|
|
31
|
-
const nameW = Math.max('name'.length, ...sorted.map(r => (r.name ?? r.trigger ?? '—').length));
|
|
32
|
-
|
|
33
|
-
console.log(`\n${'id'.padStart(idW)} ${'pri'.padEnd(priW)} ${'name'.padEnd(nameW)} trigger`);
|
|
34
|
-
console.log(`${'-'.repeat(idW)} ${'-'.repeat(priW)} ${'-'.repeat(nameW)} -------`);
|
|
35
|
-
for (const r of sorted) {
|
|
36
|
-
const id = `[${r.id}]`.padStart(idW);
|
|
37
|
-
const pri = String(r.priority ?? 0).padEnd(priW);
|
|
38
|
-
const name = (r.name ?? r.trigger ?? '—').padEnd(nameW);
|
|
39
|
-
console.log(`${id} ${pri} ${name} ${r.trigger ?? '—'}`);
|
|
40
|
-
}
|
|
41
|
-
console.log(`\n${sorted.length} rule(s)`);
|
|
42
|
-
}),
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// ── create ─────────────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
const ruleCreateCommand = {
|
|
48
|
-
command: 'create',
|
|
49
|
-
describe: 'Create a routing rule',
|
|
50
|
-
builder: (y) =>
|
|
51
|
-
y
|
|
52
|
-
.option('name', { type: 'string', demandOption: true, describe: 'Rule name' })
|
|
53
|
-
.option('trigger', { type: 'string', demandOption: true, describe: 'Trigger type (e.g. pageUrl, country)' })
|
|
54
|
-
.option('value', { type: 'string', demandOption: true, describe: 'Trigger value to match' })
|
|
55
|
-
.option('team-id', { type: 'number', demandOption: true, describe: 'Team ID to route to' })
|
|
56
|
-
.option('priority', { type: 'number', default: 0, describe: 'Rule priority (lower = higher priority)' }),
|
|
57
|
-
|
|
58
|
-
handler: runCommand(async (argv) => {
|
|
59
|
-
await requireFeature('enableWorkflowRules', 'Routing Rules');
|
|
60
|
-
|
|
61
|
-
const result = await post('/Rules', {
|
|
62
|
-
name: argv.name,
|
|
63
|
-
trigger: argv.trigger,
|
|
64
|
-
value: argv.value,
|
|
65
|
-
teamId: argv['team-id'],
|
|
66
|
-
priority: argv.priority,
|
|
67
|
-
isEnabled: true,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
console.log(`Rule created: [${result.id}] ${result.name}`);
|
|
71
|
-
}),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// ── delete ─────────────────────────────────────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
const ruleDeleteCommand = {
|
|
77
|
-
command: 'delete <id>',
|
|
78
|
-
describe: 'Delete a routing rule',
|
|
79
|
-
builder: (y) => y.positional('id', { type: 'number', describe: 'Rule ID' }),
|
|
80
|
-
handler: runCommand(async (argv) => {
|
|
81
|
-
await requireFeature('enableWorkflowRules', 'Routing Rules');
|
|
82
|
-
await del(`/Rules/${argv.id}`);
|
|
83
|
-
console.log(`Rule ${argv.id} deleted.`);
|
|
84
|
-
}),
|
|
85
|
-
};
|
|
1
|
+
import { get, post, del } from '../api.js';
|
|
2
|
+
import { requireFeature } from '../subscription.js';
|
|
3
|
+
import { runCommand } from '../run.js';
|
|
4
|
+
|
|
5
|
+
export const ruleCommand = {
|
|
6
|
+
command: 'rule <subcommand>',
|
|
7
|
+
describe: 'Manage routing rules',
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.command(ruleListCommand)
|
|
11
|
+
.command(ruleCreateCommand)
|
|
12
|
+
.command(ruleDeleteCommand)
|
|
13
|
+
.demandCommand(1, 'Specify a subcommand: list, create, delete'),
|
|
14
|
+
handler: () => {},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ── list ───────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const ruleListCommand = {
|
|
20
|
+
command: 'list',
|
|
21
|
+
describe: 'List all routing rules',
|
|
22
|
+
handler: runCommand(async () => {
|
|
23
|
+
await requireFeature('enableWorkflowRules', 'Routing Rules');
|
|
24
|
+
|
|
25
|
+
const rules = await get('/Rules/List');
|
|
26
|
+
if (!rules?.length) { console.log('No routing rules found.'); return; }
|
|
27
|
+
|
|
28
|
+
const sorted = [...rules].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
29
|
+
const idW = Math.max(...sorted.map(r => String(r.id).length + 2));
|
|
30
|
+
const priW = Math.max('pri'.length, ...sorted.map(r => String(r.priority ?? 0).length));
|
|
31
|
+
const nameW = Math.max('name'.length, ...sorted.map(r => (r.name ?? r.trigger ?? '—').length));
|
|
32
|
+
|
|
33
|
+
console.log(`\n${'id'.padStart(idW)} ${'pri'.padEnd(priW)} ${'name'.padEnd(nameW)} trigger`);
|
|
34
|
+
console.log(`${'-'.repeat(idW)} ${'-'.repeat(priW)} ${'-'.repeat(nameW)} -------`);
|
|
35
|
+
for (const r of sorted) {
|
|
36
|
+
const id = `[${r.id}]`.padStart(idW);
|
|
37
|
+
const pri = String(r.priority ?? 0).padEnd(priW);
|
|
38
|
+
const name = (r.name ?? r.trigger ?? '—').padEnd(nameW);
|
|
39
|
+
console.log(`${id} ${pri} ${name} ${r.trigger ?? '—'}`);
|
|
40
|
+
}
|
|
41
|
+
console.log(`\n${sorted.length} rule(s)`);
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── create ─────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const ruleCreateCommand = {
|
|
48
|
+
command: 'create',
|
|
49
|
+
describe: 'Create a routing rule',
|
|
50
|
+
builder: (y) =>
|
|
51
|
+
y
|
|
52
|
+
.option('name', { type: 'string', demandOption: true, describe: 'Rule name' })
|
|
53
|
+
.option('trigger', { type: 'string', demandOption: true, describe: 'Trigger type (e.g. pageUrl, country)' })
|
|
54
|
+
.option('value', { type: 'string', demandOption: true, describe: 'Trigger value to match' })
|
|
55
|
+
.option('team-id', { type: 'number', demandOption: true, describe: 'Team ID to route to' })
|
|
56
|
+
.option('priority', { type: 'number', default: 0, describe: 'Rule priority (lower = higher priority)' }),
|
|
57
|
+
|
|
58
|
+
handler: runCommand(async (argv) => {
|
|
59
|
+
await requireFeature('enableWorkflowRules', 'Routing Rules');
|
|
60
|
+
|
|
61
|
+
const result = await post('/Rules', {
|
|
62
|
+
name: argv.name,
|
|
63
|
+
trigger: argv.trigger,
|
|
64
|
+
value: argv.value,
|
|
65
|
+
teamId: argv['team-id'],
|
|
66
|
+
priority: argv.priority,
|
|
67
|
+
isEnabled: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log(`Rule created: [${result.id}] ${result.name}`);
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ── delete ─────────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
const ruleDeleteCommand = {
|
|
77
|
+
command: 'delete <id>',
|
|
78
|
+
describe: 'Delete a routing rule',
|
|
79
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Rule ID' }),
|
|
80
|
+
handler: runCommand(async (argv) => {
|
|
81
|
+
await requireFeature('enableWorkflowRules', 'Routing Rules');
|
|
82
|
+
await del(`/Rules/${argv.id}`);
|
|
83
|
+
console.log(`Rule ${argv.id} deleted.`);
|
|
84
|
+
}),
|
|
85
|
+
};
|
package/lib/commands/workflow.js
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { get, post } from '../api.js';
|
|
2
|
-
import { requireFeature } from '../subscription.js';
|
|
3
|
-
import { runCommand } from '../run.js';
|
|
4
|
-
|
|
5
|
-
export const workflowCommand = {
|
|
6
|
-
command: 'workflow <subcommand>',
|
|
7
|
-
describe: 'Manage automation workflows',
|
|
8
|
-
builder: (yargs) =>
|
|
9
|
-
yargs
|
|
10
|
-
.command(workflowListCommand)
|
|
11
|
-
.command(workflowGetCommand)
|
|
12
|
-
.command(workflowEnableCommand)
|
|
13
|
-
.command(workflowDisableCommand)
|
|
14
|
-
.demandCommand(1, 'Specify a subcommand: list, get, enable, disable'),
|
|
15
|
-
handler: () => {},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// ── list ───────────────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
const workflowListCommand = {
|
|
21
|
-
command: 'list',
|
|
22
|
-
describe: 'List all workflows',
|
|
23
|
-
handler: runCommand(async () => {
|
|
24
|
-
await requireFeature('enableAutomation', 'Workflows');
|
|
25
|
-
|
|
26
|
-
const workflows = await get('/Workflows/List');
|
|
27
|
-
const rows = (workflows ?? []).filter(w => !w.isTemplate);
|
|
28
|
-
|
|
29
|
-
if (!rows.length) { console.log('No workflows found.'); return; }
|
|
30
|
-
|
|
31
|
-
const idW = Math.max(...rows.map(w => String(w.id).length + 2));
|
|
32
|
-
const triggerW = Math.max('trigger'.length, ...rows.map(w => (w.triggerType ?? '—').length));
|
|
33
|
-
|
|
34
|
-
console.log(`\n${'id'.padStart(idW)} status ${'trigger'.padEnd(triggerW)} name`);
|
|
35
|
-
console.log(`${'-'.repeat(idW)} -------- ${'-'.repeat(triggerW)} ----`);
|
|
36
|
-
for (const w of rows) {
|
|
37
|
-
const id = `[${w.id}]`.padStart(idW);
|
|
38
|
-
const status = w.enabled ? 'enabled ' : 'disabled';
|
|
39
|
-
const trigger = (w.triggerType ?? '—').padEnd(triggerW);
|
|
40
|
-
console.log(`${id} ${status} ${trigger} ${w.name}`);
|
|
41
|
-
}
|
|
42
|
-
console.log(`\n${rows.length} workflow(s)`);
|
|
43
|
-
}),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// ── get ────────────────────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
const workflowGetCommand = {
|
|
49
|
-
command: 'get <id>',
|
|
50
|
-
describe: 'Show workflow details',
|
|
51
|
-
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
52
|
-
handler: runCommand(async (argv) => {
|
|
53
|
-
await requireFeature('enableAutomation', 'Workflows');
|
|
54
|
-
|
|
55
|
-
const w = await get(`/Workflows/${argv.id}`);
|
|
56
|
-
if (!w) { console.error(`Workflow ${argv.id} not found.`); process.exit(1); }
|
|
57
|
-
|
|
58
|
-
console.log(`\n[${w.id}] ${w.name}`);
|
|
59
|
-
console.log(` Status: ${w.enabled ? 'enabled' : 'disabled'}`);
|
|
60
|
-
console.log(` Trigger: ${w.triggerType ?? '—'}`);
|
|
61
|
-
if (w.nodes?.length) {
|
|
62
|
-
console.log(` Nodes: ${w.nodes.length}`);
|
|
63
|
-
}
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// ── enable / disable ──────────────────────────────────────────────────────────
|
|
68
|
-
|
|
69
|
-
const workflowEnableCommand = {
|
|
70
|
-
command: 'enable <id>',
|
|
71
|
-
describe: 'Enable a workflow',
|
|
72
|
-
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
73
|
-
handler: runCommand(async (argv) => {
|
|
74
|
-
await requireFeature('enableAutomation', 'Workflows');
|
|
75
|
-
await post(`/Workflows/toggle/${argv.id}`, null);
|
|
76
|
-
console.log(`Workflow ${argv.id} enabled.`);
|
|
77
|
-
}),
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const workflowDisableCommand = {
|
|
81
|
-
command: 'disable <id>',
|
|
82
|
-
describe: 'Disable a workflow',
|
|
83
|
-
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
84
|
-
handler: runCommand(async (argv) => {
|
|
85
|
-
await requireFeature('enableAutomation', 'Workflows');
|
|
86
|
-
|
|
87
|
-
const w = await get(`/Workflows/${argv.id}`);
|
|
88
|
-
if (!w) { console.error(`Workflow ${argv.id} not found.`); process.exit(1); }
|
|
89
|
-
|
|
90
|
-
if (!w.enabled) {
|
|
91
|
-
console.log(`Workflow ${argv.id} is already disabled.`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
await post(`/Workflows/toggle/${argv.id}`, null);
|
|
96
|
-
console.log(`Workflow ${argv.id} disabled.`);
|
|
97
|
-
}),
|
|
98
|
-
};
|
|
1
|
+
import { get, post } from '../api.js';
|
|
2
|
+
import { requireFeature } from '../subscription.js';
|
|
3
|
+
import { runCommand } from '../run.js';
|
|
4
|
+
|
|
5
|
+
export const workflowCommand = {
|
|
6
|
+
command: 'workflow <subcommand>',
|
|
7
|
+
describe: 'Manage automation workflows',
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.command(workflowListCommand)
|
|
11
|
+
.command(workflowGetCommand)
|
|
12
|
+
.command(workflowEnableCommand)
|
|
13
|
+
.command(workflowDisableCommand)
|
|
14
|
+
.demandCommand(1, 'Specify a subcommand: list, get, enable, disable'),
|
|
15
|
+
handler: () => {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// ── list ───────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const workflowListCommand = {
|
|
21
|
+
command: 'list',
|
|
22
|
+
describe: 'List all workflows',
|
|
23
|
+
handler: runCommand(async () => {
|
|
24
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
25
|
+
|
|
26
|
+
const workflows = await get('/Workflows/List');
|
|
27
|
+
const rows = (workflows ?? []).filter(w => !w.isTemplate);
|
|
28
|
+
|
|
29
|
+
if (!rows.length) { console.log('No workflows found.'); return; }
|
|
30
|
+
|
|
31
|
+
const idW = Math.max(...rows.map(w => String(w.id).length + 2));
|
|
32
|
+
const triggerW = Math.max('trigger'.length, ...rows.map(w => (w.triggerType ?? '—').length));
|
|
33
|
+
|
|
34
|
+
console.log(`\n${'id'.padStart(idW)} status ${'trigger'.padEnd(triggerW)} name`);
|
|
35
|
+
console.log(`${'-'.repeat(idW)} -------- ${'-'.repeat(triggerW)} ----`);
|
|
36
|
+
for (const w of rows) {
|
|
37
|
+
const id = `[${w.id}]`.padStart(idW);
|
|
38
|
+
const status = w.enabled ? 'enabled ' : 'disabled';
|
|
39
|
+
const trigger = (w.triggerType ?? '—').padEnd(triggerW);
|
|
40
|
+
console.log(`${id} ${status} ${trigger} ${w.name}`);
|
|
41
|
+
}
|
|
42
|
+
console.log(`\n${rows.length} workflow(s)`);
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ── get ────────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const workflowGetCommand = {
|
|
49
|
+
command: 'get <id>',
|
|
50
|
+
describe: 'Show workflow details',
|
|
51
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
52
|
+
handler: runCommand(async (argv) => {
|
|
53
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
54
|
+
|
|
55
|
+
const w = await get(`/Workflows/${argv.id}`);
|
|
56
|
+
if (!w) { console.error(`Workflow ${argv.id} not found.`); process.exit(1); }
|
|
57
|
+
|
|
58
|
+
console.log(`\n[${w.id}] ${w.name}`);
|
|
59
|
+
console.log(` Status: ${w.enabled ? 'enabled' : 'disabled'}`);
|
|
60
|
+
console.log(` Trigger: ${w.triggerType ?? '—'}`);
|
|
61
|
+
if (w.nodes?.length) {
|
|
62
|
+
console.log(` Nodes: ${w.nodes.length}`);
|
|
63
|
+
}
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ── enable / disable ──────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
const workflowEnableCommand = {
|
|
70
|
+
command: 'enable <id>',
|
|
71
|
+
describe: 'Enable a workflow',
|
|
72
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
73
|
+
handler: runCommand(async (argv) => {
|
|
74
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
75
|
+
await post(`/Workflows/toggle/${argv.id}`, null);
|
|
76
|
+
console.log(`Workflow ${argv.id} enabled.`);
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const workflowDisableCommand = {
|
|
81
|
+
command: 'disable <id>',
|
|
82
|
+
describe: 'Disable a workflow',
|
|
83
|
+
builder: (y) => y.positional('id', { type: 'number', describe: 'Workflow ID' }),
|
|
84
|
+
handler: runCommand(async (argv) => {
|
|
85
|
+
await requireFeature('enableAutomation', 'Workflows');
|
|
86
|
+
|
|
87
|
+
const w = await get(`/Workflows/${argv.id}`);
|
|
88
|
+
if (!w) { console.error(`Workflow ${argv.id} not found.`); process.exit(1); }
|
|
89
|
+
|
|
90
|
+
if (!w.enabled) {
|
|
91
|
+
console.log(`Workflow ${argv.id} is already disabled.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await post(`/Workflows/toggle/${argv.id}`, null);
|
|
96
|
+
console.log(`Workflow ${argv.id} disabled.`);
|
|
97
|
+
}),
|
|
98
|
+
};
|
package/lib/config.js
CHANGED
|
@@ -5,7 +5,9 @@ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
|
5
5
|
const CONFIG_DIR = join(homedir(), '.velaro');
|
|
6
6
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Production API URL — update when production messaging API is deployed.
|
|
9
|
+
// messaging.velaro.com is the chat widget host (Azure SWA), not the API.
|
|
10
|
+
export const DEFAULT_API_BASE = process.env.VELARO_API_BASE || 'https://velaro-messaging-api-staging.azurewebsites.net';
|
|
9
11
|
export const STAGING_API_BASE = process.env.VELARO_STAGING_API || 'https://velaro-messaging-api-staging.azurewebsites.net';
|
|
10
12
|
|
|
11
13
|
export function readConfig() {
|
package/lib/oauth.js
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
const TENANT_ID = '61de45b3-458d-49a5-913c-501247a6fe4f';
|
|
25
|
-
|
|
25
|
+
// CIAM tenant uses login.velaro.com, not login.microsoftonline.com
|
|
26
|
+
const AUTHORITY = `https://login.velaro.com/${TENANT_ID}`;
|
|
26
27
|
const CLIENT_ID = process.env.VELARO_CLI_CLIENT_ID || 'c0fdce54-e9b4-4427-a021-b2605855ff8b';
|
|
27
28
|
|
|
28
29
|
const SCOPE = [
|
package/lib/run.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { track } from './track.js';
|
|
2
|
+
|
|
3
|
+
/** Wraps a yargs command handler with consistent error reporting and usage tracking. */
|
|
2
4
|
export function runCommand(fn) {
|
|
3
5
|
return async (argv) => {
|
|
4
6
|
try {
|
|
5
7
|
await fn(argv);
|
|
8
|
+
// Fire-and-forget tracking after successful command — never awaited, never blocks
|
|
9
|
+
const action = argv._.join('.') || 'unknown';
|
|
10
|
+
track(action);
|
|
6
11
|
} catch (err) {
|
|
7
12
|
console.error(`Error: ${err.message}`);
|
|
8
13
|
process.exit(1);
|