@vibe-assurance/cli 1.7.0 → 1.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-assurance/cli",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
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": {
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Environment management commands
3
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.
4
7
  */
5
8
 
6
9
  const chalk = require('chalk');
7
10
  const ora = require('ora');
8
- const { getEnvironment, setEnvironment } = require('../config/credentials');
11
+ const { getEnvironment, setEnvironment, isAdmin, hasValidCredentials, deleteCredentials } = require('../config/credentials');
9
12
  const {
10
13
  ENVIRONMENTS,
11
- DEFAULT_ENVIRONMENT,
12
14
  getEnvironmentConfig,
13
15
  isInternalOnly,
14
16
  getEnvironmentNames
@@ -23,6 +25,7 @@ async function showCurrentEnv() {
23
25
  try {
24
26
  const currentEnv = await getEnvironment();
25
27
  const config = getEnvironmentConfig(currentEnv);
28
+ const userIsAdmin = await isAdmin();
26
29
  spinner.stop();
27
30
 
28
31
  console.log('');
@@ -32,7 +35,7 @@ async function showCurrentEnv() {
32
35
  console.log(` ${chalk.dim(config.url)}`);
33
36
 
34
37
  if (config.internal) {
35
- console.log(` ${chalk.yellow('⚠ Internal environment')}`);
38
+ console.log(` ${chalk.yellow('⚠ Internal environment (admin only)')}`);
36
39
  }
37
40
 
38
41
  // Check if VIBE_API_URL override is active
@@ -52,25 +55,26 @@ async function showCurrentEnv() {
52
55
 
53
56
  /**
54
57
  * CR-2026-061: List all available environments
58
+ * Only shows internal environments to admins
55
59
  */
56
- async function listEnvironments(options = {}) {
60
+ async function listEnvironments() {
57
61
  const currentEnv = await getEnvironment();
58
- const showInternal = options.internal || false;
62
+ const userIsAdmin = await isAdmin();
59
63
 
60
64
  console.log('');
61
65
  console.log(chalk.cyan('Available Environments:'));
62
66
  console.log('');
63
67
 
64
68
  for (const [name, config] of Object.entries(ENVIRONMENTS)) {
65
- // Skip internal environments unless --internal flag
66
- if (config.internal && !showInternal) {
69
+ // Only show internal environments to admins
70
+ if (config.internal && !userIsAdmin) {
67
71
  continue;
68
72
  }
69
73
 
70
74
  const isCurrent = name === currentEnv;
71
75
  const prefix = isCurrent ? chalk.green('✓') : ' ';
72
76
  const envName = isCurrent ? chalk.green.bold(name) : chalk.bold(name);
73
- const label = config.internal ? chalk.yellow(' (internal)') : '';
77
+ const label = config.internal ? chalk.yellow(' (admin only)') : '';
74
78
 
75
79
  console.log(` ${prefix} ${envName}${label}`);
76
80
  console.log(` ${chalk.dim(config.description)}`);
@@ -78,40 +82,61 @@ async function listEnvironments(options = {}) {
78
82
  console.log('');
79
83
  }
80
84
 
81
- if (!showInternal) {
82
- console.log(chalk.dim(' Use --internal to see internal environments'));
85
+ if (!userIsAdmin) {
86
+ console.log(chalk.dim(' Only production environment is available.'));
83
87
  console.log('');
84
88
  }
85
89
  }
86
90
 
87
91
  /**
88
92
  * CR-2026-061: Switch to a specific environment
93
+ * Internal environments require admin role
94
+ * Switching environments clears credentials and requires re-login
89
95
  * @param {string} envName - Environment name (prod, dev, local)
90
- * @param {Object} options - Command options
91
96
  */
92
- async function switchEnvironment(envName, options = {}) {
97
+ async function switchEnvironment(envName) {
93
98
  const config = getEnvironmentConfig(envName);
99
+ const currentEnv = await getEnvironment();
94
100
 
95
101
  if (!config) {
96
102
  console.error(chalk.red(`Unknown environment: ${envName}`));
97
- console.log(chalk.dim('Available: ' + getEnvironmentNames().join(', ')));
103
+ console.log(chalk.dim('Available: prod'));
98
104
  process.exit(1);
99
105
  }
100
106
 
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);
107
+ // Already on this environment?
108
+ if (envName === currentEnv) {
109
+ console.log(chalk.yellow(`Already using ${config.name} environment.`));
110
+ return;
111
+ }
112
+
113
+ // Check if internal environment - require admin
114
+ if (isInternalOnly(envName)) {
115
+ // Must be logged in first
116
+ if (!await hasValidCredentials()) {
117
+ console.error(chalk.red('You must be logged in to switch environments.'));
118
+ console.log(chalk.dim('Run `vibe login` first.'));
119
+ process.exit(1);
120
+ }
121
+
122
+ // Must be admin
123
+ if (!await isAdmin()) {
124
+ console.error(chalk.red('Access denied.'));
125
+ console.log('');
126
+ console.log(chalk.yellow('Internal environments are restricted to platform administrators.'));
127
+ console.log(chalk.dim('Contact your administrator if you need access.'));
128
+ console.log('');
129
+ process.exit(1);
130
+ }
110
131
  }
111
132
 
112
133
  const spinner = ora(`Switching to ${config.name}...`).start();
113
134
 
114
135
  try {
136
+ // Clear existing credentials - different environments have different auth
137
+ await deleteCredentials();
138
+
139
+ // Store only the new environment (no auth tokens)
115
140
  await setEnvironment(envName);
116
141
  spinner.succeed(`Switched to ${chalk.bold(config.name)}`);
117
142
 
@@ -121,11 +146,10 @@ async function switchEnvironment(envName, options = {}) {
121
146
  if (config.internal) {
122
147
  console.log('');
123
148
  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
149
  }
126
150
 
127
151
  console.log('');
128
- console.log(chalk.dim('Note: You may need to run `vibe login` to authenticate with this environment.'));
152
+ console.log(chalk.cyan('You have been logged out. Run `vibe login` to authenticate with this environment.'));
129
153
  console.log('');
130
154
  } catch (err) {
131
155
  spinner.fail('Failed to switch environment');
@@ -141,7 +165,7 @@ async function switchEnvironment(envName, options = {}) {
141
165
  function registerEnvCommands(program) {
142
166
  const env = program
143
167
  .command('env')
144
- .description('Manage API environment (prod, dev, local)');
168
+ .description('Manage API environment');
145
169
 
146
170
  // vibe env (no subcommand) - show current
147
171
  env
@@ -161,9 +185,8 @@ function registerEnvCommands(program) {
161
185
  env
162
186
  .command('list')
163
187
  .description('List available environments')
164
- .option('--internal', 'Show internal environments (dev, local)')
165
- .action(async (options) => {
166
- await listEnvironments(options);
188
+ .action(async () => {
189
+ await listEnvironments();
167
190
  });
168
191
 
169
192
  // vibe env prod
@@ -171,25 +194,23 @@ function registerEnvCommands(program) {
171
194
  .command('prod')
172
195
  .description('Switch to production environment')
173
196
  .action(async () => {
174
- await switchEnvironment('prod', {});
197
+ await switchEnvironment('prod');
175
198
  });
176
199
 
177
- // vibe env dev
200
+ // vibe env dev (admin only - not shown in description)
178
201
  env
179
202
  .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);
203
+ .description('Switch to development environment')
204
+ .action(async () => {
205
+ await switchEnvironment('dev');
184
206
  });
185
207
 
186
- // vibe env local
208
+ // vibe env local (admin only - not shown in description)
187
209
  env
188
210
  .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);
211
+ .description('Switch to local environment')
212
+ .action(async () => {
213
+ await switchEnvironment('local');
193
214
  });
194
215
  }
195
216
 
@@ -193,13 +193,14 @@ async function selectProject() {
193
193
  }
194
194
  });
195
195
 
196
- const { username, email, projects, defaultProjectId } = response.data;
196
+ const { username, email, role, projects, defaultProjectId } = response.data;
197
197
  spinner.stop();
198
198
 
199
- // Store and display user info
199
+ // Store and display user info (CR-2026-061: now includes role for admin checks)
200
200
  if (username && email) {
201
- await setUserInfo(username, email);
202
- 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}`));
203
204
  }
204
205
 
205
206
  if (!projects || projects.length === 0) {
@@ -159,14 +159,15 @@ async function setProjectId(projectId, projectName) {
159
159
 
160
160
  /**
161
161
  * Get stored user info
162
- * @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
163
163
  */
164
164
  async function getUserInfo() {
165
165
  const creds = await getCredentials();
166
166
  if (creds?.username && creds?.email) {
167
167
  return {
168
168
  username: creds.username,
169
- email: creds.email
169
+ email: creds.email,
170
+ role: creds.role || 'user'
170
171
  };
171
172
  }
172
173
  return null;
@@ -174,21 +175,33 @@ async function getUserInfo() {
174
175
 
175
176
  /**
176
177
  * Set user info (updates existing credentials)
178
+ * CR-2026-061: Now includes role for admin checks
177
179
  * @param {string} username - Username to store
178
180
  * @param {string} email - Email to store
181
+ * @param {string} role - User role ('user' or 'admin')
179
182
  * @returns {Promise<void>}
180
183
  */
181
- async function setUserInfo(username, email) {
184
+ async function setUserInfo(username, email, role = 'user') {
182
185
  const creds = await getCredentials();
183
186
  if (creds) {
184
187
  await storeCredentials({
185
188
  ...creds,
186
189
  username,
187
- email
190
+ email,
191
+ role
188
192
  });
189
193
  }
190
194
  }
191
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
+
192
205
  /**
193
206
  * CR-2026-061: Get stored environment
194
207
  * @returns {Promise<string>} Environment name (prod, dev, local)
@@ -228,5 +241,6 @@ module.exports = {
228
241
  getUserInfo,
229
242
  setUserInfo,
230
243
  getEnvironment, // CR-2026-061
231
- setEnvironment // CR-2026-061
244
+ setEnvironment, // CR-2026-061
245
+ isAdmin // CR-2026-061
232
246
  };