@velaro/cli 0.3.0 → 0.5.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 +2 -0
- package/lib/api.js +17 -13
- package/lib/commands/env.js +45 -0
- package/lib/commands/kb.js +46 -1
- package/lib/commands/login.js +86 -60
- package/lib/commands/mcp-key.js +60 -1
- package/lib/commands/status.js +4 -2
- package/lib/config.js +83 -28
- package/package.json +1 -1
package/bin/velaro.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import yargs from 'yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
5
|
import { loginCommand, logoutCommand } from '../lib/commands/login.js';
|
|
6
|
+
import { envCommand } from '../lib/commands/env.js';
|
|
6
7
|
import { whoamiCommand } from '../lib/commands/whoami.js';
|
|
7
8
|
import { teamCommand } from '../lib/commands/team.js';
|
|
8
9
|
import { botCommand } from '../lib/commands/bot.js';
|
|
@@ -28,6 +29,7 @@ await yargs(hideBin(process.argv))
|
|
|
28
29
|
.command(loginCommand)
|
|
29
30
|
.command(logoutCommand)
|
|
30
31
|
.command(whoamiCommand)
|
|
32
|
+
.command(envCommand)
|
|
31
33
|
// Info
|
|
32
34
|
.command(siteCommand)
|
|
33
35
|
.command(checkCommand)
|
package/lib/api.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
import { readConfig, writeConfig } from './config.js';
|
|
2
|
-
import { refreshVelaroToken }
|
|
1
|
+
import { readConfig, writeConfig, getActiveEnv, setEnvCredentials, ENVS } from './config.js';
|
|
2
|
+
import { refreshVelaroToken } from './oauth.js';
|
|
3
3
|
|
|
4
4
|
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
5
5
|
|
|
6
|
-
export async function getCredentials() {
|
|
7
|
-
|
|
6
|
+
export async function getCredentials(envOverride) {
|
|
7
|
+
const cfg = readConfig();
|
|
8
|
+
const env = envOverride || cfg.activeEnv || 'prod';
|
|
9
|
+
let creds = cfg.envs?.[env];
|
|
8
10
|
|
|
9
11
|
if (!creds?.velaroToken) {
|
|
10
|
-
throw new Error(
|
|
12
|
+
throw new Error(`Not logged in to ${env}. Run: velaro login${env === 'staging' ? ' --staging' : ''}`);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
if (Date.now() + REFRESH_BUFFER_MS >= new Date(creds.velaroExpires).getTime()) {
|
|
14
16
|
if (!creds.entraRefreshToken) {
|
|
15
|
-
throw new Error(
|
|
17
|
+
throw new Error(`Session expired for ${env}. Run: velaro login${env === 'staging' ? ' --staging' : ''}`);
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const refreshed = await refreshVelaroToken({ ...creds, apiBase: creds.adminApiBase });
|
|
20
|
+
const updated = { ...creds, ...refreshed };
|
|
21
|
+
setEnvCredentials(env, updated);
|
|
22
|
+
creds = updated;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
return creds;
|
|
25
|
+
return { ...creds, env };
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
export async function request(method, path, body) {
|
|
25
|
-
const creds = await getCredentials();
|
|
28
|
+
export async function request(method, path, body, envOverride) {
|
|
29
|
+
const creds = await getCredentials(envOverride);
|
|
26
30
|
|
|
27
|
-
const res = await fetch(`${creds.
|
|
31
|
+
const res = await fetch(`${creds.adminApiBase}${path}`, {
|
|
28
32
|
method,
|
|
29
33
|
headers: {
|
|
30
34
|
Authorization: `Bearer ${creds.velaroToken}`,
|
|
@@ -35,7 +39,7 @@ export async function request(method, path, body) {
|
|
|
35
39
|
|
|
36
40
|
if (!res.ok) {
|
|
37
41
|
let msg = `${method} ${path} → ${res.status}`;
|
|
38
|
-
try { const t = await res.text(); if (t) msg += `: ${t}`; } catch { /*
|
|
42
|
+
try { const t = await res.text(); if (t) msg += `: ${t}`; } catch { /* status already captured */ }
|
|
39
43
|
throw new Error(msg);
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -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
|
+
};
|
package/lib/commands/kb.js
CHANGED
|
@@ -14,10 +14,55 @@ export const kbCommand = {
|
|
|
14
14
|
.command(qnaCommand)
|
|
15
15
|
.command(overrideCommand)
|
|
16
16
|
.command(contentCommand)
|
|
17
|
-
.
|
|
17
|
+
.command(reindexCommand)
|
|
18
|
+
.demandCommand(1, 'Specify a subcommand: article, qna, override, content, reindex'),
|
|
18
19
|
handler: () => {},
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// Reindex — re-embed all KB articles with the current embedding model
|
|
24
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const reindexCommand = {
|
|
27
|
+
command: 'reindex',
|
|
28
|
+
describe: 'Re-embed all KB articles into the vector index (use after embedding model upgrade)',
|
|
29
|
+
builder: (y) =>
|
|
30
|
+
y
|
|
31
|
+
.option('all', {
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
describe: 'Reindex ALL sites (Velaro staff only)',
|
|
34
|
+
default: false,
|
|
35
|
+
})
|
|
36
|
+
.option('site', {
|
|
37
|
+
type: 'number',
|
|
38
|
+
describe: 'Reindex a specific site by ID (Velaro staff only)',
|
|
39
|
+
}),
|
|
40
|
+
handler: runCommand(async (argv) => {
|
|
41
|
+
if (argv.all) {
|
|
42
|
+
console.log('Starting bulk reindex for all sites — this runs in the background.');
|
|
43
|
+
console.log('Watch server logs ([ReindexAllKb]) for progress.\n');
|
|
44
|
+
const result = await post('/DatabaseTool/ReindexAllKb', {});
|
|
45
|
+
console.log(result.message ?? 'Queued.');
|
|
46
|
+
} else if (argv.site) {
|
|
47
|
+
console.log(`Reindexing site ${argv.site}…`);
|
|
48
|
+
const result = await post(`/DatabaseTool/ReindexKb/${argv.site}`, {});
|
|
49
|
+
if (result.success) {
|
|
50
|
+
console.log(`✅ Done — ${result.articles} articles reindexed.`);
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`❌ Failed: ${result.message}`);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
console.log("Reindexing your site's KB articles\u2026");
|
|
56
|
+
const result = await post('/KBSearchIndex/reindex', {});
|
|
57
|
+
if (result.success === false) {
|
|
58
|
+
console.error(`❌ Failed: ${result.message}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`✅ Done — ${result.articles ?? result.count ?? 'all'} articles reindexed.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
|
|
21
66
|
// ────────────────────────────────────────────────────────────────────────────
|
|
22
67
|
// Q&A
|
|
23
68
|
// ────────────────────────────────────────────────────────────────────────────
|
package/lib/commands/login.js
CHANGED
|
@@ -1,60 +1,86 @@
|
|
|
1
|
-
import { requestDeviceCode, pollForToken, exchangeForVelaroToken } from '../oauth.js';
|
|
2
|
-
import { readConfig, writeConfig,
|
|
3
|
-
import { runCommand }
|
|
4
|
-
|
|
5
|
-
export const loginCommand = {
|
|
6
|
-
command: 'login',
|
|
7
|
-
describe: 'Authenticate with Velaro
|
|
8
|
-
builder: (y) =>
|
|
9
|
-
y
|
|
10
|
-
.option('staging', {
|
|
11
|
-
describe: '
|
|
12
|
-
type: 'boolean',
|
|
13
|
-
default: false,
|
|
14
|
-
})
|
|
15
|
-
.option('api', {
|
|
16
|
-
describe: 'Override the
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
console.log(
|
|
25
|
-
|
|
26
|
-
const deviceData = await requestDeviceCode();
|
|
27
|
-
|
|
28
|
-
console.log(` Open: ${deviceData.verification_uri}`);
|
|
29
|
-
console.log(` Enter: ${deviceData.user_code}\n`);
|
|
30
|
-
console.log('Waiting for you to complete login in your browser...');
|
|
31
|
-
|
|
32
|
-
const entraTokens = await pollForToken(deviceData.device_code, deviceData.interval ?? 5);
|
|
33
|
-
const velaroResult = await exchangeForVelaroToken(entraTokens.access_token,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
import { requestDeviceCode, pollForToken, exchangeForVelaroToken } from '../oauth.js';
|
|
2
|
+
import { readConfig, writeConfig, setEnvCredentials, getActiveEnv, ENVS } from '../config.js';
|
|
3
|
+
import { runCommand } from '../run.js';
|
|
4
|
+
|
|
5
|
+
export const loginCommand = {
|
|
6
|
+
command: 'login',
|
|
7
|
+
describe: 'Authenticate with Velaro (saves credentials for the chosen environment)',
|
|
8
|
+
builder: (y) =>
|
|
9
|
+
y
|
|
10
|
+
.option('staging', {
|
|
11
|
+
describe: 'Log in to the staging environment',
|
|
12
|
+
type: 'boolean',
|
|
13
|
+
default: false,
|
|
14
|
+
})
|
|
15
|
+
.option('api', {
|
|
16
|
+
describe: 'Override the admin API base URL (advanced)',
|
|
17
|
+
type: 'string',
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
handler: runCommand(async (argv) => {
|
|
21
|
+
const env = argv.staging || argv.api?.includes('staging') ? 'staging' : 'prod';
|
|
22
|
+
const adminApiBase = argv.api || ENVS[env].adminApiBase;
|
|
23
|
+
|
|
24
|
+
console.log(`Starting Velaro login (${env})...\n`);
|
|
25
|
+
|
|
26
|
+
const deviceData = await requestDeviceCode();
|
|
27
|
+
|
|
28
|
+
console.log(` Open: ${deviceData.verification_uri}`);
|
|
29
|
+
console.log(` Enter: ${deviceData.user_code}\n`);
|
|
30
|
+
console.log('Waiting for you to complete login in your browser...');
|
|
31
|
+
|
|
32
|
+
const entraTokens = await pollForToken(deviceData.device_code, deviceData.interval ?? 5);
|
|
33
|
+
const velaroResult = await exchangeForVelaroToken(entraTokens.access_token, adminApiBase);
|
|
34
|
+
|
|
35
|
+
setEnvCredentials(env, {
|
|
36
|
+
adminApiBase,
|
|
37
|
+
velaroToken: velaroResult.token.token,
|
|
38
|
+
velaroExpires: velaroResult.token.expires,
|
|
39
|
+
entraRefreshToken: entraTokens.refresh_token,
|
|
40
|
+
siteId: velaroResult.profile?.SiteId,
|
|
41
|
+
userName: velaroResult.profile?.Name,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Set as active if it's the only env logged in, or if explicitly chosen
|
|
45
|
+
const cfg = readConfig();
|
|
46
|
+
if (!cfg.activeEnv || Object.keys(cfg.envs || {}).length === 1) {
|
|
47
|
+
cfg.activeEnv = env;
|
|
48
|
+
writeConfig(cfg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`\n✅ Logged in to ${env} as ${velaroResult.profile?.Name ?? velaroResult.profile?.UserName}`);
|
|
52
|
+
console.log(` Site ID: ${velaroResult.profile?.SiteId}`);
|
|
53
|
+
console.log(` API: ${adminApiBase}`);
|
|
54
|
+
console.log('\nCredentials saved. Run "velaro env" to see all environments.');
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const logoutCommand = {
|
|
59
|
+
command: 'logout',
|
|
60
|
+
describe: 'Clear stored credentials',
|
|
61
|
+
builder: (y) =>
|
|
62
|
+
y.option('staging', { describe: 'Log out of staging only', type: 'boolean', default: false })
|
|
63
|
+
.option('all', { describe: 'Log out of all environments', type: 'boolean', default: false }),
|
|
64
|
+
|
|
65
|
+
handler: runCommand(async (argv) => {
|
|
66
|
+
const { readConfig, writeConfig, clearConfig } = await import('../config.js');
|
|
67
|
+
if (argv.all) {
|
|
68
|
+
clearConfig();
|
|
69
|
+
console.log('Logged out of all environments.');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const env = argv.staging ? 'staging' : getActiveEnv();
|
|
73
|
+
const cfg = readConfig();
|
|
74
|
+
if (cfg.envs?.[env]) {
|
|
75
|
+
delete cfg.envs[env];
|
|
76
|
+
if (cfg.activeEnv === env) {
|
|
77
|
+
const remaining = Object.keys(cfg.envs || {});
|
|
78
|
+
cfg.activeEnv = remaining[0] || 'prod';
|
|
79
|
+
}
|
|
80
|
+
writeConfig(cfg);
|
|
81
|
+
console.log(`Logged out of ${env}.`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`Not logged in to ${env}.`);
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
};
|
package/lib/commands/mcp-key.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
1
4
|
import { get, post, del } from '../api.js';
|
|
5
|
+
import { getActiveEnv, ENVS } from '../config.js';
|
|
2
6
|
import { runCommand } from '../run.js';
|
|
3
7
|
|
|
4
8
|
export const mcpKeyCommand = {
|
|
@@ -10,7 +14,8 @@ export const mcpKeyCommand = {
|
|
|
10
14
|
.command(mcpKeyCreateCommand)
|
|
11
15
|
.command(mcpKeyRevokeCommand)
|
|
12
16
|
.command(mcpKeyRotateCommand)
|
|
13
|
-
.
|
|
17
|
+
.command(mcpKeyInstallCommand)
|
|
18
|
+
.demandCommand(1, 'Specify a subcommand: list, create, revoke, rotate, install'),
|
|
14
19
|
handler: () => {},
|
|
15
20
|
};
|
|
16
21
|
|
|
@@ -64,6 +69,60 @@ const mcpKeyRevokeCommand = {
|
|
|
64
69
|
}),
|
|
65
70
|
};
|
|
66
71
|
|
|
72
|
+
const mcpKeyInstallCommand = {
|
|
73
|
+
command: 'install',
|
|
74
|
+
describe: 'Create an MCP key and write it to ~/.claude/settings.json automatically',
|
|
75
|
+
builder: (y) =>
|
|
76
|
+
y
|
|
77
|
+
.option('label', {
|
|
78
|
+
describe: 'Label for the key',
|
|
79
|
+
type: 'string',
|
|
80
|
+
})
|
|
81
|
+
.option('staging', {
|
|
82
|
+
describe: 'Install key for the staging environment',
|
|
83
|
+
type: 'boolean',
|
|
84
|
+
default: false,
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
handler: runCommand(async (argv) => {
|
|
88
|
+
const env = argv.staging ? 'staging' : getActiveEnv();
|
|
89
|
+
const apiBase = ENVS[env].adminApiBase;
|
|
90
|
+
const label = argv.label || (env === 'staging' ? 'Claude Code (Staging)' : 'Claude Code');
|
|
91
|
+
|
|
92
|
+
const payload = { label };
|
|
93
|
+
const result = await post('/McpApiKeys', payload);
|
|
94
|
+
const rawKey = result.rawKey;
|
|
95
|
+
|
|
96
|
+
// Write to ~/.claude/settings.json
|
|
97
|
+
// prod → entry named "velaro"
|
|
98
|
+
// staging → entry named "velaro-staging"
|
|
99
|
+
const serverName = env === 'prod' ? 'velaro' : `velaro-${env}`;
|
|
100
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
101
|
+
let settings = {};
|
|
102
|
+
if (fs.existsSync(settingsPath)) {
|
|
103
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
107
|
+
settings.mcpServers[serverName] = {
|
|
108
|
+
command: 'npx',
|
|
109
|
+
args: ['-y', '@velaro/mcp-server'],
|
|
110
|
+
env: {
|
|
111
|
+
VELARO_MCP_KEY: rawKey,
|
|
112
|
+
VELARO_ADMIN_API: apiBase,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
117
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
118
|
+
|
|
119
|
+
console.log(`✅ MCP key created: ${label} (${result.keyPrefix}...)`);
|
|
120
|
+
console.log(`✅ Written to settings.json as "${serverName}" → ${apiBase}`);
|
|
121
|
+
console.log(`\nRestart Claude Code to pick up the new MCP server.`);
|
|
122
|
+
console.log(`\nTo use in Claude Code: say "use ${serverName}" to target ${env}.`);
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
|
|
67
126
|
const mcpKeyRotateCommand = {
|
|
68
127
|
command: 'rotate <id>',
|
|
69
128
|
describe: 'Revoke an existing key and issue a replacement in one step',
|
package/lib/commands/status.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { readConfig,
|
|
1
|
+
import { readConfig, getActiveEnv, ENVS } from '../config.js';
|
|
2
2
|
|
|
3
3
|
export const statusCommand = {
|
|
4
4
|
command: 'status',
|
|
5
5
|
describe: 'Check Velaro API health',
|
|
6
6
|
handler: async () => {
|
|
7
|
-
const
|
|
7
|
+
const cfg = readConfig();
|
|
8
|
+
const env = getActiveEnv();
|
|
9
|
+
const apiBase = cfg.envs?.[env]?.adminApiBase ?? ENVS[env].adminApiBase;
|
|
8
10
|
process.stdout.write(`Checking ${apiBase}/Status ... `);
|
|
9
11
|
try {
|
|
10
12
|
const res = await fetch(`${apiBase}/Status`);
|
package/lib/config.js
CHANGED
|
@@ -1,28 +1,83 @@
|
|
|
1
|
-
import { homedir } from 'os';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
-
|
|
5
|
-
const CONFIG_DIR = join(homedir(), '.velaro');
|
|
6
|
-
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.velaro');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
export const ENVS = {
|
|
9
|
+
prod: {
|
|
10
|
+
adminApiBase: 'https://api-admin-us-east.velaro.com',
|
|
11
|
+
messagingApiBase: 'https://velaro-messaging-api-staging.azurewebsites.net',
|
|
12
|
+
},
|
|
13
|
+
staging: {
|
|
14
|
+
adminApiBase: 'https://velaro-admin-staging.azurewebsites.net',
|
|
15
|
+
messagingApiBase: 'https://velaro-messaging-api-staging.azurewebsites.net',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Legacy single-entry config — migrate transparently on first read.
|
|
20
|
+
function migrate(raw) {
|
|
21
|
+
if (raw.envs) return raw; // already multi-env
|
|
22
|
+
const apiBase = raw.apiBase || '';
|
|
23
|
+
const env = apiBase.includes('staging') ? 'staging' : 'prod';
|
|
24
|
+
return {
|
|
25
|
+
activeEnv: env,
|
|
26
|
+
envs: {
|
|
27
|
+
[env]: {
|
|
28
|
+
adminApiBase: ENVS[env].adminApiBase,
|
|
29
|
+
messagingApiBase: ENVS[env].messagingApiBase,
|
|
30
|
+
velaroToken: raw.velaroToken,
|
|
31
|
+
velaroExpires: raw.velaroExpires,
|
|
32
|
+
entraRefreshToken: raw.entraRefreshToken,
|
|
33
|
+
siteId: raw.siteId,
|
|
34
|
+
userName: raw.userName,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
_lastUpdateCheck: raw._lastUpdateCheck,
|
|
38
|
+
_latestVersion: raw._latestVersion,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function readConfig() {
|
|
43
|
+
try {
|
|
44
|
+
const raw = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
45
|
+
return migrate(raw);
|
|
46
|
+
} catch {
|
|
47
|
+
return { activeEnv: 'prod', envs: {} };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function writeConfig(data) {
|
|
52
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
53
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getActiveEnv() {
|
|
57
|
+
return readConfig().activeEnv || 'prod';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function setActiveEnv(env) {
|
|
61
|
+
if (!ENVS[env]) throw new Error(`Unknown environment "${env}". Use: prod, staging`);
|
|
62
|
+
const cfg = readConfig();
|
|
63
|
+
cfg.activeEnv = env;
|
|
64
|
+
writeConfig(cfg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getEnvCredentials(env) {
|
|
68
|
+
const cfg = readConfig();
|
|
69
|
+
return cfg.envs?.[env] || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function setEnvCredentials(env, creds) {
|
|
73
|
+
if (!ENVS[env]) throw new Error(`Unknown environment "${env}". Use: prod, staging`);
|
|
74
|
+
const cfg = readConfig();
|
|
75
|
+
cfg.envs = cfg.envs || {};
|
|
76
|
+
cfg.envs[env] = { ...ENVS[env], ...creds };
|
|
77
|
+
cfg.activeEnv = cfg.activeEnv || env;
|
|
78
|
+
writeConfig(cfg);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function clearConfig() {
|
|
82
|
+
writeConfig({ activeEnv: 'prod', envs: {} });
|
|
83
|
+
}
|