@vibe-assurance/cli 1.0.7 → 1.2.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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  // Load .env file if present (for local development)
4
4
  const path = require('path');
@@ -10,8 +10,10 @@ const pkg = require('../package.json');
10
10
  // Commands
11
11
  const login = require('../src/commands/login');
12
12
  const logout = require('../src/commands/logout');
13
+ const whoami = require('../src/commands/whoami');
13
14
  const mcpServer = require('../src/commands/mcp-server');
14
15
  const setupClaude = require('../src/commands/setup-claude');
16
+ const { registerProjectCommands } = require('../src/commands/projects');
15
17
 
16
18
  program
17
19
  .name('vibe')
@@ -28,6 +30,11 @@ program
28
30
  .description('Clear stored credentials')
29
31
  .action(logout);
30
32
 
33
+ program
34
+ .command('whoami')
35
+ .description('Show current authenticated user')
36
+ .action(whoami);
37
+
31
38
  program
32
39
  .command('mcp-server')
33
40
  .description('Start the MCP server for Claude Code')
@@ -38,4 +45,7 @@ program
38
45
  .description('Configure Claude Code to use the Vibe Assurance MCP server')
39
46
  .action(setupClaude);
40
47
 
48
+ // CR-2026-043: Register project management commands
49
+ registerProjectCommands(program);
50
+
41
51
  program.parse();
package/nul ADDED
@@ -0,0 +1,2 @@
1
+ dir: cannot access '/s': No such file or directory
2
+ dir: cannot access '/b': No such file or directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-assurance/cli",
3
- "version": "1.0.7",
3
+ "version": "1.2.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": {
@@ -31,6 +31,7 @@
31
31
  "chalk": "^4.1.2",
32
32
  "commander": "^12.0.0",
33
33
  "dotenv": "^16.6.1",
34
+ "inquirer": "^8.2.6",
34
35
  "keytar": "^7.9.0",
35
36
  "open": "^8.4.2",
36
37
  "ora": "^5.4.1"
package/src/api/client.js CHANGED
@@ -1,11 +1,12 @@
1
1
  const axios = require('axios');
2
- const { getCredentials, storeCredentials, deleteCredentials } = require('../config/credentials');
2
+ const { getCredentials, storeCredentials, deleteCredentials, getProjectId } = require('../config/credentials');
3
3
 
4
4
  // Default to dev Azure URL, can be overridden via environment variable
5
5
  const API_BASE_URL = process.env.VIBE_API_URL || 'https://agent-platform-dev.azurewebsites.net';
6
6
 
7
7
  /**
8
8
  * Create an authenticated axios instance with token refresh handling
9
+ * CR-2026-043: Now includes X-Project-Id header for project scoping
9
10
  * @returns {Promise<import('axios').AxiosInstance>}
10
11
  */
11
12
  async function createClient() {
@@ -14,13 +15,23 @@ async function createClient() {
14
15
  throw new Error('Not authenticated. Run: vibe login');
15
16
  }
16
17
 
18
+ // CR-2026-043: Get current project ID for scoping
19
+ const projectId = await getProjectId();
20
+
21
+ const headers = {
22
+ 'Authorization': `Bearer ${creds.accessToken}`,
23
+ 'Content-Type': 'application/json',
24
+ 'User-Agent': 'vibe-cli/1.0.0'
25
+ };
26
+
27
+ // CR-2026-043: Add project header if available
28
+ if (projectId) {
29
+ headers['X-Project-Id'] = projectId;
30
+ }
31
+
17
32
  const client = axios.create({
18
33
  baseURL: API_BASE_URL,
19
- headers: {
20
- 'Authorization': `Bearer ${creds.accessToken}`,
21
- 'Content-Type': 'application/json',
22
- 'User-Agent': 'vibe-cli/1.0.0'
23
- },
34
+ headers,
24
35
  timeout: 30000 // 30 second timeout
25
36
  });
26
37
 
@@ -3,7 +3,9 @@ const http = require('http');
3
3
  const { URL } = require('url');
4
4
  const chalk = require('chalk');
5
5
  const ora = require('ora');
6
- const { storeCredentials, hasValidCredentials } = require('../config/credentials');
6
+ const inquirer = require('inquirer');
7
+ const axios = require('axios');
8
+ const { storeCredentials, hasValidCredentials, setProjectId, setUserInfo } = require('../config/credentials');
7
9
  const { API_BASE_URL } = require('../api/client');
8
10
 
9
11
  // Local callback server port (chosen to be unlikely to conflict)
@@ -140,6 +142,10 @@ async function login() {
140
142
  await authPromise;
141
143
  cleanup();
142
144
  spinner.succeed('Successfully authenticated!');
145
+
146
+ // CR-2026-043: Select project after successful login
147
+ await selectProject();
148
+
143
149
  console.log(chalk.green('\nYou are now logged in to Vibe Assurance.'));
144
150
  console.log('\nNext steps:');
145
151
  console.log(' 1. Run `vibe setup-claude` to configure Claude Code');
@@ -153,6 +159,74 @@ async function login() {
153
159
  }
154
160
  }
155
161
 
162
+ /**
163
+ * CR-2026-043: Select project after successful authentication
164
+ * Fetches user's projects and prompts for selection if multiple exist
165
+ * Also stores and displays user info
166
+ */
167
+ async function selectProject() {
168
+ const spinner = ora('Fetching your profile...').start();
169
+
170
+ try {
171
+ // Get credentials to make authenticated request
172
+ const { getCredentials } = require('../config/credentials');
173
+ const creds = await getCredentials();
174
+
175
+ // Fetch user profile which now includes projects (CR-2026-043 backend change)
176
+ const response = await axios.get(`${API_BASE_URL}/api/auth/me`, {
177
+ headers: {
178
+ 'Authorization': `Bearer ${creds.accessToken}`,
179
+ 'Content-Type': 'application/json'
180
+ }
181
+ });
182
+
183
+ const { username, email, projects, defaultProjectId } = response.data;
184
+ spinner.stop();
185
+
186
+ // Store and display user info
187
+ if (username && email) {
188
+ await setUserInfo(username, email);
189
+ console.log(chalk.cyan(`\nSigned in as ${chalk.bold(username)} (${email})`));
190
+ }
191
+
192
+ if (!projects || projects.length === 0) {
193
+ console.log(chalk.yellow('\nNo projects found. A default project will be created when you first use the API.'));
194
+ return;
195
+ }
196
+
197
+ if (projects.length === 1) {
198
+ // Auto-select single project
199
+ await setProjectId(projects[0]._id, projects[0].name);
200
+ console.log(chalk.green(`✓ Selected project: ${projects[0].name}`));
201
+ return;
202
+ }
203
+
204
+ // Multiple projects - prompt for selection
205
+ console.log(''); // Empty line for spacing
206
+ const choices = projects.map(p => ({
207
+ name: `${p.name}${p.isDefault ? chalk.dim(' (default)') : ''}`,
208
+ value: { id: p._id, name: p.name }
209
+ }));
210
+
211
+ const { selected } = await inquirer.prompt([{
212
+ type: 'list',
213
+ name: 'selected',
214
+ message: 'Select a project to work with:',
215
+ choices,
216
+ default: choices.findIndex(c => c.value.id === defaultProjectId)
217
+ }]);
218
+
219
+ await setProjectId(selected.id, selected.name);
220
+ console.log(chalk.green(`✓ Selected project: ${selected.name}`));
221
+
222
+ } catch (err) {
223
+ spinner.stop();
224
+ // Non-fatal error - user can still use CLI with default project
225
+ console.log(chalk.yellow('\nCould not fetch projects. Using default project.'));
226
+ console.log(chalk.dim(` (You can run 'vibe project select' later to switch projects)`));
227
+ }
228
+ }
229
+
156
230
  /**
157
231
  * Generate success HTML page
158
232
  * Matches the main app's branding with Tailwind CSS and emerald colors
@@ -0,0 +1,211 @@
1
+ /**
2
+ * CR-2026-043: Project management commands
3
+ *
4
+ * Provides CLI commands for listing and switching between projects:
5
+ * - vibe projects - List all accessible projects
6
+ * - vibe project current - Show current project
7
+ * - vibe project select - Switch to a different project
8
+ */
9
+
10
+ const chalk = require('chalk');
11
+ const ora = require('ora');
12
+ const inquirer = require('inquirer');
13
+ const api = require('../api/client');
14
+ const { getProjectId, getProjectName, setProjectId, hasValidCredentials } = require('../config/credentials');
15
+
16
+ /**
17
+ * List all accessible projects
18
+ * Command: vibe projects
19
+ */
20
+ async function listProjects() {
21
+ // Check authentication
22
+ if (!await hasValidCredentials()) {
23
+ console.log(chalk.red('Not authenticated. Run: vibe login'));
24
+ process.exit(1);
25
+ }
26
+
27
+ const spinner = ora('Fetching projects...').start();
28
+
29
+ try {
30
+ const projects = await api.get('/api/projects');
31
+ const currentProjectId = await getProjectId();
32
+ spinner.stop();
33
+
34
+ if (!projects || projects.length === 0) {
35
+ console.log(chalk.yellow('\nNo projects found.'));
36
+ console.log(chalk.dim('Create a project in the Vibe Assurance web portal.'));
37
+ return;
38
+ }
39
+
40
+ console.log(chalk.bold('\nYour Projects:\n'));
41
+
42
+ for (const p of projects) {
43
+ const current = p._id === currentProjectId ? chalk.green(' ← current') : '';
44
+ const def = p.isDefault ? chalk.dim(' (default)') : '';
45
+
46
+ console.log(` ${p.name}${def}${current}`);
47
+ console.log(chalk.dim(` ID: ${p._id}`));
48
+ if (p.description) {
49
+ console.log(chalk.dim(` ${p.description}`));
50
+ }
51
+ console.log('');
52
+ }
53
+ } catch (err) {
54
+ spinner.stop();
55
+ console.error(chalk.red('Failed to fetch projects:'), err.message);
56
+ process.exit(1);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Show current project
62
+ * Command: vibe project current
63
+ */
64
+ async function showCurrentProject() {
65
+ // Check authentication
66
+ if (!await hasValidCredentials()) {
67
+ console.log(chalk.red('Not authenticated. Run: vibe login'));
68
+ process.exit(1);
69
+ }
70
+
71
+ const projectId = await getProjectId();
72
+ const projectName = await getProjectName();
73
+
74
+ if (!projectId) {
75
+ console.log(chalk.yellow('No project selected.'));
76
+ console.log(chalk.dim('Run "vibe login" or "vibe project select" to select a project.'));
77
+ return;
78
+ }
79
+
80
+ const spinner = ora('Fetching project details...').start();
81
+
82
+ try {
83
+ const project = await api.get(`/api/projects/${projectId}`);
84
+ spinner.stop();
85
+
86
+ console.log(chalk.bold('\nCurrent Project:\n'));
87
+ console.log(` Name: ${project.name}`);
88
+ console.log(` ID: ${project._id}`);
89
+ if (project.description) {
90
+ console.log(` Description: ${project.description}`);
91
+ }
92
+ if (project.isDefault) {
93
+ console.log(chalk.dim(' (This is your default project)'));
94
+ }
95
+ } catch (err) {
96
+ spinner.stop();
97
+ // Fallback to cached info if API fails
98
+ if (projectName) {
99
+ console.log(chalk.bold('\nCurrent Project:\n'));
100
+ console.log(` Name: ${projectName}`);
101
+ console.log(` ID: ${projectId}`);
102
+ console.log(chalk.dim(' (Unable to fetch full details)'));
103
+ } else {
104
+ console.log(chalk.yellow(`Project ID: ${projectId}`));
105
+ console.log(chalk.dim('(Unable to fetch project details)'));
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Select/switch project
112
+ * Command: vibe project select [projectId]
113
+ * @param {string} [projectIdArg] - Optional project ID or name to select
114
+ */
115
+ async function selectProject(projectIdArg) {
116
+ // Check authentication
117
+ if (!await hasValidCredentials()) {
118
+ console.log(chalk.red('Not authenticated. Run: vibe login'));
119
+ process.exit(1);
120
+ }
121
+
122
+ const spinner = ora('Fetching projects...').start();
123
+
124
+ try {
125
+ const projects = await api.get('/api/projects');
126
+ spinner.stop();
127
+
128
+ if (!projects || projects.length === 0) {
129
+ console.log(chalk.yellow('No projects found.'));
130
+ return;
131
+ }
132
+
133
+ let selected;
134
+
135
+ if (projectIdArg) {
136
+ // Find by ID or name (case-insensitive)
137
+ selected = projects.find(p =>
138
+ p._id === projectIdArg ||
139
+ p.name.toLowerCase() === projectIdArg.toLowerCase()
140
+ );
141
+
142
+ if (!selected) {
143
+ console.log(chalk.red(`Project not found: ${projectIdArg}`));
144
+ console.log(chalk.dim('\nAvailable projects:'));
145
+ projects.forEach(p => console.log(chalk.dim(` - ${p.name} (${p._id})`)));
146
+ process.exit(1);
147
+ }
148
+ } else {
149
+ // Interactive selection
150
+ const currentProjectId = await getProjectId();
151
+ const choices = projects.map(p => ({
152
+ name: `${p.name}${p.isDefault ? chalk.dim(' (default)') : ''}${p._id === currentProjectId ? chalk.green(' ← current') : ''}`,
153
+ value: p
154
+ }));
155
+
156
+ const result = await inquirer.prompt([{
157
+ type: 'list',
158
+ name: 'selected',
159
+ message: 'Select a project:',
160
+ choices
161
+ }]);
162
+
163
+ selected = result.selected;
164
+ }
165
+
166
+ // Store the selected project
167
+ await setProjectId(selected._id, selected.name);
168
+ console.log(chalk.green(`✓ Switched to project: ${selected.name}`));
169
+
170
+ } catch (err) {
171
+ spinner.stop();
172
+ console.error(chalk.red('Failed to switch project:'), err.message);
173
+ process.exit(1);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Register project commands with Commander
179
+ * @param {import('commander').Command} program - Commander program instance
180
+ */
181
+ function registerProjectCommands(program) {
182
+ // vibe projects - List all projects
183
+ program
184
+ .command('projects')
185
+ .description('List your accessible projects')
186
+ .action(listProjects);
187
+
188
+ // vibe project - Project subcommands
189
+ const projectCmd = program
190
+ .command('project')
191
+ .description('Manage project context');
192
+
193
+ // vibe project current
194
+ projectCmd
195
+ .command('current')
196
+ .description('Show current project')
197
+ .action(showCurrentProject);
198
+
199
+ // vibe project select [projectId]
200
+ projectCmd
201
+ .command('select [projectId]')
202
+ .description('Switch to a different project (by ID or name)')
203
+ .action(selectProject);
204
+ }
205
+
206
+ module.exports = {
207
+ listProjects,
208
+ showCurrentProject,
209
+ selectProject,
210
+ registerProjectCommands
211
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Whoami command - shows current authenticated user
3
+ */
4
+
5
+ const chalk = require('chalk');
6
+ const ora = require('ora');
7
+ const axios = require('axios');
8
+ const { hasValidCredentials, getUserInfo, getCredentials, getProjectName } = require('../config/credentials');
9
+ const { API_BASE_URL } = require('../api/client');
10
+
11
+ /**
12
+ * Show current user info
13
+ * Command: vibe whoami
14
+ */
15
+ async function whoami() {
16
+ // Check if logged in
17
+ if (!await hasValidCredentials()) {
18
+ console.log(chalk.red('Not logged in.'));
19
+ console.log(chalk.dim('Run `vibe login` to authenticate.'));
20
+ process.exit(1);
21
+ }
22
+
23
+ // Try to get cached user info first
24
+ const cachedUser = await getUserInfo();
25
+ const projectName = await getProjectName();
26
+
27
+ if (cachedUser) {
28
+ console.log(chalk.cyan(`\nSigned in as ${chalk.bold(cachedUser.username)} (${cachedUser.email})`));
29
+ if (projectName) {
30
+ console.log(chalk.dim(`Current project: ${projectName}`));
31
+ }
32
+ return;
33
+ }
34
+
35
+ // If no cached info, fetch from API
36
+ const spinner = ora('Fetching user info...').start();
37
+
38
+ try {
39
+ const creds = await getCredentials();
40
+ const response = await axios.get(`${API_BASE_URL}/api/auth/me`, {
41
+ headers: {
42
+ 'Authorization': `Bearer ${creds.accessToken}`,
43
+ 'Content-Type': 'application/json'
44
+ }
45
+ });
46
+
47
+ const { username, email } = response.data;
48
+ spinner.stop();
49
+
50
+ if (username && email) {
51
+ console.log(chalk.cyan(`\nSigned in as ${chalk.bold(username)} (${email})`));
52
+ if (projectName) {
53
+ console.log(chalk.dim(`Current project: ${projectName}`));
54
+ }
55
+ } else {
56
+ console.log(chalk.yellow('Could not retrieve user info.'));
57
+ }
58
+ } catch (err) {
59
+ spinner.stop();
60
+ console.error(chalk.red('Failed to fetch user info:'), err.message);
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ module.exports = whoami;
@@ -121,9 +121,81 @@ async function hasValidCredentials() {
121
121
  return true;
122
122
  }
123
123
 
124
+ /**
125
+ * CR-2026-043: Get stored project ID
126
+ * @returns {Promise<string|null>} Project ID or null if not set
127
+ */
128
+ async function getProjectId() {
129
+ const creds = await getCredentials();
130
+ return creds?.projectId || null;
131
+ }
132
+
133
+ /**
134
+ * CR-2026-043: Get stored project name
135
+ * @returns {Promise<string|null>} Project name or null if not set
136
+ */
137
+ async function getProjectName() {
138
+ const creds = await getCredentials();
139
+ return creds?.projectName || null;
140
+ }
141
+
142
+ /**
143
+ * CR-2026-043: Set project ID (updates existing credentials)
144
+ * @param {string} projectId - Project ID to store
145
+ * @param {string} projectName - Project name for display
146
+ * @returns {Promise<void>}
147
+ */
148
+ async function setProjectId(projectId, projectName) {
149
+ const creds = await getCredentials();
150
+ if (creds) {
151
+ await storeCredentials({
152
+ ...creds,
153
+ projectId,
154
+ projectName
155
+ });
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Get stored user info
161
+ * @returns {Promise<{username: string, email: string}|null>} User info or null
162
+ */
163
+ async function getUserInfo() {
164
+ const creds = await getCredentials();
165
+ if (creds?.username && creds?.email) {
166
+ return {
167
+ username: creds.username,
168
+ email: creds.email
169
+ };
170
+ }
171
+ return null;
172
+ }
173
+
174
+ /**
175
+ * Set user info (updates existing credentials)
176
+ * @param {string} username - Username to store
177
+ * @param {string} email - Email to store
178
+ * @returns {Promise<void>}
179
+ */
180
+ async function setUserInfo(username, email) {
181
+ const creds = await getCredentials();
182
+ if (creds) {
183
+ await storeCredentials({
184
+ ...creds,
185
+ username,
186
+ email
187
+ });
188
+ }
189
+ }
190
+
124
191
  module.exports = {
125
192
  storeCredentials,
126
193
  getCredentials,
127
194
  deleteCredentials,
128
- hasValidCredentials
195
+ hasValidCredentials,
196
+ getProjectId,
197
+ getProjectName,
198
+ setProjectId,
199
+ getUserInfo,
200
+ setUserInfo
129
201
  };