@vibe-assurance/cli 1.6.0 → 1.7.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/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.0",
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,201 @@
1
+ /**
2
+ * Environment management commands
3
+ * CR-2026-061: CLI Environment Switching with Production Default
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const ora = require('ora');
8
+ const { getEnvironment, setEnvironment } = require('../config/credentials');
9
+ const {
10
+ ENVIRONMENTS,
11
+ DEFAULT_ENVIRONMENT,
12
+ getEnvironmentConfig,
13
+ isInternalOnly,
14
+ getEnvironmentNames
15
+ } = require('../config/environments');
16
+
17
+ /**
18
+ * CR-2026-061: Show current environment
19
+ */
20
+ async function showCurrentEnv() {
21
+ const spinner = ora('Checking environment...').start();
22
+
23
+ try {
24
+ const currentEnv = await getEnvironment();
25
+ const config = getEnvironmentConfig(currentEnv);
26
+ spinner.stop();
27
+
28
+ console.log('');
29
+ console.log(chalk.cyan('Current Environment:'));
30
+ console.log('');
31
+ console.log(` ${chalk.bold(config.name)} ${chalk.dim('(' + currentEnv + ')')}`);
32
+ console.log(` ${chalk.dim(config.url)}`);
33
+
34
+ if (config.internal) {
35
+ console.log(` ${chalk.yellow('⚠ Internal environment')}`);
36
+ }
37
+
38
+ // Check if VIBE_API_URL override is active
39
+ if (process.env.VIBE_API_URL) {
40
+ console.log('');
41
+ console.log(chalk.yellow('Note: VIBE_API_URL environment variable is set'));
42
+ console.log(chalk.dim(` Override URL: ${process.env.VIBE_API_URL}`));
43
+ }
44
+
45
+ console.log('');
46
+ } catch (err) {
47
+ spinner.fail('Failed to get environment');
48
+ console.error(chalk.red(err.message));
49
+ process.exit(1);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * CR-2026-061: List all available environments
55
+ */
56
+ async function listEnvironments(options = {}) {
57
+ const currentEnv = await getEnvironment();
58
+ const showInternal = options.internal || false;
59
+
60
+ console.log('');
61
+ console.log(chalk.cyan('Available Environments:'));
62
+ console.log('');
63
+
64
+ for (const [name, config] of Object.entries(ENVIRONMENTS)) {
65
+ // Skip internal environments unless --internal flag
66
+ if (config.internal && !showInternal) {
67
+ continue;
68
+ }
69
+
70
+ const isCurrent = name === currentEnv;
71
+ const prefix = isCurrent ? chalk.green('✓') : ' ';
72
+ const envName = isCurrent ? chalk.green.bold(name) : chalk.bold(name);
73
+ const label = config.internal ? chalk.yellow(' (internal)') : '';
74
+
75
+ console.log(` ${prefix} ${envName}${label}`);
76
+ console.log(` ${chalk.dim(config.description)}`);
77
+ console.log(` ${chalk.dim(config.url)}`);
78
+ console.log('');
79
+ }
80
+
81
+ if (!showInternal) {
82
+ console.log(chalk.dim(' Use --internal to see internal environments'));
83
+ console.log('');
84
+ }
85
+ }
86
+
87
+ /**
88
+ * CR-2026-061: Switch to a specific environment
89
+ * @param {string} envName - Environment name (prod, dev, local)
90
+ * @param {Object} options - Command options
91
+ */
92
+ async function switchEnvironment(envName, options = {}) {
93
+ const config = getEnvironmentConfig(envName);
94
+
95
+ if (!config) {
96
+ console.error(chalk.red(`Unknown environment: ${envName}`));
97
+ console.log(chalk.dim('Available: ' + getEnvironmentNames().join(', ')));
98
+ process.exit(1);
99
+ }
100
+
101
+ // Check if internal flag is required
102
+ if (isInternalOnly(envName) && !options.internal) {
103
+ console.error(chalk.red(`Environment '${envName}' requires --internal flag`));
104
+ console.log('');
105
+ console.log(chalk.yellow('⚠ This environment is for internal development only.'));
106
+ console.log(chalk.dim(' If you are a Vibe Assurance developer, run:'));
107
+ console.log(chalk.dim(` vibe env ${envName} --internal`));
108
+ console.log('');
109
+ process.exit(1);
110
+ }
111
+
112
+ const spinner = ora(`Switching to ${config.name}...`).start();
113
+
114
+ try {
115
+ await setEnvironment(envName);
116
+ spinner.succeed(`Switched to ${chalk.bold(config.name)}`);
117
+
118
+ console.log('');
119
+ console.log(` ${chalk.dim('API URL:')} ${config.url}`);
120
+
121
+ if (config.internal) {
122
+ console.log('');
123
+ console.log(chalk.yellow('⚠ You are now using an internal environment.'));
124
+ console.log(chalk.dim(' Run `vibe env prod` to switch back to production.'));
125
+ }
126
+
127
+ console.log('');
128
+ console.log(chalk.dim('Note: You may need to run `vibe login` to authenticate with this environment.'));
129
+ console.log('');
130
+ } catch (err) {
131
+ spinner.fail('Failed to switch environment');
132
+ console.error(chalk.red(err.message));
133
+ process.exit(1);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * CR-2026-061: Register environment commands with Commander
139
+ * @param {import('commander').Command} program - Commander program instance
140
+ */
141
+ function registerEnvCommands(program) {
142
+ const env = program
143
+ .command('env')
144
+ .description('Manage API environment (prod, dev, local)');
145
+
146
+ // vibe env (no subcommand) - show current
147
+ env
148
+ .action(async () => {
149
+ await showCurrentEnv();
150
+ });
151
+
152
+ // vibe env current
153
+ env
154
+ .command('current')
155
+ .description('Show current environment')
156
+ .action(async () => {
157
+ await showCurrentEnv();
158
+ });
159
+
160
+ // vibe env list
161
+ env
162
+ .command('list')
163
+ .description('List available environments')
164
+ .option('--internal', 'Show internal environments (dev, local)')
165
+ .action(async (options) => {
166
+ await listEnvironments(options);
167
+ });
168
+
169
+ // vibe env prod
170
+ env
171
+ .command('prod')
172
+ .description('Switch to production environment')
173
+ .action(async () => {
174
+ await switchEnvironment('prod', {});
175
+ });
176
+
177
+ // vibe env dev
178
+ env
179
+ .command('dev')
180
+ .description('Switch to development environment (internal only)')
181
+ .option('--internal', 'Confirm internal environment access')
182
+ .action(async (options) => {
183
+ await switchEnvironment('dev', options);
184
+ });
185
+
186
+ // vibe env local
187
+ env
188
+ .command('local')
189
+ .description('Switch to local development environment (internal only)')
190
+ .option('--internal', 'Confirm internal environment access')
191
+ .action(async (options) => {
192
+ await switchEnvironment('local', options);
193
+ });
194
+ }
195
+
196
+ module.exports = {
197
+ registerEnvCommands,
198
+ showCurrentEnv,
199
+ listEnvironments,
200
+ switchEnvironment
201
+ };
@@ -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,8 +182,11 @@ 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'
@@ -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';
@@ -188,6 +189,34 @@ async function setUserInfo(username, email) {
188
189
  }
189
190
  }
190
191
 
192
+ /**
193
+ * CR-2026-061: Get stored environment
194
+ * @returns {Promise<string>} Environment name (prod, dev, local)
195
+ */
196
+ async function getEnvironment() {
197
+ const creds = await getCredentials();
198
+ return creds?.environment || DEFAULT_ENVIRONMENT;
199
+ }
200
+
201
+ /**
202
+ * CR-2026-061: Set environment (updates existing credentials)
203
+ * @param {string} environment - Environment name to store
204
+ * @returns {Promise<void>}
205
+ */
206
+ async function setEnvironment(environment) {
207
+ const creds = await getCredentials();
208
+ if (creds) {
209
+ await storeCredentials({
210
+ ...creds,
211
+ environment
212
+ });
213
+ } else {
214
+ // No credentials yet - store just the environment
215
+ // This allows setting env before login
216
+ await storeCredentials({ environment });
217
+ }
218
+ }
219
+
191
220
  module.exports = {
192
221
  storeCredentials,
193
222
  getCredentials,
@@ -197,5 +226,7 @@ module.exports = {
197
226
  getProjectName,
198
227
  setProjectId,
199
228
  getUserInfo,
200
- setUserInfo
229
+ setUserInfo,
230
+ getEnvironment, // CR-2026-061
231
+ setEnvironment // CR-2026-061
201
232
  };
@@ -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
+ };