@nestbox-ai/cli 1.0.6 → 1.0.8

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.
@@ -12,256 +12,279 @@ import axios from 'axios';
12
12
 
13
13
 
14
14
  export function registerAuthCommands(program: Command): void {
15
- // Login command
16
- program
17
- .command('login <nestbox-domain>')
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);
15
+ // Login command
16
+ program
17
+ .command('login <nestbox-domain>')
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
+
104
23
  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
- apiServerUrl,
131
- name,
132
- picture,
133
- timestamp: new Date().toISOString()
134
- };
135
-
136
- // Write to file
137
- fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2));
138
-
139
- spinner.succeed('Authentication successful');
140
- console.log(chalk.green(`Successfully logged in as ${email}`));
141
- console.log(chalk.blue(`Credentials saved to: ${filePath}`));
142
- } catch (fileError) {
143
- spinner.warn('Authentication successful, but failed to save credentials file');
144
- console.error(chalk.yellow('File error:'), fileError instanceof Error ? fileError.message : 'Unknown error');
145
-
146
- }
147
- } catch (authError) {
148
- spinner.fail('Failed to verify access token');
149
- if (axios.isAxiosError(authError) && authError.response) {
150
- console.error(chalk.red('Authentication Error:'), authError.response.data?.message || 'Unknown authentication error');
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`;
151
29
  } else {
152
- console.error(chalk.red('Authentication Error:'), authError instanceof Error ? authError.message : 'Unknown error');
30
+ // Use HTTPS for all other domains
31
+ authUrl = `https://${domain}/cli/auth`;
153
32
  }
154
- }
155
- } else {
156
- spinner.fail('Missing required information for authentication');
157
- }
158
- } catch (error) {
159
- spinner.fail('Authentication failed');
160
- console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
161
- }
162
- });
163
-
164
-
165
-
166
- // Logout command
167
- program
168
- .command('logout [nestbox-domain]')
169
- .description('Logout from Nestbox platform')
170
- .action(async (domain?: string) => {
171
- try {
172
- const authToken = getAuthToken(domain);
173
- if (!authToken) {
174
- console.log(chalk.yellow('No authentication token found. Please log in first.'));
175
- return;
176
- }
177
33
 
178
- // Function to remove all credential files for a domain
179
- const removeCredentialFiles = (domain: string) => {
180
- try {
181
- const configDir = path.join(os.homedir(), '.config', '.nestbox');
182
- if (!fs.existsSync(configDir)) {
183
- return false;
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'
184
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
+ apiServerUrl,
131
+ name,
132
+ picture,
133
+ timestamp: new Date().toISOString()
134
+ };
135
+
136
+ // Write to file
137
+ fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2));
138
+
139
+ spinner.succeed('Authentication successful');
140
+ console.log(chalk.green(`Successfully logged in as ${email}`));
141
+ console.log(chalk.blue(`Credentials saved to: ${filePath}`));
142
+ } catch (fileError) {
143
+ spinner.warn('Authentication successful, but failed to save credentials file');
144
+ console.error(chalk.yellow('File error:'), fileError instanceof Error ? fileError.message : 'Unknown error');
145
+
146
+ }
147
+ } catch (authError) {
148
+ spinner.fail('Failed to verify access token');
149
+ if (axios.isAxiosError(authError) && authError.response) {
150
+ if (authError.response.data.message === "user.not_found") {
151
+ console.error(chalk.red('Authentication Error:'), "You need to register your email with the Nestbox platform");
152
+ const { openSignup } = await inquirer.prompt<{ openSignup: boolean }>([
153
+ {
154
+ type: 'confirm',
155
+ name: 'openSignup',
156
+ message: 'Would you like to open the signup page to register?',
157
+ default: true
158
+ }
159
+ ]);
185
160
 
186
- // Sanitize domain for file matching
187
- // Replace characters that are problematic in filenames
188
- const sanitizedDomain = domain.replace(/:/g, '_');
189
-
190
- // Get all files in the directory
191
- const files = fs.readdirSync(configDir);
192
-
193
- // Find and remove all files that match the domain
194
- let removedCount = 0;
195
- for (const file of files) {
196
- // Check if the file matches any of the possible domain formats
197
- if (
198
- file.endsWith(`_${domain}.json`) ||
199
- file.endsWith(`_${sanitizedDomain}.json`)
200
- ) {
201
- fs.unlinkSync(path.join(configDir, file));
202
- removedCount++;
161
+ if (openSignup) {
162
+ // Construct signup URL with the same protocol logic as login
163
+ let signupUrl;
164
+ if (domain.includes('localhost')) {
165
+ signupUrl = `http://${domain}`;
166
+ } else {
167
+ signupUrl = `https://${domain}`;
168
+ }
169
+
170
+ console.log(chalk.blue(`Opening signup page: ${signupUrl}`));
171
+ await open(signupUrl);
172
+ }
173
+ }
174
+ } else {
175
+ console.error(chalk.red('Authentication Error:'), authError instanceof Error ? authError.message : 'Unknown error');
203
176
  }
204
177
  }
205
-
206
- return removedCount > 0;
207
- } catch (error) {
208
- console.warn(chalk.yellow(`Warning: Could not remove credential files. ${error instanceof Error ? error.message : ''}`));
209
- return false;
210
- }
211
- };
212
-
213
- if (domain) {
214
- // Logout from specific domain
215
- // Remove credentials using utility function
216
- const removed = removeCredentials(domain);
217
-
218
- // Also remove all credential files for this domain
219
- const filesRemoved = removeCredentialFiles(domain);
220
-
221
- if (removed || filesRemoved) {
222
- console.log(chalk.green(`Successfully logged out from ${domain}`));
223
178
  } else {
224
- console.log(chalk.yellow(`No credentials found for ${domain}`));
179
+ spinner.fail('Missing required information for authentication');
225
180
  }
226
- } else {
227
- // Ask which domain to logout from
228
- const credentials = listCredentials();
229
-
230
- if (credentials.length === 0) {
231
- console.log(chalk.yellow('No credentials found'));
181
+ } catch (error) {
182
+ spinner.fail('Authentication failed');
183
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
184
+ }
185
+ });
186
+
187
+
188
+
189
+ // Logout command
190
+ program
191
+ .command('logout [nestbox-domain]')
192
+ .description('Logout from Nestbox platform')
193
+ .action(async (domain?: string) => {
194
+ try {
195
+ const authToken = getAuthToken(domain);
196
+ if (!authToken) {
197
+ console.log(chalk.yellow('No authentication token found. Please log in first.'));
232
198
  return;
233
199
  }
234
-
235
- // Group credentials by domain
236
- const domains = Array.from(new Set(credentials.map(cred => cred.domain)));
237
- const domainChoices = domains.map(domain => {
238
- const accounts = credentials.filter(cred => cred.domain === domain);
239
- return `${domain} (${accounts.length} account${accounts.length > 1 ? 's' : ''})`;
240
- });
241
-
242
- const { selected } = await inquirer.prompt<{ selected: string }>([
243
- {
244
- type: 'list',
245
- name: 'selected',
246
- message: 'Select domain to logout from:',
247
- choices: domainChoices,
200
+
201
+ // Function to remove all credential files for a domain
202
+ const removeCredentialFiles = (domain: string) => {
203
+ try {
204
+ const configDir = path.join(os.homedir(), '.config', '.nestbox');
205
+ if (!fs.existsSync(configDir)) {
206
+ return false;
207
+ }
208
+
209
+ // Sanitize domain for file matching
210
+ // Replace characters that are problematic in filenames
211
+ const sanitizedDomain = domain.replace(/:/g, '_');
212
+
213
+ // Get all files in the directory
214
+ const files = fs.readdirSync(configDir);
215
+
216
+ // Find and remove all files that match the domain
217
+ let removedCount = 0;
218
+ for (const file of files) {
219
+ // Check if the file matches any of the possible domain formats
220
+ if (
221
+ file.endsWith(`_${domain}.json`) ||
222
+ file.endsWith(`_${sanitizedDomain}.json`)
223
+ ) {
224
+ fs.unlinkSync(path.join(configDir, file));
225
+ removedCount++;
226
+ }
227
+ }
228
+
229
+ return removedCount > 0;
230
+ } catch (error) {
231
+ console.warn(chalk.yellow(`Warning: Could not remove credential files. ${error instanceof Error ? error.message : ''}`));
232
+ return false;
248
233
  }
249
- ]);
250
-
251
- // Extract domain from the selected choice
252
- const selectedDomain = selected.split(' ')[0];
253
-
254
- // Remove credentials using utility function
255
- removeCredentials(selectedDomain);
256
-
257
- // Also remove all credential files for this domain
258
- removeCredentialFiles(selectedDomain);
259
-
260
- console.log(chalk.green(`Successfully logged out from ${selectedDomain}`));
234
+ };
235
+
236
+ if (domain) {
237
+ // Logout from specific domain
238
+ // Remove credentials using utility function
239
+ const removed = removeCredentials(domain);
240
+
241
+ // Also remove all credential files for this domain
242
+ const filesRemoved = removeCredentialFiles(domain);
243
+
244
+ if (removed || filesRemoved) {
245
+ console.log(chalk.green(`Successfully logged out from ${domain}`));
246
+ } else {
247
+ console.log(chalk.yellow(`No credentials found for ${domain}`));
248
+ }
249
+ } else {
250
+ // Ask which domain to logout from
251
+ const credentials = listCredentials();
252
+
253
+ if (credentials.length === 0) {
254
+ console.log(chalk.yellow('No credentials found'));
255
+ return;
256
+ }
257
+
258
+ // Group credentials by domain
259
+ const domains = Array.from(new Set(credentials.map(cred => cred.domain)));
260
+ const domainChoices = domains.map(domain => {
261
+ const accounts = credentials.filter(cred => cred.domain === domain);
262
+ return `${domain} (${accounts.length} account${accounts.length > 1 ? 's' : ''})`;
263
+ });
264
+
265
+ const { selected } = await inquirer.prompt<{ selected: string }>([
266
+ {
267
+ type: 'list',
268
+ name: 'selected',
269
+ message: 'Select domain to logout from:',
270
+ choices: domainChoices,
271
+ }
272
+ ]);
273
+
274
+ // Extract domain from the selected choice
275
+ const selectedDomain = selected.split(' ')[0];
276
+
277
+ // Remove credentials using utility function
278
+ removeCredentials(selectedDomain);
279
+
280
+ // Also remove all credential files for this domain
281
+ removeCredentialFiles(selectedDomain);
282
+
283
+ console.log(chalk.green(`Successfully logged out from ${selectedDomain}`));
284
+ }
285
+ } catch (error) {
286
+ const err = error as Error;
287
+ console.error(chalk.red('Error:'), err.message);
261
288
  }
262
- } catch (error) {
263
- const err = error as Error;
264
- console.error(chalk.red('Error:'), err.message);
265
- }
266
- });
289
+ });
267
290
  }