@velaro/cli 0.2.0 → 0.3.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.
@@ -1,63 +1,100 @@
1
- import { get, post, del } from '../api.js';
2
- import { runCommand } from '../run.js';
3
-
4
- export const mcpKeyCommand = {
5
- command: 'mcp-key <subcommand>',
6
- describe: 'Manage MCP API keys (vel_live_* keys for AI skill access)',
7
- builder: (yargs) =>
8
- yargs
9
- .command(mcpKeyListCommand)
10
- .command(mcpKeyCreateCommand)
11
- .command(mcpKeyRevokeCommand)
12
- .demandCommand(1, 'Specify a subcommand: list, create, revoke'),
13
- handler: () => {},
14
- };
15
-
16
- const mcpKeyListCommand = {
17
- command: 'list',
18
- describe: 'List MCP API keys for your site',
19
- handler: runCommand(async () => {
20
- const keys = await get('/McpApiKeys');
21
- if (!keys?.length) { console.log('No MCP keys found.'); return; }
22
- console.log(`Found ${keys.length} key(s):\n`);
23
- for (const k of keys) {
24
- const used = k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleDateString() : 'never';
25
- console.log(` [${k.id}] ${k.label} prefix=${k.keyPrefix} ${k.isActive ? 'active' : 'revoked'} last used=${used}`);
26
- }
27
- }),
28
- };
29
-
30
- const mcpKeyCreateCommand = {
31
- command: 'create',
32
- describe: 'Create a new MCP API key',
33
- builder: (y) =>
34
- y
35
- .option('label', {
36
- describe: 'Human-readable label (e.g. "Claude Code")',
37
- type: 'string',
38
- demandOption: true,
39
- })
40
- .option('expires', {
41
- describe: 'Expiry date in ISO 8601 format (e.g. 2027-01-01)',
42
- type: 'string',
43
- }),
44
-
45
- handler: runCommand(async (argv) => {
46
- const payload = { label: argv.label };
47
- if (argv.expires) payload.expiresAt = new Date(argv.expires).toISOString();
48
-
49
- const result = await post('/McpApiKeys', payload);
50
- console.log(`MCP key created for: ${result.label}`);
51
- console.log(`\n Key: ${result.rawKey}`);
52
- console.log(`\n ${result.warning ?? 'Copy this key now — it will not be shown again.'}`);
53
- }),
54
- };
55
-
56
- const mcpKeyRevokeCommand = {
57
- command: 'revoke <id>',
58
- describe: 'Revoke an MCP API key by ID',
59
- handler: runCommand(async (argv) => {
60
- await del(`/McpApiKeys/${argv.id}`);
61
- console.log(`Key ${argv.id} revoked.`);
62
- }),
63
- };
1
+ import { get, post, del } from '../api.js';
2
+ import { runCommand } from '../run.js';
3
+
4
+ export const mcpKeyCommand = {
5
+ command: 'mcp-key <subcommand>',
6
+ describe: 'Manage MCP API keys (vel_live_* keys for AI skill access)',
7
+ builder: (yargs) =>
8
+ yargs
9
+ .command(mcpKeyListCommand)
10
+ .command(mcpKeyCreateCommand)
11
+ .command(mcpKeyRevokeCommand)
12
+ .command(mcpKeyRotateCommand)
13
+ .demandCommand(1, 'Specify a subcommand: list, create, revoke, rotate'),
14
+ handler: () => {},
15
+ };
16
+
17
+ const mcpKeyListCommand = {
18
+ command: 'list',
19
+ describe: 'List MCP API keys for your site',
20
+ handler: runCommand(async () => {
21
+ const keys = await get('/McpApiKeys');
22
+ if (!keys?.length) { console.log('No MCP keys found.'); return; }
23
+ console.log(`Found ${keys.length} key(s):\n`);
24
+ for (const k of keys) {
25
+ const used = k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleDateString() : 'never';
26
+ const expires = k.expiresAt ? ` expires ${new Date(k.expiresAt).toLocaleDateString()}` : '';
27
+ console.log(` [${k.id}] ${k.label} prefix=${k.keyPrefix} ${k.isActive ? 'active' : 'revoked'} last used=${used}${expires}`);
28
+ }
29
+ }),
30
+ };
31
+
32
+ const mcpKeyCreateCommand = {
33
+ command: 'create',
34
+ describe: 'Create a new MCP API key',
35
+ builder: (y) =>
36
+ y
37
+ .option('label', {
38
+ describe: 'Human-readable label (e.g. "Claude Code")',
39
+ type: 'string',
40
+ demandOption: true,
41
+ })
42
+ .option('expires', {
43
+ describe: 'Expiry date in ISO 8601 format (e.g. 2027-01-01)',
44
+ type: 'string',
45
+ }),
46
+
47
+ handler: runCommand(async (argv) => {
48
+ const payload = { label: argv.label };
49
+ if (argv.expires) payload.expiresAt = new Date(argv.expires).toISOString();
50
+
51
+ const result = await post('/McpApiKeys', payload);
52
+ console.log(`MCP key created: ${result.label}`);
53
+ console.log(`\n Key: ${result.rawKey}`);
54
+ console.log(`\n ${result.warning ?? 'Copy this key now — it will not be shown again.'}`);
55
+ }),
56
+ };
57
+
58
+ const mcpKeyRevokeCommand = {
59
+ command: 'revoke <id>',
60
+ describe: 'Revoke an MCP API key by ID',
61
+ handler: runCommand(async (argv) => {
62
+ await del(`/McpApiKeys/${argv.id}`);
63
+ console.log(`Key ${argv.id} revoked.`);
64
+ }),
65
+ };
66
+
67
+ const mcpKeyRotateCommand = {
68
+ command: 'rotate <id>',
69
+ describe: 'Revoke an existing key and issue a replacement in one step',
70
+ builder: (y) =>
71
+ y
72
+ .positional('id', { type: 'number', describe: 'ID of the key to revoke (get it from mcp-key list)' })
73
+ .option('label', {
74
+ describe: 'Label for the replacement key (defaults to the old key\'s label)',
75
+ type: 'string',
76
+ })
77
+ .option('expires', {
78
+ describe: 'Expiry date for the new key in ISO 8601 format (e.g. 2027-01-01)',
79
+ type: 'string',
80
+ }),
81
+
82
+ handler: runCommand(async (argv) => {
83
+ const keys = await get('/McpApiKeys');
84
+ const old = keys?.find(k => k.id === argv.id);
85
+ if (!old) throw new Error(`Key ID ${argv.id} not found. Run 'velaro mcp-key list' to see your keys.`);
86
+
87
+ const label = argv.label || old.label;
88
+
89
+ await del(`/McpApiKeys/${argv.id}`);
90
+ console.log(`Revoked: [${argv.id}] ${old.label} (${old.keyPrefix}...)`);
91
+
92
+ const payload = { label };
93
+ if (argv.expires) payload.expiresAt = new Date(argv.expires).toISOString();
94
+ const result = await post('/McpApiKeys', payload);
95
+
96
+ console.log(`Replaced: [${result.id}] ${result.label}`);
97
+ console.log(`\n New key: ${result.rawKey}`);
98
+ console.log(`\n ${result.warning ?? 'Copy this key now — it will not be shown again.'}`);
99
+ }),
100
+ };
@@ -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
+ };
@@ -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
- export const DEFAULT_API_BASE = process.env.VELARO_API_BASE || 'https://messaging.velaro.com';
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
- const AUTHORITY = `https://login.microsoftonline.com/${TENANT_ID}`;
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
- /** Wraps a yargs command handler with consistent error reporting. */
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);
package/lib/track.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Fire-and-forget CLI usage tracker.
3
+ * Posts a single event to /ToolUsage/cli after each command.
4
+ * Never throws, never blocks — silently dropped on any failure.
5
+ */
6
+ import { readConfig } from './config.js';
7
+ import { createRequire } from 'module';
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const { version: CLI_VERSION } = require('../package.json');
11
+
12
+ /**
13
+ * Track a CLI action. Call after a command succeeds.
14
+ * @param {string} action e.g. "login", "bot.push", "workflow.pull"
15
+ */
16
+ export function track(action) {
17
+ setImmediate(async () => {
18
+ try {
19
+ const creds = readConfig();
20
+ if (!creds?.velaroToken || !creds?.apiBase) return;
21
+
22
+ await fetch(`${creds.apiBase}/ToolUsage/cli`, {
23
+ method: 'POST',
24
+ headers: {
25
+ Authorization: `Bearer ${creds.velaroToken}`,
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ body: JSON.stringify({ action, cliVersion: CLI_VERSION }),
29
+ signal: AbortSignal.timeout(3000),
30
+ });
31
+ } catch {
32
+ // Silently drop — tracking must never surface errors to the user
33
+ }
34
+ });
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Velaro Workspace v20 — command-line interface for managing bots, knowledge base ingestion, and MCP API keys.",
5
5
  "type": "module",
6
6
  "bin": {