@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 +11 -1
- package/nul +2 -0
- package/package.json +2 -1
- package/src/api/client.js +17 -6
- package/src/commands/login.js +75 -1
- package/src/commands/projects.js +211 -0
- package/src/commands/whoami.js +65 -0
- package/src/config/credentials.js +73 -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');
|
|
@@ -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-assurance/cli",
|
|
3
|
-
"version": "1.0
|
|
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
|
|
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, 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
|
};
|