@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 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.6.0",
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://agent-platform-prod.azurewebsites.net/docs-cli.html",
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
- // Default to dev Azure URL, can be overridden via environment variable
5
- const API_BASE_URL = process.env.VIBE_API_URL || 'https://agent-platform-dev.azurewebsites.net';
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.0.0'
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: API_BASE_URL,
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 response = await axios.post(`${API_BASE_URL}/api/auth/refresh`, {
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
+ };
@@ -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 { API_BASE_URL } = require('../api/client');
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 authUrl = `${API_BASE_URL}/api/auth/cli?callback=${encodeURIComponent(CALLBACK_URL)}`;
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(`${API_BASE_URL}/api/auth/me`, {
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
- console.log(chalk.cyan(`\nSigned in as ${chalk.bold(username)} (${email})`));
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) {
@@ -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 { API_BASE_URL } = require('../api/client');
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 response = await axios.get(`${API_BASE_URL}/api/auth/me`, {
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
+ };