@vibe-assurance/cli 1.0.7 → 1.1.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 +5 -1
- package/nul +2 -0
- package/package.json +2 -1
- package/src/api/client.js +17 -6
- package/src/commands/login.js +68 -1
- package/src/commands/projects.js +211 -0
- package/src/config/credentials.js +39 -1
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');
|
|
@@ -12,6 +12,7 @@ const login = require('../src/commands/login');
|
|
|
12
12
|
const logout = require('../src/commands/logout');
|
|
13
13
|
const mcpServer = require('../src/commands/mcp-server');
|
|
14
14
|
const setupClaude = require('../src/commands/setup-claude');
|
|
15
|
+
const { registerProjectCommands } = require('../src/commands/projects');
|
|
15
16
|
|
|
16
17
|
program
|
|
17
18
|
.name('vibe')
|
|
@@ -38,4 +39,7 @@ program
|
|
|
38
39
|
.description('Configure Claude Code to use the Vibe Assurance MCP server')
|
|
39
40
|
.action(setupClaude);
|
|
40
41
|
|
|
42
|
+
// CR-2026-043: Register project management commands
|
|
43
|
+
registerProjectCommands(program);
|
|
44
|
+
|
|
41
45
|
program.parse();
|
package/nul
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-assurance/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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
|
|
package/src/commands/login.js
CHANGED
|
@@ -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
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const axios = require('axios');
|
|
8
|
+
const { storeCredentials, hasValidCredentials, setProjectId } = 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,67 @@ 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
|
+
*/
|
|
166
|
+
async function selectProject() {
|
|
167
|
+
const spinner = ora('Fetching your projects...').start();
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Get credentials to make authenticated request
|
|
171
|
+
const { getCredentials } = require('../config/credentials');
|
|
172
|
+
const creds = await getCredentials();
|
|
173
|
+
|
|
174
|
+
// Fetch user profile which now includes projects (CR-2026-043 backend change)
|
|
175
|
+
const response = await axios.get(`${API_BASE_URL}/api/auth/me`, {
|
|
176
|
+
headers: {
|
|
177
|
+
'Authorization': `Bearer ${creds.accessToken}`,
|
|
178
|
+
'Content-Type': 'application/json'
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const { projects, defaultProjectId } = response.data;
|
|
183
|
+
spinner.stop();
|
|
184
|
+
|
|
185
|
+
if (!projects || projects.length === 0) {
|
|
186
|
+
console.log(chalk.yellow('\nNo projects found. A default project will be created when you first use the API.'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (projects.length === 1) {
|
|
191
|
+
// Auto-select single project
|
|
192
|
+
await setProjectId(projects[0]._id, projects[0].name);
|
|
193
|
+
console.log(chalk.green(`\n✓ Selected project: ${projects[0].name}`));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Multiple projects - prompt for selection
|
|
198
|
+
console.log(''); // Empty line for spacing
|
|
199
|
+
const choices = projects.map(p => ({
|
|
200
|
+
name: `${p.name}${p.isDefault ? chalk.dim(' (default)') : ''}`,
|
|
201
|
+
value: { id: p._id, name: p.name }
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
const { selected } = await inquirer.prompt([{
|
|
205
|
+
type: 'list',
|
|
206
|
+
name: 'selected',
|
|
207
|
+
message: 'Select a project to work with:',
|
|
208
|
+
choices,
|
|
209
|
+
default: choices.findIndex(c => c.value.id === defaultProjectId)
|
|
210
|
+
}]);
|
|
211
|
+
|
|
212
|
+
await setProjectId(selected.id, selected.name);
|
|
213
|
+
console.log(chalk.green(`✓ Selected project: ${selected.name}`));
|
|
214
|
+
|
|
215
|
+
} catch (err) {
|
|
216
|
+
spinner.stop();
|
|
217
|
+
// Non-fatal error - user can still use CLI with default project
|
|
218
|
+
console.log(chalk.yellow('\nCould not fetch projects. Using default project.'));
|
|
219
|
+
console.log(chalk.dim(` (You can run 'vibe project select' later to switch projects)`));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
156
223
|
/**
|
|
157
224
|
* Generate success HTML page
|
|
158
225
|
* 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
|
+
};
|
|
@@ -121,9 +121,47 @@ 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
|
+
|
|
124
159
|
module.exports = {
|
|
125
160
|
storeCredentials,
|
|
126
161
|
getCredentials,
|
|
127
162
|
deleteCredentials,
|
|
128
|
-
hasValidCredentials
|
|
163
|
+
hasValidCredentials,
|
|
164
|
+
getProjectId,
|
|
165
|
+
getProjectName,
|
|
166
|
+
setProjectId
|
|
129
167
|
};
|