@nestbox-ai/cli 1.0.25 → 1.0.27
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/dist/commands/auth/index.d.ts +2 -0
- package/dist/commands/auth/index.js +9 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/auth/login.d.ts +2 -0
- package/dist/commands/auth/login.js +186 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +2 -0
- package/dist/commands/auth/logout.js +111 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth.d.ts +4 -1
- package/dist/commands/auth.js +8 -270
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/project/add.d.ts +2 -0
- package/dist/commands/project/add.js +49 -0
- package/dist/commands/project/add.js.map +1 -0
- package/dist/commands/project/apiUtils.d.ts +8 -0
- package/dist/commands/project/apiUtils.js +18 -0
- package/dist/commands/project/apiUtils.js.map +1 -0
- package/dist/commands/project/config.d.ts +11 -0
- package/dist/commands/project/config.js +32 -0
- package/dist/commands/project/config.js.map +1 -0
- package/dist/commands/project/index.d.ts +4 -0
- package/dist/commands/project/index.js +11 -0
- package/dist/commands/project/index.js.map +1 -0
- package/dist/commands/project/list.d.ts +2 -0
- package/dist/commands/project/list.js +83 -0
- package/dist/commands/project/list.js.map +1 -0
- package/dist/commands/project/use.d.ts +2 -0
- package/dist/commands/project/use.js +57 -0
- package/dist/commands/project/use.js.map +1 -0
- package/dist/commands/projects.d.ts +5 -12
- package/dist/commands/projects.js +18 -188
- package/dist/commands/projects.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/auth/index.ts +3 -0
- package/src/commands/auth/login.ts +184 -0
- package/src/commands/auth/logout.ts +110 -0
- package/src/commands/auth.ts +9 -288
- package/src/commands/project/add.ts +47 -0
- package/src/commands/project/apiUtils.ts +20 -0
- package/src/commands/project/config.ts +37 -0
- package/src/commands/project/index.ts +5 -0
- package/src/commands/project/list.ts +78 -0
- package/src/commands/project/use.ts +45 -0
- package/src/commands/projects.ts +26 -201
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { AuthApi, Configuration, OAuthLoginRequestDTOTypeEnum } from '@nestbox-ai/admin';
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
|
|
12
|
+
export function registerLoginCommand(program: Command): void {
|
|
13
|
+
program
|
|
14
|
+
.command('login <nestbox-domain>')
|
|
15
|
+
.description('Login using Google SSO')
|
|
16
|
+
.action(async (domain: string) => {
|
|
17
|
+
console.log('Login command triggered for domain:', domain);
|
|
18
|
+
const spinner = ora('Initiating Google login...').start();
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Determine the protocol and construct the auth URL based on the provided domain
|
|
22
|
+
let authUrl;
|
|
23
|
+
if (domain.includes('localhost')) {
|
|
24
|
+
// Use HTTP for localhost and specific port
|
|
25
|
+
authUrl = `http://${domain}/cli/auth`;
|
|
26
|
+
} else {
|
|
27
|
+
// Use HTTPS for all other domains
|
|
28
|
+
authUrl = `https://${domain}/cli/auth`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
spinner.text = 'Opening browser for Google authentication...';
|
|
32
|
+
|
|
33
|
+
// Open the browser for authentication
|
|
34
|
+
await open(authUrl);
|
|
35
|
+
spinner.succeed('Browser opened for authentication');
|
|
36
|
+
|
|
37
|
+
// Prompt user to paste the combined token and API URL
|
|
38
|
+
const { combinedInput } = await inquirer.prompt<{ combinedInput: string }>([
|
|
39
|
+
{
|
|
40
|
+
type: 'input',
|
|
41
|
+
name: 'combinedInput',
|
|
42
|
+
message: 'After authenticating, please paste the data here:',
|
|
43
|
+
validate: (input) => input.trim().length > 0 || 'Input is required'
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
// Split the input by comma
|
|
48
|
+
const [accessToken, apiServerUrl] = combinedInput.split(',').map(item => item.trim());
|
|
49
|
+
|
|
50
|
+
if (!accessToken || !apiServerUrl) {
|
|
51
|
+
spinner.fail('Invalid input format. Expected: token,apiServerUrl');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(chalk.green('Credentials received. Extracting user information...'));
|
|
56
|
+
|
|
57
|
+
// Fetch user data from the token
|
|
58
|
+
let email = '';
|
|
59
|
+
let name = '';
|
|
60
|
+
let picture = '';
|
|
61
|
+
try {
|
|
62
|
+
// Try to decode JWT to get user data (email, name, picture, etc.)
|
|
63
|
+
const tokenParts = accessToken.split('.');
|
|
64
|
+
if (tokenParts.length === 3) {
|
|
65
|
+
// Base64 decode the payload part of JWT
|
|
66
|
+
const base64Payload = tokenParts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
67
|
+
const decodedPayload = Buffer.from(base64Payload, 'base64').toString('utf-8');
|
|
68
|
+
const tokenPayload = JSON.parse(decodedPayload);
|
|
69
|
+
|
|
70
|
+
// Extract user information
|
|
71
|
+
email = tokenPayload.email || '';
|
|
72
|
+
name = tokenPayload.name || '';
|
|
73
|
+
picture = tokenPayload.picture || '';
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.log(chalk.yellow('Could not decode token payload. Will prompt for email.'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// If email couldn't be extracted from token, prompt user
|
|
80
|
+
if (!email) {
|
|
81
|
+
const response = await inquirer.prompt<{ email: string }>([
|
|
82
|
+
{
|
|
83
|
+
type: 'input',
|
|
84
|
+
name: 'email',
|
|
85
|
+
message: 'Enter your email address:',
|
|
86
|
+
validate: (input) => /\S+@\S+\.\S+/.test(input) || 'Please enter a valid email'
|
|
87
|
+
}
|
|
88
|
+
]);
|
|
89
|
+
email = response.email;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
spinner.start('Verifying access token...');
|
|
93
|
+
|
|
94
|
+
if (apiServerUrl && email && accessToken) {
|
|
95
|
+
// Verify the access token
|
|
96
|
+
const configuration = new Configuration({
|
|
97
|
+
basePath: apiServerUrl,
|
|
98
|
+
accessToken: accessToken,
|
|
99
|
+
});
|
|
100
|
+
const authApi = new AuthApi(configuration);
|
|
101
|
+
try {
|
|
102
|
+
const response = await authApi.authControllerOAuthLogin({
|
|
103
|
+
providerId: accessToken,
|
|
104
|
+
type: OAuthLoginRequestDTOTypeEnum.Google,
|
|
105
|
+
email,
|
|
106
|
+
profilePictureUrl: picture || '',
|
|
107
|
+
});
|
|
108
|
+
const authResponse = response.data;
|
|
109
|
+
|
|
110
|
+
// Save credentials to file
|
|
111
|
+
try {
|
|
112
|
+
// Create directory structure
|
|
113
|
+
const configDir = path.join(os.homedir(), '.config', '.nestbox');
|
|
114
|
+
if (!fs.existsSync(configDir)) {
|
|
115
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create the file path
|
|
119
|
+
const fileName = `${email.replace('@', '_at_')}_${domain}.json`;
|
|
120
|
+
const filePath = path.join(configDir, fileName);
|
|
121
|
+
|
|
122
|
+
// Create credentials object
|
|
123
|
+
const credentials = {
|
|
124
|
+
domain,
|
|
125
|
+
email,
|
|
126
|
+
token: authResponse.token,
|
|
127
|
+
accessToken, // Save the original accessToken
|
|
128
|
+
apiServerUrl,
|
|
129
|
+
name,
|
|
130
|
+
picture,
|
|
131
|
+
timestamp: new Date().toISOString()
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Write to file
|
|
135
|
+
fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2));
|
|
136
|
+
|
|
137
|
+
spinner.succeed('Authentication successful');
|
|
138
|
+
console.log(chalk.green(`Successfully logged in as ${email}`));
|
|
139
|
+
console.log(chalk.blue(`Credentials saved to: ${filePath}`));
|
|
140
|
+
} catch (fileError) {
|
|
141
|
+
spinner.warn('Authentication successful, but failed to save credentials file');
|
|
142
|
+
console.error(chalk.yellow('File error:'), fileError instanceof Error ? fileError.message : 'Unknown error');
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
} catch (authError) {
|
|
146
|
+
spinner.fail('Failed to verify access token');
|
|
147
|
+
if (axios.isAxiosError(authError) && authError.response) {
|
|
148
|
+
if (authError.response.data.message === "user.not_found") {
|
|
149
|
+
console.error(chalk.red('Authentication Error:'), "You need to register your email with the Nestbox platform");
|
|
150
|
+
const { openSignup } = await inquirer.prompt<{ openSignup: boolean }>([
|
|
151
|
+
{
|
|
152
|
+
type: 'confirm',
|
|
153
|
+
name: 'openSignup',
|
|
154
|
+
message: 'Would you like to open the signup page to register?',
|
|
155
|
+
default: true
|
|
156
|
+
}
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
if (openSignup) {
|
|
160
|
+
// Construct signup URL with the same protocol logic as login
|
|
161
|
+
let signupUrl;
|
|
162
|
+
if (domain.includes('localhost')) {
|
|
163
|
+
signupUrl = `http://${domain}`;
|
|
164
|
+
} else {
|
|
165
|
+
signupUrl = `https://${domain}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(chalk.blue(`Opening signup page: ${signupUrl}`));
|
|
169
|
+
await open(signupUrl);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
console.error(chalk.red('Authentication Error:'), authError instanceof Error ? authError.message : 'Unknown error');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
spinner.fail('Missing required information for authentication');
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
spinner.fail('Authentication failed');
|
|
181
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { getAuthToken, listCredentials, removeCredentials } from '../../utils/auth';
|
|
8
|
+
|
|
9
|
+
export function registerLogoutCommand(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command('logout [nestbox-domain]')
|
|
12
|
+
.description('Logout from Nestbox platform')
|
|
13
|
+
.action(async (domain?: string) => {
|
|
14
|
+
try {
|
|
15
|
+
const authToken = getAuthToken(domain);
|
|
16
|
+
if (!authToken) {
|
|
17
|
+
console.log(chalk.yellow('No authentication token found. Please log in first.'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Function to remove all credential files for a domain
|
|
22
|
+
const removeCredentialFiles = (domain: string) => {
|
|
23
|
+
try {
|
|
24
|
+
const configDir = path.join(os.homedir(), '.config', '.nestbox');
|
|
25
|
+
if (!fs.existsSync(configDir)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Sanitize domain for file matching
|
|
30
|
+
// Replace characters that are problematic in filenames
|
|
31
|
+
const sanitizedDomain = domain.replace(/:/g, '_');
|
|
32
|
+
|
|
33
|
+
// Get all files in the directory
|
|
34
|
+
const files = fs.readdirSync(configDir);
|
|
35
|
+
|
|
36
|
+
// Find and remove all files that match the domain
|
|
37
|
+
let removedCount = 0;
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
// Check if the file matches any of the possible domain formats
|
|
40
|
+
if (
|
|
41
|
+
file.endsWith(`_${domain}.json`) ||
|
|
42
|
+
file.endsWith(`_${sanitizedDomain}.json`)
|
|
43
|
+
) {
|
|
44
|
+
fs.unlinkSync(path.join(configDir, file));
|
|
45
|
+
removedCount++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return removedCount > 0;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn(chalk.yellow(`Warning: Could not remove credential files. ${error instanceof Error ? error.message : ''}`));
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (domain) {
|
|
57
|
+
// Logout from specific domain
|
|
58
|
+
// Remove credentials using utility function
|
|
59
|
+
const removed = removeCredentials(domain);
|
|
60
|
+
|
|
61
|
+
// Also remove all credential files for this domain
|
|
62
|
+
const filesRemoved = removeCredentialFiles(domain);
|
|
63
|
+
|
|
64
|
+
if (removed || filesRemoved) {
|
|
65
|
+
console.log(chalk.green(`Successfully logged out from ${domain}`));
|
|
66
|
+
} else {
|
|
67
|
+
console.log(chalk.yellow(`No credentials found for ${domain}`));
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// Ask which domain to logout from
|
|
71
|
+
const credentials = listCredentials();
|
|
72
|
+
|
|
73
|
+
if (credentials.length === 0) {
|
|
74
|
+
console.log(chalk.yellow('No credentials found'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Group credentials by domain
|
|
79
|
+
const domains = Array.from(new Set(credentials.map(cred => cred.domain)));
|
|
80
|
+
const domainChoices = domains.map(domain => {
|
|
81
|
+
const accounts = credentials.filter(cred => cred.domain === domain);
|
|
82
|
+
return `${domain} (${accounts.length} account${accounts.length > 1 ? 's' : ''})`;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const { selected } = await inquirer.prompt<{ selected: string }>([
|
|
86
|
+
{
|
|
87
|
+
type: 'list',
|
|
88
|
+
name: 'selected',
|
|
89
|
+
message: 'Select domain to logout from:',
|
|
90
|
+
choices: domainChoices,
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// Extract domain from the selected choice
|
|
95
|
+
const selectedDomain = selected.split(' ')[0];
|
|
96
|
+
|
|
97
|
+
// Remove credentials using utility function
|
|
98
|
+
removeCredentials(selectedDomain);
|
|
99
|
+
|
|
100
|
+
// Also remove all credential files for this domain
|
|
101
|
+
removeCredentialFiles(selectedDomain);
|
|
102
|
+
|
|
103
|
+
console.log(chalk.green(`Successfully logged out from ${selectedDomain}`));
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const err = error as Error;
|
|
107
|
+
console.error(chalk.red('Error:'), err.message);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
package/src/commands/auth.ts
CHANGED
|
@@ -1,291 +1,12 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import open from 'open';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import os from 'os';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { getAuthToken, listCredentials, removeCredentials } from '../utils/auth';
|
|
10
|
-
import { AuthApi, Configuration, OAuthLoginRequestDTOTypeEnum } from '@nestbox-ai/admin';
|
|
11
|
-
import axios from 'axios';
|
|
12
|
-
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { registerLoginCommand } from "./auth/login";
|
|
3
|
+
import { registerLogoutCommand } from "./auth/logout";
|
|
13
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Register all auth-related commands
|
|
7
|
+
*/
|
|
14
8
|
export function registerAuthCommands(program: Command): void {
|
|
15
|
-
//
|
|
16
|
-
program
|
|
17
|
-
|
|
18
|
-
.description('Login using Google SSO')
|
|
19
|
-
.action(async (domain: string) => {
|
|
20
|
-
console.log('Login command triggered for domain:', domain);
|
|
21
|
-
const spinner = ora('Initiating Google login...').start();
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
// Determine the protocol and construct the auth URL based on the provided domain
|
|
25
|
-
let authUrl;
|
|
26
|
-
if (domain.includes('localhost')) {
|
|
27
|
-
// Use HTTP for localhost and specific port
|
|
28
|
-
authUrl = `http://${domain}/cli/auth`;
|
|
29
|
-
} else {
|
|
30
|
-
// Use HTTPS for all other domains
|
|
31
|
-
authUrl = `https://${domain}/cli/auth`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
spinner.text = 'Opening browser for Google authentication...';
|
|
35
|
-
|
|
36
|
-
// Open the browser for authentication
|
|
37
|
-
await open(authUrl);
|
|
38
|
-
spinner.succeed('Browser opened for authentication');
|
|
39
|
-
|
|
40
|
-
// Prompt user to paste the combined token and API URL
|
|
41
|
-
const { combinedInput } = await inquirer.prompt<{ combinedInput: string }>([
|
|
42
|
-
{
|
|
43
|
-
type: 'input',
|
|
44
|
-
name: 'combinedInput',
|
|
45
|
-
message: 'After authenticating, please paste the data here:',
|
|
46
|
-
validate: (input) => input.trim().length > 0 || 'Input is required'
|
|
47
|
-
}
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
// Split the input by comma
|
|
51
|
-
const [accessToken, apiServerUrl] = combinedInput.split(',').map(item => item.trim());
|
|
52
|
-
|
|
53
|
-
if (!accessToken || !apiServerUrl) {
|
|
54
|
-
spinner.fail('Invalid input format. Expected: token,apiServerUrl');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(chalk.green('Credentials received. Extracting user information...'));
|
|
59
|
-
|
|
60
|
-
// Fetch user data from the token
|
|
61
|
-
let email = '';
|
|
62
|
-
let name = '';
|
|
63
|
-
let picture = '';
|
|
64
|
-
try {
|
|
65
|
-
// Try to decode JWT to get user data (email, name, picture, etc.)
|
|
66
|
-
const tokenParts = accessToken.split('.');
|
|
67
|
-
if (tokenParts.length === 3) {
|
|
68
|
-
// Base64 decode the payload part of JWT
|
|
69
|
-
const base64Payload = tokenParts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
70
|
-
const decodedPayload = Buffer.from(base64Payload, 'base64').toString('utf-8');
|
|
71
|
-
const tokenPayload = JSON.parse(decodedPayload);
|
|
72
|
-
|
|
73
|
-
// Extract user information
|
|
74
|
-
email = tokenPayload.email || '';
|
|
75
|
-
name = tokenPayload.name || '';
|
|
76
|
-
picture = tokenPayload.picture || '';
|
|
77
|
-
}
|
|
78
|
-
} catch (e) {
|
|
79
|
-
console.log(chalk.yellow('Could not decode token payload. Will prompt for email.'));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// If email couldn't be extracted from token, prompt user
|
|
83
|
-
if (!email) {
|
|
84
|
-
const response = await inquirer.prompt<{ email: string }>([
|
|
85
|
-
{
|
|
86
|
-
type: 'input',
|
|
87
|
-
name: 'email',
|
|
88
|
-
message: 'Enter your email address:',
|
|
89
|
-
validate: (input) => /\S+@\S+\.\S+/.test(input) || 'Please enter a valid email'
|
|
90
|
-
}
|
|
91
|
-
]);
|
|
92
|
-
email = response.email;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
spinner.start('Verifying access token...');
|
|
96
|
-
|
|
97
|
-
if (apiServerUrl && email && accessToken) {
|
|
98
|
-
// Verify the access token
|
|
99
|
-
const configuration = new Configuration({
|
|
100
|
-
basePath: apiServerUrl,
|
|
101
|
-
accessToken: accessToken,
|
|
102
|
-
});
|
|
103
|
-
const authApi = new AuthApi(configuration);
|
|
104
|
-
try {
|
|
105
|
-
const response = await authApi.authControllerOAuthLogin({
|
|
106
|
-
providerId: accessToken,
|
|
107
|
-
type: OAuthLoginRequestDTOTypeEnum.Google,
|
|
108
|
-
email,
|
|
109
|
-
profilePictureUrl: picture || '',
|
|
110
|
-
});
|
|
111
|
-
const authResponse = response.data;
|
|
112
|
-
|
|
113
|
-
// Save credentials to file
|
|
114
|
-
try {
|
|
115
|
-
// Create directory structure
|
|
116
|
-
const configDir = path.join(os.homedir(), '.config', '.nestbox');
|
|
117
|
-
if (!fs.existsSync(configDir)) {
|
|
118
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Create the file path
|
|
122
|
-
const fileName = `${email.replace('@', '_at_')}_${domain}.json`;
|
|
123
|
-
const filePath = path.join(configDir, fileName);
|
|
124
|
-
|
|
125
|
-
// Create credentials object
|
|
126
|
-
const credentials = {
|
|
127
|
-
domain,
|
|
128
|
-
email,
|
|
129
|
-
token: authResponse.token,
|
|
130
|
-
accessToken, // Save the original accessToken
|
|
131
|
-
apiServerUrl,
|
|
132
|
-
name,
|
|
133
|
-
picture,
|
|
134
|
-
timestamp: new Date().toISOString()
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Write to file
|
|
138
|
-
fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2));
|
|
139
|
-
|
|
140
|
-
spinner.succeed('Authentication successful');
|
|
141
|
-
console.log(chalk.green(`Successfully logged in as ${email}`));
|
|
142
|
-
console.log(chalk.blue(`Credentials saved to: ${filePath}`));
|
|
143
|
-
} catch (fileError) {
|
|
144
|
-
spinner.warn('Authentication successful, but failed to save credentials file');
|
|
145
|
-
console.error(chalk.yellow('File error:'), fileError instanceof Error ? fileError.message : 'Unknown error');
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
} catch (authError) {
|
|
149
|
-
spinner.fail('Failed to verify access token');
|
|
150
|
-
if (axios.isAxiosError(authError) && authError.response) {
|
|
151
|
-
if (authError.response.data.message === "user.not_found") {
|
|
152
|
-
console.error(chalk.red('Authentication Error:'), "You need to register your email with the Nestbox platform");
|
|
153
|
-
const { openSignup } = await inquirer.prompt<{ openSignup: boolean }>([
|
|
154
|
-
{
|
|
155
|
-
type: 'confirm',
|
|
156
|
-
name: 'openSignup',
|
|
157
|
-
message: 'Would you like to open the signup page to register?',
|
|
158
|
-
default: true
|
|
159
|
-
}
|
|
160
|
-
]);
|
|
161
|
-
|
|
162
|
-
if (openSignup) {
|
|
163
|
-
// Construct signup URL with the same protocol logic as login
|
|
164
|
-
let signupUrl;
|
|
165
|
-
if (domain.includes('localhost')) {
|
|
166
|
-
signupUrl = `http://${domain}`;
|
|
167
|
-
} else {
|
|
168
|
-
signupUrl = `https://${domain}`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
console.log(chalk.blue(`Opening signup page: ${signupUrl}`));
|
|
172
|
-
await open(signupUrl);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
console.error(chalk.red('Authentication Error:'), authError instanceof Error ? authError.message : 'Unknown error');
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
spinner.fail('Missing required information for authentication');
|
|
181
|
-
}
|
|
182
|
-
} catch (error) {
|
|
183
|
-
spinner.fail('Authentication failed');
|
|
184
|
-
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Logout command
|
|
191
|
-
program
|
|
192
|
-
.command('logout [nestbox-domain]')
|
|
193
|
-
.description('Logout from Nestbox platform')
|
|
194
|
-
.action(async (domain?: string) => {
|
|
195
|
-
try {
|
|
196
|
-
const authToken = getAuthToken(domain);
|
|
197
|
-
if (!authToken) {
|
|
198
|
-
console.log(chalk.yellow('No authentication token found. Please log in first.'));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Function to remove all credential files for a domain
|
|
203
|
-
const removeCredentialFiles = (domain: string) => {
|
|
204
|
-
try {
|
|
205
|
-
const configDir = path.join(os.homedir(), '.config', '.nestbox');
|
|
206
|
-
if (!fs.existsSync(configDir)) {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Sanitize domain for file matching
|
|
211
|
-
// Replace characters that are problematic in filenames
|
|
212
|
-
const sanitizedDomain = domain.replace(/:/g, '_');
|
|
213
|
-
|
|
214
|
-
// Get all files in the directory
|
|
215
|
-
const files = fs.readdirSync(configDir);
|
|
216
|
-
|
|
217
|
-
// Find and remove all files that match the domain
|
|
218
|
-
let removedCount = 0;
|
|
219
|
-
for (const file of files) {
|
|
220
|
-
// Check if the file matches any of the possible domain formats
|
|
221
|
-
if (
|
|
222
|
-
file.endsWith(`_${domain}.json`) ||
|
|
223
|
-
file.endsWith(`_${sanitizedDomain}.json`)
|
|
224
|
-
) {
|
|
225
|
-
fs.unlinkSync(path.join(configDir, file));
|
|
226
|
-
removedCount++;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return removedCount > 0;
|
|
231
|
-
} catch (error) {
|
|
232
|
-
console.warn(chalk.yellow(`Warning: Could not remove credential files. ${error instanceof Error ? error.message : ''}`));
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
if (domain) {
|
|
238
|
-
// Logout from specific domain
|
|
239
|
-
// Remove credentials using utility function
|
|
240
|
-
const removed = removeCredentials(domain);
|
|
241
|
-
|
|
242
|
-
// Also remove all credential files for this domain
|
|
243
|
-
const filesRemoved = removeCredentialFiles(domain);
|
|
244
|
-
|
|
245
|
-
if (removed || filesRemoved) {
|
|
246
|
-
console.log(chalk.green(`Successfully logged out from ${domain}`));
|
|
247
|
-
} else {
|
|
248
|
-
console.log(chalk.yellow(`No credentials found for ${domain}`));
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
// Ask which domain to logout from
|
|
252
|
-
const credentials = listCredentials();
|
|
253
|
-
|
|
254
|
-
if (credentials.length === 0) {
|
|
255
|
-
console.log(chalk.yellow('No credentials found'));
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Group credentials by domain
|
|
260
|
-
const domains = Array.from(new Set(credentials.map(cred => cred.domain)));
|
|
261
|
-
const domainChoices = domains.map(domain => {
|
|
262
|
-
const accounts = credentials.filter(cred => cred.domain === domain);
|
|
263
|
-
return `${domain} (${accounts.length} account${accounts.length > 1 ? 's' : ''})`;
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const { selected } = await inquirer.prompt<{ selected: string }>([
|
|
267
|
-
{
|
|
268
|
-
type: 'list',
|
|
269
|
-
name: 'selected',
|
|
270
|
-
message: 'Select domain to logout from:',
|
|
271
|
-
choices: domainChoices,
|
|
272
|
-
}
|
|
273
|
-
]);
|
|
274
|
-
|
|
275
|
-
// Extract domain from the selected choice
|
|
276
|
-
const selectedDomain = selected.split(' ')[0];
|
|
277
|
-
|
|
278
|
-
// Remove credentials using utility function
|
|
279
|
-
removeCredentials(selectedDomain);
|
|
280
|
-
|
|
281
|
-
// Also remove all credential files for this domain
|
|
282
|
-
removeCredentialFiles(selectedDomain);
|
|
283
|
-
|
|
284
|
-
console.log(chalk.green(`Successfully logged out from ${selectedDomain}`));
|
|
285
|
-
}
|
|
286
|
-
} catch (error) {
|
|
287
|
-
const err = error as Error;
|
|
288
|
-
console.error(chalk.red('Error:'), err.message);
|
|
289
|
-
}
|
|
290
|
-
});
|
|
9
|
+
// Register auth commands directly on the program (not as subcommands)
|
|
10
|
+
registerLoginCommand(program);
|
|
11
|
+
registerLogoutCommand(program);
|
|
291
12
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { readNestboxConfig, writeNestboxConfig } from "./config";
|
|
4
|
+
import { createApis } from "./apiUtils";
|
|
5
|
+
|
|
6
|
+
export function registerAddCommand(projectCommand: Command): void {
|
|
7
|
+
projectCommand
|
|
8
|
+
.command('add <project-name> [alias]')
|
|
9
|
+
.description('Add a project with optional alias')
|
|
10
|
+
.action((projectName: string, alias?: string) => {
|
|
11
|
+
try {
|
|
12
|
+
const apis = createApis();
|
|
13
|
+
|
|
14
|
+
const config = readNestboxConfig();
|
|
15
|
+
config.projects = config.projects || {};
|
|
16
|
+
|
|
17
|
+
// Check if the project already exists
|
|
18
|
+
if (config.projects[projectName]) {
|
|
19
|
+
console.error(chalk.red(`Project '${projectName}' already exists.`));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Check if the alias already exists
|
|
23
|
+
if (alias && config.projects[alias]) {
|
|
24
|
+
console.error(chalk.red(`Alias '${alias}' already exists.`));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (alias) {
|
|
29
|
+
config.projects[alias] = projectName;
|
|
30
|
+
console.log(chalk.green(`Added project '${projectName}' with alias '${alias}'`));
|
|
31
|
+
} else {
|
|
32
|
+
config.projects[projectName] = projectName;
|
|
33
|
+
console.log(chalk.green(`Added project '${projectName}'`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If this is the first project, set it as default
|
|
37
|
+
if (!config.projects.default) {
|
|
38
|
+
config.projects.default = projectName;
|
|
39
|
+
console.log(chalk.green(`Set '${projectName}' as the default project`));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
writeNestboxConfig(config);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(chalk.red('Error adding project:'), error instanceof Error ? error.message : 'Unknown error');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Configuration, ProjectsApi } from "@nestbox-ai/admin";
|
|
2
|
+
import { setupAuthAndConfig, type AuthResult } from "../../utils/api";
|
|
3
|
+
|
|
4
|
+
export interface ApiInstances {
|
|
5
|
+
projectsApi: ProjectsApi;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create API instances with current authentication using setupAuthAndConfig
|
|
10
|
+
*/
|
|
11
|
+
export function createApis(): ApiInstances {
|
|
12
|
+
const authResult = setupAuthAndConfig();
|
|
13
|
+
if (!authResult) {
|
|
14
|
+
throw new Error('No authentication token found. Please log in first.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
projectsApi: new ProjectsApi(authResult.configuration),
|
|
19
|
+
};
|
|
20
|
+
}
|