@vibe-assurance/cli 1.6.0 → 1.7.1
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/vibe.js +4 -0
- package/package.json +2 -2
- package/src/api/client.js +32 -7
- package/src/commands/env.js +211 -0
- package/src/commands/login.js +22 -8
- package/src/commands/whoami.js +19 -3
- package/src/config/credentials.js +50 -5
- package/src/config/environments.js +73 -0
package/bin/vibe.js
CHANGED
|
@@ -14,6 +14,7 @@ const whoami = require('../src/commands/whoami');
|
|
|
14
14
|
const mcpServer = require('../src/commands/mcp-server');
|
|
15
15
|
const setupClaude = require('../src/commands/setup-claude');
|
|
16
16
|
const { registerProjectCommands } = require('../src/commands/projects');
|
|
17
|
+
const { registerEnvCommands } = require('../src/commands/env'); // CR-2026-061
|
|
17
18
|
|
|
18
19
|
program
|
|
19
20
|
.name('vibe')
|
|
@@ -48,4 +49,7 @@ program
|
|
|
48
49
|
// CR-2026-043: Register project management commands
|
|
49
50
|
registerProjectCommands(program);
|
|
50
51
|
|
|
52
|
+
// CR-2026-061: Register environment management commands
|
|
53
|
+
registerEnvCommands(program);
|
|
54
|
+
|
|
51
55
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-assurance/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Vibe Assurance CLI - Connect AI coding agents to your governance platform via MCP",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"author": "Vibe Assurance",
|
|
23
23
|
"license": "MIT",
|
|
24
|
-
"homepage": "https://
|
|
24
|
+
"homepage": "https://vibeassurance.app/docs-cli.html",
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=18.0.0"
|
|
27
27
|
},
|
package/src/api/client.js
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const { getCredentials, storeCredentials, deleteCredentials, getProjectId } = require('../config/credentials');
|
|
2
|
+
const { getCredentials, storeCredentials, deleteCredentials, getProjectId, getEnvironment } = require('../config/credentials');
|
|
3
|
+
const { getEnvironmentUrl, DEFAULT_ENVIRONMENT } = require('../config/environments');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* CR-2026-061: Get API base URL
|
|
7
|
+
* Priority: VIBE_API_URL env var > stored environment > default (prod)
|
|
8
|
+
* @returns {Promise<string>} API base URL
|
|
9
|
+
*/
|
|
10
|
+
async function getApiBaseUrl() {
|
|
11
|
+
// Environment variable takes precedence (backward compatibility)
|
|
12
|
+
if (process.env.VIBE_API_URL) {
|
|
13
|
+
return process.env.VIBE_API_URL;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Use stored environment
|
|
17
|
+
const envName = await getEnvironment();
|
|
18
|
+
return getEnvironmentUrl(envName);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// For backward compatibility - synchronous version for simple cases
|
|
22
|
+
// Uses env var or default, doesn't read stored credentials
|
|
23
|
+
const API_BASE_URL = process.env.VIBE_API_URL || getEnvironmentUrl(DEFAULT_ENVIRONMENT);
|
|
6
24
|
|
|
7
25
|
/**
|
|
8
26
|
* Create an authenticated axios instance with token refresh handling
|
|
9
27
|
* CR-2026-043: Now includes X-Project-Id header for project scoping
|
|
28
|
+
* CR-2026-061: Uses stored environment for API URL
|
|
10
29
|
* @returns {Promise<import('axios').AxiosInstance>}
|
|
11
30
|
*/
|
|
12
31
|
async function createClient() {
|
|
@@ -15,13 +34,16 @@ async function createClient() {
|
|
|
15
34
|
throw new Error('Not authenticated. Run: vibe login');
|
|
16
35
|
}
|
|
17
36
|
|
|
37
|
+
// CR-2026-061: Get API URL from stored environment
|
|
38
|
+
const apiBaseUrl = await getApiBaseUrl();
|
|
39
|
+
|
|
18
40
|
// CR-2026-043: Get current project ID for scoping
|
|
19
41
|
const projectId = await getProjectId();
|
|
20
42
|
|
|
21
43
|
const headers = {
|
|
22
44
|
'Authorization': `Bearer ${creds.accessToken}`,
|
|
23
45
|
'Content-Type': 'application/json',
|
|
24
|
-
'User-Agent': 'vibe-cli/1.
|
|
46
|
+
'User-Agent': 'vibe-cli/1.7.0'
|
|
25
47
|
};
|
|
26
48
|
|
|
27
49
|
// CR-2026-043: Add project header if available
|
|
@@ -30,7 +52,7 @@ async function createClient() {
|
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
const client = axios.create({
|
|
33
|
-
baseURL:
|
|
55
|
+
baseURL: apiBaseUrl, // CR-2026-061: Dynamic URL
|
|
34
56
|
headers,
|
|
35
57
|
timeout: 30000 // 30 second timeout
|
|
36
58
|
});
|
|
@@ -73,11 +95,13 @@ async function createClient() {
|
|
|
73
95
|
|
|
74
96
|
/**
|
|
75
97
|
* Refresh access token using refresh token
|
|
98
|
+
* CR-2026-061: Uses dynamic API URL
|
|
76
99
|
* @param {string} refreshTokenValue
|
|
77
100
|
* @returns {Promise<Object>} New token object
|
|
78
101
|
*/
|
|
79
102
|
async function refreshToken(refreshTokenValue) {
|
|
80
|
-
const
|
|
103
|
+
const apiBaseUrl = await getApiBaseUrl(); // CR-2026-061
|
|
104
|
+
const response = await axios.post(`${apiBaseUrl}/api/auth/refresh`, {
|
|
81
105
|
refreshToken: refreshTokenValue
|
|
82
106
|
});
|
|
83
107
|
|
|
@@ -139,5 +163,6 @@ module.exports = {
|
|
|
139
163
|
post,
|
|
140
164
|
put,
|
|
141
165
|
delete: del,
|
|
142
|
-
API_BASE_URL
|
|
166
|
+
API_BASE_URL, // Sync version for backward compat
|
|
167
|
+
getApiBaseUrl // CR-2026-061: Async version
|
|
143
168
|
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment management commands
|
|
3
|
+
* CR-2026-061: CLI Environment Switching with Production Default
|
|
4
|
+
*
|
|
5
|
+
* Security: dev/local environments are restricted to platform admins only.
|
|
6
|
+
* Regular users can only use production.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const ora = require('ora');
|
|
11
|
+
const { getEnvironment, setEnvironment, isAdmin, hasValidCredentials } = require('../config/credentials');
|
|
12
|
+
const {
|
|
13
|
+
ENVIRONMENTS,
|
|
14
|
+
getEnvironmentConfig,
|
|
15
|
+
isInternalOnly,
|
|
16
|
+
getEnvironmentNames
|
|
17
|
+
} = require('../config/environments');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* CR-2026-061: Show current environment
|
|
21
|
+
*/
|
|
22
|
+
async function showCurrentEnv() {
|
|
23
|
+
const spinner = ora('Checking environment...').start();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const currentEnv = await getEnvironment();
|
|
27
|
+
const config = getEnvironmentConfig(currentEnv);
|
|
28
|
+
const userIsAdmin = await isAdmin();
|
|
29
|
+
spinner.stop();
|
|
30
|
+
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(chalk.cyan('Current Environment:'));
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(` ${chalk.bold(config.name)} ${chalk.dim('(' + currentEnv + ')')}`);
|
|
35
|
+
console.log(` ${chalk.dim(config.url)}`);
|
|
36
|
+
|
|
37
|
+
if (config.internal) {
|
|
38
|
+
console.log(` ${chalk.yellow('⚠ Internal environment (admin only)')}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if VIBE_API_URL override is active
|
|
42
|
+
if (process.env.VIBE_API_URL) {
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(chalk.yellow('Note: VIBE_API_URL environment variable is set'));
|
|
45
|
+
console.log(chalk.dim(` Override URL: ${process.env.VIBE_API_URL}`));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('');
|
|
49
|
+
} catch (err) {
|
|
50
|
+
spinner.fail('Failed to get environment');
|
|
51
|
+
console.error(chalk.red(err.message));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* CR-2026-061: List all available environments
|
|
58
|
+
* Only shows internal environments to admins
|
|
59
|
+
*/
|
|
60
|
+
async function listEnvironments() {
|
|
61
|
+
const currentEnv = await getEnvironment();
|
|
62
|
+
const userIsAdmin = await isAdmin();
|
|
63
|
+
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(chalk.cyan('Available Environments:'));
|
|
66
|
+
console.log('');
|
|
67
|
+
|
|
68
|
+
for (const [name, config] of Object.entries(ENVIRONMENTS)) {
|
|
69
|
+
// Only show internal environments to admins
|
|
70
|
+
if (config.internal && !userIsAdmin) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isCurrent = name === currentEnv;
|
|
75
|
+
const prefix = isCurrent ? chalk.green('✓') : ' ';
|
|
76
|
+
const envName = isCurrent ? chalk.green.bold(name) : chalk.bold(name);
|
|
77
|
+
const label = config.internal ? chalk.yellow(' (admin only)') : '';
|
|
78
|
+
|
|
79
|
+
console.log(` ${prefix} ${envName}${label}`);
|
|
80
|
+
console.log(` ${chalk.dim(config.description)}`);
|
|
81
|
+
console.log(` ${chalk.dim(config.url)}`);
|
|
82
|
+
console.log('');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!userIsAdmin) {
|
|
86
|
+
console.log(chalk.dim(' Only production environment is available.'));
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* CR-2026-061: Switch to a specific environment
|
|
93
|
+
* Internal environments require admin role
|
|
94
|
+
* @param {string} envName - Environment name (prod, dev, local)
|
|
95
|
+
*/
|
|
96
|
+
async function switchEnvironment(envName) {
|
|
97
|
+
const config = getEnvironmentConfig(envName);
|
|
98
|
+
|
|
99
|
+
if (!config) {
|
|
100
|
+
console.error(chalk.red(`Unknown environment: ${envName}`));
|
|
101
|
+
console.log(chalk.dim('Available: prod'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if internal environment - require admin
|
|
106
|
+
if (isInternalOnly(envName)) {
|
|
107
|
+
// Must be logged in first
|
|
108
|
+
if (!await hasValidCredentials()) {
|
|
109
|
+
console.error(chalk.red('You must be logged in to switch environments.'));
|
|
110
|
+
console.log(chalk.dim('Run `vibe login` first.'));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Must be admin
|
|
115
|
+
if (!await isAdmin()) {
|
|
116
|
+
console.error(chalk.red('Access denied.'));
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(chalk.yellow('Internal environments are restricted to platform administrators.'));
|
|
119
|
+
console.log(chalk.dim('Contact your administrator if you need access.'));
|
|
120
|
+
console.log('');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const spinner = ora(`Switching to ${config.name}...`).start();
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await setEnvironment(envName);
|
|
129
|
+
spinner.succeed(`Switched to ${chalk.bold(config.name)}`);
|
|
130
|
+
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(` ${chalk.dim('API URL:')} ${config.url}`);
|
|
133
|
+
|
|
134
|
+
if (config.internal) {
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(chalk.yellow('⚠ You are now using an internal environment.'));
|
|
137
|
+
console.log(chalk.dim(' Run `vibe env prod` to switch back to production.'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(chalk.dim('Note: You may need to run `vibe login` to authenticate with this environment.'));
|
|
142
|
+
console.log('');
|
|
143
|
+
} catch (err) {
|
|
144
|
+
spinner.fail('Failed to switch environment');
|
|
145
|
+
console.error(chalk.red(err.message));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* CR-2026-061: Register environment commands with Commander
|
|
152
|
+
* @param {import('commander').Command} program - Commander program instance
|
|
153
|
+
*/
|
|
154
|
+
function registerEnvCommands(program) {
|
|
155
|
+
const env = program
|
|
156
|
+
.command('env')
|
|
157
|
+
.description('Manage API environment');
|
|
158
|
+
|
|
159
|
+
// vibe env (no subcommand) - show current
|
|
160
|
+
env
|
|
161
|
+
.action(async () => {
|
|
162
|
+
await showCurrentEnv();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// vibe env current
|
|
166
|
+
env
|
|
167
|
+
.command('current')
|
|
168
|
+
.description('Show current environment')
|
|
169
|
+
.action(async () => {
|
|
170
|
+
await showCurrentEnv();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// vibe env list
|
|
174
|
+
env
|
|
175
|
+
.command('list')
|
|
176
|
+
.description('List available environments')
|
|
177
|
+
.action(async () => {
|
|
178
|
+
await listEnvironments();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// vibe env prod
|
|
182
|
+
env
|
|
183
|
+
.command('prod')
|
|
184
|
+
.description('Switch to production environment')
|
|
185
|
+
.action(async () => {
|
|
186
|
+
await switchEnvironment('prod');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// vibe env dev (admin only - not shown in description)
|
|
190
|
+
env
|
|
191
|
+
.command('dev')
|
|
192
|
+
.description('Switch to development environment')
|
|
193
|
+
.action(async () => {
|
|
194
|
+
await switchEnvironment('dev');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// vibe env local (admin only - not shown in description)
|
|
198
|
+
env
|
|
199
|
+
.command('local')
|
|
200
|
+
.description('Switch to local environment')
|
|
201
|
+
.action(async () => {
|
|
202
|
+
await switchEnvironment('local');
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
registerEnvCommands,
|
|
208
|
+
showCurrentEnv,
|
|
209
|
+
listEnvironments,
|
|
210
|
+
switchEnvironment
|
|
211
|
+
};
|
package/src/commands/login.js
CHANGED
|
@@ -5,8 +5,9 @@ const chalk = require('chalk');
|
|
|
5
5
|
const ora = require('ora');
|
|
6
6
|
const inquirer = require('inquirer');
|
|
7
7
|
const axios = require('axios');
|
|
8
|
-
const { storeCredentials, hasValidCredentials, setProjectId, setUserInfo } = require('../config/credentials');
|
|
9
|
-
const {
|
|
8
|
+
const { storeCredentials, hasValidCredentials, setProjectId, setUserInfo, getEnvironment } = require('../config/credentials');
|
|
9
|
+
const { getApiBaseUrl } = require('../api/client');
|
|
10
|
+
const { getEnvironmentConfig } = require('../config/environments');
|
|
10
11
|
|
|
11
12
|
// Local callback server port (chosen to be unlikely to conflict)
|
|
12
13
|
const CALLBACK_PORT = 38274;
|
|
@@ -14,6 +15,7 @@ const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Login command - authenticates user via OAuth flow
|
|
18
|
+
* CR-2026-061: Uses dynamic environment URL
|
|
17
19
|
*/
|
|
18
20
|
async function login() {
|
|
19
21
|
// Check if already logged in
|
|
@@ -23,6 +25,13 @@ async function login() {
|
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
// CR-2026-061: Show current environment
|
|
29
|
+
const envName = await getEnvironment();
|
|
30
|
+
const envConfig = getEnvironmentConfig(envName);
|
|
31
|
+
if (envConfig.internal) {
|
|
32
|
+
console.log(chalk.yellow(`⚠ Logging in to ${envConfig.name} environment (${envConfig.url})`));
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
const spinner = ora('Opening browser for authentication...').start();
|
|
27
36
|
|
|
28
37
|
// Track server for cleanup
|
|
@@ -122,7 +131,8 @@ async function login() {
|
|
|
122
131
|
// Start listening and open browser
|
|
123
132
|
server.listen(CALLBACK_PORT, async () => {
|
|
124
133
|
try {
|
|
125
|
-
const
|
|
134
|
+
const apiBaseUrl = await getApiBaseUrl(); // CR-2026-061: Dynamic URL
|
|
135
|
+
const authUrl = `${apiBaseUrl}/api/auth/cli?callback=${encodeURIComponent(CALLBACK_URL)}`;
|
|
126
136
|
await open(authUrl);
|
|
127
137
|
spinner.text = 'Waiting for authentication in browser...';
|
|
128
138
|
} catch (err) {
|
|
@@ -172,21 +182,25 @@ async function selectProject() {
|
|
|
172
182
|
const { getCredentials } = require('../config/credentials');
|
|
173
183
|
const creds = await getCredentials();
|
|
174
184
|
|
|
185
|
+
// CR-2026-061: Get API URL from stored environment
|
|
186
|
+
const apiBaseUrl = await getApiBaseUrl();
|
|
187
|
+
|
|
175
188
|
// Fetch user profile which now includes projects (CR-2026-043 backend change)
|
|
176
|
-
const response = await axios.get(`${
|
|
189
|
+
const response = await axios.get(`${apiBaseUrl}/api/auth/me`, {
|
|
177
190
|
headers: {
|
|
178
191
|
'Authorization': `Bearer ${creds.accessToken}`,
|
|
179
192
|
'Content-Type': 'application/json'
|
|
180
193
|
}
|
|
181
194
|
});
|
|
182
195
|
|
|
183
|
-
const { username, email, projects, defaultProjectId } = response.data;
|
|
196
|
+
const { username, email, role, projects, defaultProjectId } = response.data;
|
|
184
197
|
spinner.stop();
|
|
185
198
|
|
|
186
|
-
// Store and display user info
|
|
199
|
+
// Store and display user info (CR-2026-061: now includes role for admin checks)
|
|
187
200
|
if (username && email) {
|
|
188
|
-
await setUserInfo(username, email);
|
|
189
|
-
|
|
201
|
+
await setUserInfo(username, email, role || 'user');
|
|
202
|
+
const roleLabel = role === 'admin' ? chalk.magenta(' [admin]') : '';
|
|
203
|
+
console.log(chalk.cyan(`\nSigned in as ${chalk.bold(username)} (${email})${roleLabel}`));
|
|
190
204
|
}
|
|
191
205
|
|
|
192
206
|
if (!projects || projects.length === 0) {
|
package/src/commands/whoami.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Whoami command - shows current authenticated user
|
|
3
|
+
* CR-2026-061: Added environment display
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
const chalk = require('chalk');
|
|
6
7
|
const ora = require('ora');
|
|
7
8
|
const axios = require('axios');
|
|
8
|
-
const { hasValidCredentials, getUserInfo, getCredentials, getProjectName } = require('../config/credentials');
|
|
9
|
-
const {
|
|
9
|
+
const { hasValidCredentials, getUserInfo, getCredentials, getProjectName, getEnvironment } = require('../config/credentials');
|
|
10
|
+
const { getApiBaseUrl } = require('../api/client');
|
|
11
|
+
const { getEnvironmentConfig } = require('../config/environments');
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Show current user info
|
|
@@ -24,11 +26,21 @@ async function whoami() {
|
|
|
24
26
|
const cachedUser = await getUserInfo();
|
|
25
27
|
const projectName = await getProjectName();
|
|
26
28
|
|
|
29
|
+
// CR-2026-061: Get current environment
|
|
30
|
+
const envName = await getEnvironment();
|
|
31
|
+
const envConfig = getEnvironmentConfig(envName);
|
|
32
|
+
const envLabel = envConfig.internal
|
|
33
|
+
? chalk.yellow(`${envConfig.name} (internal)`)
|
|
34
|
+
: chalk.green(envConfig.name);
|
|
35
|
+
|
|
27
36
|
if (cachedUser) {
|
|
28
37
|
console.log(chalk.cyan(`\nSigned in as ${chalk.bold(cachedUser.username)} (${cachedUser.email})`));
|
|
29
38
|
if (projectName) {
|
|
30
39
|
console.log(chalk.dim(`Current project: ${projectName}`));
|
|
31
40
|
}
|
|
41
|
+
// CR-2026-061: Show environment
|
|
42
|
+
console.log(`Environment: ${envLabel}`);
|
|
43
|
+
console.log(chalk.dim(` ${envConfig.url}`));
|
|
32
44
|
return;
|
|
33
45
|
}
|
|
34
46
|
|
|
@@ -37,7 +49,8 @@ async function whoami() {
|
|
|
37
49
|
|
|
38
50
|
try {
|
|
39
51
|
const creds = await getCredentials();
|
|
40
|
-
const
|
|
52
|
+
const apiBaseUrl = await getApiBaseUrl(); // CR-2026-061: Dynamic URL
|
|
53
|
+
const response = await axios.get(`${apiBaseUrl}/api/auth/me`, {
|
|
41
54
|
headers: {
|
|
42
55
|
'Authorization': `Bearer ${creds.accessToken}`,
|
|
43
56
|
'Content-Type': 'application/json'
|
|
@@ -52,6 +65,9 @@ async function whoami() {
|
|
|
52
65
|
if (projectName) {
|
|
53
66
|
console.log(chalk.dim(`Current project: ${projectName}`));
|
|
54
67
|
}
|
|
68
|
+
// CR-2026-061: Show environment
|
|
69
|
+
console.log(`Environment: ${envLabel}`);
|
|
70
|
+
console.log(chalk.dim(` ${envConfig.url}`));
|
|
55
71
|
} else {
|
|
56
72
|
console.log(chalk.yellow('Could not retrieve user info.'));
|
|
57
73
|
}
|
|
@@ -2,6 +2,7 @@ const keytar = require('keytar');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
|
+
const { DEFAULT_ENVIRONMENT } = require('./environments');
|
|
5
6
|
|
|
6
7
|
const SERVICE_NAME = 'vibe-assurance';
|
|
7
8
|
const ACCOUNT_NAME = 'auth-token';
|
|
@@ -158,14 +159,15 @@ async function setProjectId(projectId, projectName) {
|
|
|
158
159
|
|
|
159
160
|
/**
|
|
160
161
|
* Get stored user info
|
|
161
|
-
* @returns {Promise<{username: string, email: string}|null>} User info or null
|
|
162
|
+
* @returns {Promise<{username: string, email: string, role: string}|null>} User info or null
|
|
162
163
|
*/
|
|
163
164
|
async function getUserInfo() {
|
|
164
165
|
const creds = await getCredentials();
|
|
165
166
|
if (creds?.username && creds?.email) {
|
|
166
167
|
return {
|
|
167
168
|
username: creds.username,
|
|
168
|
-
email: creds.email
|
|
169
|
+
email: creds.email,
|
|
170
|
+
role: creds.role || 'user'
|
|
169
171
|
};
|
|
170
172
|
}
|
|
171
173
|
return null;
|
|
@@ -173,21 +175,61 @@ async function getUserInfo() {
|
|
|
173
175
|
|
|
174
176
|
/**
|
|
175
177
|
* Set user info (updates existing credentials)
|
|
178
|
+
* CR-2026-061: Now includes role for admin checks
|
|
176
179
|
* @param {string} username - Username to store
|
|
177
180
|
* @param {string} email - Email to store
|
|
181
|
+
* @param {string} role - User role ('user' or 'admin')
|
|
178
182
|
* @returns {Promise<void>}
|
|
179
183
|
*/
|
|
180
|
-
async function setUserInfo(username, email) {
|
|
184
|
+
async function setUserInfo(username, email, role = 'user') {
|
|
181
185
|
const creds = await getCredentials();
|
|
182
186
|
if (creds) {
|
|
183
187
|
await storeCredentials({
|
|
184
188
|
...creds,
|
|
185
189
|
username,
|
|
186
|
-
email
|
|
190
|
+
email,
|
|
191
|
+
role
|
|
187
192
|
});
|
|
188
193
|
}
|
|
189
194
|
}
|
|
190
195
|
|
|
196
|
+
/**
|
|
197
|
+
* CR-2026-061: Check if user is a platform admin
|
|
198
|
+
* @returns {Promise<boolean>} True if user is admin
|
|
199
|
+
*/
|
|
200
|
+
async function isAdmin() {
|
|
201
|
+
const creds = await getCredentials();
|
|
202
|
+
return creds?.role === 'admin';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* CR-2026-061: Get stored environment
|
|
207
|
+
* @returns {Promise<string>} Environment name (prod, dev, local)
|
|
208
|
+
*/
|
|
209
|
+
async function getEnvironment() {
|
|
210
|
+
const creds = await getCredentials();
|
|
211
|
+
return creds?.environment || DEFAULT_ENVIRONMENT;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* CR-2026-061: Set environment (updates existing credentials)
|
|
216
|
+
* @param {string} environment - Environment name to store
|
|
217
|
+
* @returns {Promise<void>}
|
|
218
|
+
*/
|
|
219
|
+
async function setEnvironment(environment) {
|
|
220
|
+
const creds = await getCredentials();
|
|
221
|
+
if (creds) {
|
|
222
|
+
await storeCredentials({
|
|
223
|
+
...creds,
|
|
224
|
+
environment
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
// No credentials yet - store just the environment
|
|
228
|
+
// This allows setting env before login
|
|
229
|
+
await storeCredentials({ environment });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
191
233
|
module.exports = {
|
|
192
234
|
storeCredentials,
|
|
193
235
|
getCredentials,
|
|
@@ -197,5 +239,8 @@ module.exports = {
|
|
|
197
239
|
getProjectName,
|
|
198
240
|
setProjectId,
|
|
199
241
|
getUserInfo,
|
|
200
|
-
setUserInfo
|
|
242
|
+
setUserInfo,
|
|
243
|
+
getEnvironment, // CR-2026-061
|
|
244
|
+
setEnvironment, // CR-2026-061
|
|
245
|
+
isAdmin // CR-2026-061
|
|
201
246
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment configuration for Vibe Assurance CLI
|
|
3
|
+
* CR-2026-061: Environment switching with production default
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ENVIRONMENTS = {
|
|
7
|
+
prod: {
|
|
8
|
+
name: 'Production',
|
|
9
|
+
url: 'https://vibeassurance.app',
|
|
10
|
+
internal: false,
|
|
11
|
+
description: 'Production server (default)'
|
|
12
|
+
},
|
|
13
|
+
dev: {
|
|
14
|
+
name: 'Development',
|
|
15
|
+
url: 'https://agent-platform-dev.azurewebsites.net',
|
|
16
|
+
internal: true,
|
|
17
|
+
description: 'Development server (internal only)'
|
|
18
|
+
},
|
|
19
|
+
local: {
|
|
20
|
+
name: 'Local',
|
|
21
|
+
url: 'http://localhost:3000',
|
|
22
|
+
internal: true,
|
|
23
|
+
description: 'Local development server (internal only)'
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DEFAULT_ENVIRONMENT = 'prod';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get environment configuration by name
|
|
31
|
+
* @param {string} envName - Environment name (prod, dev, local)
|
|
32
|
+
* @returns {Object|null} Environment config or null if not found
|
|
33
|
+
*/
|
|
34
|
+
function getEnvironmentConfig(envName) {
|
|
35
|
+
return ENVIRONMENTS[envName] || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get API URL for an environment
|
|
40
|
+
* @param {string} envName - Environment name
|
|
41
|
+
* @returns {string} API URL
|
|
42
|
+
*/
|
|
43
|
+
function getEnvironmentUrl(envName) {
|
|
44
|
+
const env = ENVIRONMENTS[envName];
|
|
45
|
+
return env ? env.url : ENVIRONMENTS[DEFAULT_ENVIRONMENT].url;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if environment requires internal flag
|
|
50
|
+
* @param {string} envName - Environment name
|
|
51
|
+
* @returns {boolean} True if internal flag required
|
|
52
|
+
*/
|
|
53
|
+
function isInternalOnly(envName) {
|
|
54
|
+
const env = ENVIRONMENTS[envName];
|
|
55
|
+
return env ? env.internal : false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get all environment names
|
|
60
|
+
* @returns {string[]} Array of environment names
|
|
61
|
+
*/
|
|
62
|
+
function getEnvironmentNames() {
|
|
63
|
+
return Object.keys(ENVIRONMENTS);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
ENVIRONMENTS,
|
|
68
|
+
DEFAULT_ENVIRONMENT,
|
|
69
|
+
getEnvironmentConfig,
|
|
70
|
+
getEnvironmentUrl,
|
|
71
|
+
isInternalOnly,
|
|
72
|
+
getEnvironmentNames
|
|
73
|
+
};
|