@nestbox-ai/cli 1.0.13 → 1.0.16
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/.nestboxrc +5 -0
- package/dist/commands/agent.js +592 -173
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/auth.js +1 -0
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/compute.js +38 -8
- package/dist/commands/compute.js.map +1 -1
- package/dist/commands/document.js +2 -0
- package/dist/commands/document.js.map +1 -1
- package/dist/commands/image.js +107 -110
- package/dist/commands/image.js.map +1 -1
- package/dist/commands/projects.js +118 -30
- package/dist/commands/projects.js.map +1 -1
- package/dist/types/agentType.d.ts +1 -1
- package/dist/types/agentType.js +1 -1
- package/dist/types/agentType.js.map +1 -1
- package/dist/types/agentYaml.d.ts +91 -0
- package/dist/types/agentYaml.js +6 -0
- package/dist/types/agentYaml.js.map +1 -0
- package/dist/types/auth.d.ts +4 -6
- package/dist/types/auth.js +1 -0
- package/dist/types/auth.js.map +1 -1
- package/dist/utils/agent.d.ts +11 -0
- package/dist/utils/agent.js +72 -0
- package/dist/utils/agent.js.map +1 -1
- package/dist/utils/auth.d.ts +4 -0
- package/dist/utils/auth.js +31 -3
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.js +163 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/project.d.ts +4 -1
- package/dist/utils/project.js +17 -10
- package/dist/utils/project.js.map +1 -1
- package/package.json +3 -1
- package/sample-agents.yaml +0 -0
- package/src/commands/agent.ts +770 -303
- package/src/commands/auth.ts +1 -0
- package/src/commands/compute.ts +30 -8
- package/src/commands/document.ts +2 -2
- package/src/commands/image.ts +121 -129
- package/src/commands/projects.ts +125 -34
- package/src/types/agentType.ts +1 -1
- package/src/types/agentYaml.ts +107 -0
- package/src/types/auth.ts +10 -10
- package/src/utils/agent.ts +82 -0
- package/src/utils/auth.ts +33 -3
- package/src/utils/error.ts +168 -0
- package/src/utils/project.ts +20 -13
- package/templates/template-base-js.zip +0 -0
- package/templates/template-base-ts.zip +0 -0
- package/templates/template-chatbot-js.zip +0 -0
- package/templates/template-chatbot-ts.zip +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definition for Agent YAML configuration file
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a parameter for an agent in the YAML configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface AgentParameter {
|
|
9
|
+
/**
|
|
10
|
+
* Name of the parameter
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Description of the parameter
|
|
16
|
+
*/
|
|
17
|
+
description: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default value for the parameter
|
|
21
|
+
*/
|
|
22
|
+
default?: any;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default value as a string (used internally)
|
|
26
|
+
*/
|
|
27
|
+
default_value?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether this parameter is configurable by users
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
isUserParam?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents a single agent definition in the YAML configuration
|
|
38
|
+
*/
|
|
39
|
+
export interface AgentDefinition {
|
|
40
|
+
/**
|
|
41
|
+
* Name of the agent (required)
|
|
42
|
+
*/
|
|
43
|
+
name: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Description of the agent's purpose
|
|
47
|
+
* @default `AI agent for ${name}`
|
|
48
|
+
*/
|
|
49
|
+
goal?: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Agent type ("CHAT" or "AGENT")
|
|
53
|
+
* CHAT creates a chatbot, AGENT creates a regular agent
|
|
54
|
+
* @default "CHAT"
|
|
55
|
+
*/
|
|
56
|
+
type?: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Machine manifest ID for the agent
|
|
60
|
+
* @default "llamaindex-agent"
|
|
61
|
+
*/
|
|
62
|
+
machineManifestId?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Instance IP address
|
|
66
|
+
* @default "http://34.121.124.21"
|
|
67
|
+
*/
|
|
68
|
+
instanceIP?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Name of the machine
|
|
72
|
+
* @default "agent-instance"
|
|
73
|
+
*/
|
|
74
|
+
machineName?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Machine instance ID
|
|
78
|
+
* @default 17
|
|
79
|
+
*/
|
|
80
|
+
machineInstanceId?: number;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Name of the instance
|
|
84
|
+
*/
|
|
85
|
+
instanceName?: string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Model base ID
|
|
89
|
+
* @default ""
|
|
90
|
+
*/
|
|
91
|
+
modelBaseId?: string;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Agent parameters
|
|
95
|
+
*/
|
|
96
|
+
parameters?: AgentParameter[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Overall structure of the YAML configuration file
|
|
101
|
+
*/
|
|
102
|
+
export interface AgentYamlConfig {
|
|
103
|
+
/**
|
|
104
|
+
* Array of agent definitions
|
|
105
|
+
*/
|
|
106
|
+
agents: AgentDefinition[];
|
|
107
|
+
}
|
package/src/types/auth.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
1
|
+
// types/auth.ts
|
|
2
|
+
|
|
4
3
|
export interface UserCredentials {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
domain: string;
|
|
5
|
+
email: string;
|
|
6
|
+
token: string;
|
|
7
|
+
accessToken: string; // Google OAuth token
|
|
8
|
+
apiServerUrl: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
picture?: string;
|
|
11
|
+
timestamp: string;
|
|
12
12
|
}
|
package/src/utils/agent.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
|
|
6
6
|
import { exec } from "child_process";
|
|
7
7
|
import AdmZip from "adm-zip";
|
|
8
8
|
import * as os from 'os';
|
|
9
|
+
import axios from "axios";
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
const execAsync = promisify(exec);
|
|
@@ -167,4 +168,85 @@ export function createZipFromDirectory(dirPath: any, excludePatterns = ['node_mo
|
|
|
167
168
|
|
|
168
169
|
// Return the temp path for upload
|
|
169
170
|
return tempZipFilePath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface TemplateInfo {
|
|
174
|
+
name: string;
|
|
175
|
+
description: string;
|
|
176
|
+
fileId: string;
|
|
177
|
+
lang: string;
|
|
178
|
+
type: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const TEMPLATES: Record<string, TemplateInfo> = {
|
|
182
|
+
'template-base-js.zip': {
|
|
183
|
+
name: 'Base JavaScript Agent',
|
|
184
|
+
description: 'Basic JavaScript agent template',
|
|
185
|
+
fileId: '1EYaa4eZWDc3HiaSnauXgW7oLJYzqOhPZ', // Replace with actual file ID
|
|
186
|
+
lang: 'js',
|
|
187
|
+
type: 'agent'
|
|
188
|
+
},
|
|
189
|
+
'template-base-ts.zip': {
|
|
190
|
+
name: 'Base TypeScript Agent',
|
|
191
|
+
description: 'Basic TypeScript agent template',
|
|
192
|
+
fileId: '1kk2JWlgeRuNOGpz8wsZUTD115qfNzWk5', // Replace with actual file ID
|
|
193
|
+
lang: 'ts',
|
|
194
|
+
type: 'agent'
|
|
195
|
+
},
|
|
196
|
+
'template-chatbot-js.zip': {
|
|
197
|
+
name: 'JavaScript Chatbot',
|
|
198
|
+
description: 'JavaScript chatbot template',
|
|
199
|
+
fileId: '1b4c4NQa_Qm85-GwObn52D-bEKz48zh9O', // Replace with actual file ID
|
|
200
|
+
lang: 'js',
|
|
201
|
+
type: 'chatbot'
|
|
202
|
+
},
|
|
203
|
+
'template-chatbot-ts.zip': {
|
|
204
|
+
name: 'TypeScript Chatbot',
|
|
205
|
+
description: 'TypeScript chatbot template',
|
|
206
|
+
fileId: '1vbA5Jlet3XIRMQ4NSsMsLMeHt-mUhRTe', // Replace with actual file ID
|
|
207
|
+
lang: 'ts',
|
|
208
|
+
type: 'chatbot'
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export async function downloadFromGoogleDrive(fileId: string, outputPath: string): Promise<void> {
|
|
213
|
+
const url = `https://drive.google.com/uc?export=download&id=${fileId}`;
|
|
214
|
+
|
|
215
|
+
const response = await axios({
|
|
216
|
+
method: 'GET',
|
|
217
|
+
url: url,
|
|
218
|
+
responseType: 'stream',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const writer = fs.createWriteStream(outputPath);
|
|
222
|
+
response.data.pipe(writer);
|
|
223
|
+
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
writer.on('finish', resolve);
|
|
226
|
+
writer.on('error', reject);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Helper function to extract zip file
|
|
231
|
+
export function extractZip(zipPath: string, extractPath: string): void {
|
|
232
|
+
const zip = new AdmZip(zipPath);
|
|
233
|
+
zip.extractAllTo(extractPath, true);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function createNestboxConfig(projectPath: string, isTypeScript: boolean): void {
|
|
237
|
+
if (!isTypeScript) return;
|
|
238
|
+
|
|
239
|
+
const configPath = path.join(projectPath, 'nestbox.config.json');
|
|
240
|
+
const config = {
|
|
241
|
+
agents: {
|
|
242
|
+
predeploy: [
|
|
243
|
+
'rm -rf dist',
|
|
244
|
+
'npm run lint',
|
|
245
|
+
'npm run build'
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
251
|
+
console.log(chalk.green(`Created nestbox.config.json at ${configPath}`));
|
|
170
252
|
}
|
package/src/utils/auth.ts
CHANGED
|
@@ -62,6 +62,7 @@ export function getAuthToken(domain?: string): {token: string, serverUrl: string
|
|
|
62
62
|
return {
|
|
63
63
|
token: configData.token,
|
|
64
64
|
serverUrl: configData.apiServerUrl,
|
|
65
|
+
accessToken: configData.accessToken,
|
|
65
66
|
}
|
|
66
67
|
} catch (error) {
|
|
67
68
|
console.error('Error getting auth token:', error);
|
|
@@ -69,6 +70,31 @@ export function getAuthToken(domain?: string): {token: string, serverUrl: string
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Update the authentication token for a specific user
|
|
75
|
+
*/
|
|
76
|
+
export function updateAuthToken(email: string, domain: string, newToken: string): boolean {
|
|
77
|
+
try {
|
|
78
|
+
const fileName = `${email.replace('@', '_at_')}_${domain}.json`;
|
|
79
|
+
const filePath = path.join(CONFIG_DIR, fileName);
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(filePath)) {
|
|
82
|
+
console.error(`Credential file not found for ${email} at ${domain}`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const configData = JSON.parse(fs.readFileSync(filePath).toString());
|
|
87
|
+
configData.token = newToken;
|
|
88
|
+
configData.timestamp = new Date().toISOString();
|
|
89
|
+
|
|
90
|
+
fs.writeFileSync(filePath, JSON.stringify(configData, null, 2));
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Error updating auth token:', error);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
/**
|
|
73
99
|
* Get user credentials for a specific domain and email
|
|
74
100
|
*/
|
|
@@ -79,7 +105,8 @@ export function getUserCredentials(domain: string, email?: string): UserCredenti
|
|
|
79
105
|
|
|
80
106
|
if (email) {
|
|
81
107
|
// Get specific email for domain
|
|
82
|
-
|
|
108
|
+
const emailFile = email.replace('@', '_at_');
|
|
109
|
+
targetFiles = files.filter(file => file === `${emailFile}_${domain}.json`);
|
|
83
110
|
} else {
|
|
84
111
|
// Get all for domain, sort by last used
|
|
85
112
|
targetFiles = files.filter(file => file.endsWith(`_${domain}.json`));
|
|
@@ -89,10 +116,12 @@ export function getUserCredentials(domain: string, email?: string): UserCredenti
|
|
|
89
116
|
const data = JSON.parse(fs.readFileSync(path.join(CONFIG_DIR, file)).toString()) as UserCredentials;
|
|
90
117
|
return {
|
|
91
118
|
file,
|
|
119
|
+
timestamp: data.timestamp || '1970-01-01T00:00:00.000Z'
|
|
92
120
|
};
|
|
93
121
|
});
|
|
94
122
|
|
|
95
|
-
// Sort by
|
|
123
|
+
// Sort by timestamp, most recent first
|
|
124
|
+
allConfigs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
96
125
|
targetFiles = [allConfigs[0].file];
|
|
97
126
|
}
|
|
98
127
|
}
|
|
@@ -155,7 +184,8 @@ export function removeCredentials(domain: string, email?: string): boolean {
|
|
|
155
184
|
|
|
156
185
|
if (email) {
|
|
157
186
|
// Remove specific email for domain
|
|
158
|
-
|
|
187
|
+
const emailFile = email.replace('@', '_at_');
|
|
188
|
+
domainFiles = files.filter(file => file === `${emailFile}_${domain}.json`);
|
|
159
189
|
} else {
|
|
160
190
|
// Remove all for domain
|
|
161
191
|
domainFiles = files.filter(file => file.endsWith(`_${domain}.json`));
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// utils/error.ts
|
|
2
|
+
import { AuthApi, Configuration, OAuthLoginRequestDTOTypeEnum } from '@nestbox-ai/admin';
|
|
3
|
+
import { getAuthToken } from './auth';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
interface TokenRefreshResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
newToken?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Attempts to refresh the authentication token using stored credentials
|
|
17
|
+
*/
|
|
18
|
+
async function refreshAuthToken(serverUrl: string, accessToken: string): Promise<TokenRefreshResult> {
|
|
19
|
+
try {
|
|
20
|
+
// Get the stored credentials to extract user info
|
|
21
|
+
const configDir = path.join(os.homedir(), '.config', '.nestbox');
|
|
22
|
+
const files = fs.readdirSync(configDir);
|
|
23
|
+
|
|
24
|
+
// Find the credential file that matches this server URL
|
|
25
|
+
let userCredentials: any = null;
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(fs.readFileSync(path.join(configDir, file), 'utf8'));
|
|
29
|
+
if (data.apiServerUrl === serverUrl && data.accessToken === accessToken) {
|
|
30
|
+
userCredentials = data;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {
|
|
34
|
+
// Skip invalid files
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!userCredentials) {
|
|
39
|
+
return { success: false, error: 'Could not find stored credentials' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Create new configuration with the access token
|
|
43
|
+
const configuration = new Configuration({
|
|
44
|
+
basePath: serverUrl,
|
|
45
|
+
accessToken: accessToken,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const authApi = new AuthApi(configuration);
|
|
49
|
+
|
|
50
|
+
// Try to re-authenticate using the stored Google OAuth token
|
|
51
|
+
const response = await authApi.authControllerOAuthLogin({
|
|
52
|
+
providerId: accessToken,
|
|
53
|
+
type: OAuthLoginRequestDTOTypeEnum.Google,
|
|
54
|
+
email: userCredentials.email,
|
|
55
|
+
profilePictureUrl: userCredentials.picture || '',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const newToken = response.data.token;
|
|
59
|
+
|
|
60
|
+
// Update the stored credentials with the new token
|
|
61
|
+
const fileName = `${userCredentials.email.replace('@', '_at_')}_${userCredentials.domain}.json`;
|
|
62
|
+
const filePath = path.join(configDir, fileName);
|
|
63
|
+
|
|
64
|
+
userCredentials.token = newToken;
|
|
65
|
+
userCredentials.timestamp = new Date().toISOString();
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(filePath, JSON.stringify(userCredentials, null, 2));
|
|
68
|
+
|
|
69
|
+
return { success: true, newToken };
|
|
70
|
+
} catch (error: any) {
|
|
71
|
+
console.error(chalk.yellow('Token refresh failed:'), error.message);
|
|
72
|
+
return { success: false, error: error.message };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Enhanced 401 error handler with automatic token refresh
|
|
78
|
+
*/
|
|
79
|
+
export async function handle401Error(error: any, retryCallback?: () => Promise<any>): Promise<any> {
|
|
80
|
+
if (error.response && error.response.status === 401) {
|
|
81
|
+
// Get current auth token info
|
|
82
|
+
const authInfo = getAuthToken();
|
|
83
|
+
|
|
84
|
+
if (!authInfo || !authInfo.accessToken) {
|
|
85
|
+
throw new Error('Authentication token has expired. Please login again using "nestbox login <domain>".');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.yellow('Authentication token expired. Attempting to refresh...'));
|
|
89
|
+
|
|
90
|
+
// Try to refresh the token
|
|
91
|
+
const refreshResult = await refreshAuthToken(authInfo.serverUrl, authInfo.accessToken);
|
|
92
|
+
|
|
93
|
+
if (refreshResult.success && retryCallback) {
|
|
94
|
+
console.log(chalk.green('Token refreshed successfully. Retrying request...'));
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Retry the original request with the new token
|
|
98
|
+
const result = await retryCallback();
|
|
99
|
+
return { success: true, data: result };
|
|
100
|
+
} catch (retryError: any) {
|
|
101
|
+
// If retry also fails with 401, the refresh didn't work properly
|
|
102
|
+
if (retryError.response && retryError.response.status === 401) {
|
|
103
|
+
throw new Error('Authentication failed after token refresh. Please login again using "nestbox login <domain>".');
|
|
104
|
+
}
|
|
105
|
+
// Re-throw other errors
|
|
106
|
+
throw retryError;
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// Refresh failed
|
|
110
|
+
throw new Error('Authentication token has expired and automatic refresh failed. Please login again using "nestbox login <domain>".');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wrapper function to make API calls with automatic retry on 401
|
|
119
|
+
*/
|
|
120
|
+
export async function withTokenRefresh<T>(
|
|
121
|
+
apiCall: () => Promise<T>,
|
|
122
|
+
onRetry?: () => void
|
|
123
|
+
): Promise<T> {
|
|
124
|
+
try {
|
|
125
|
+
return await apiCall();
|
|
126
|
+
} catch (error: any) {
|
|
127
|
+
if (error.response && error.response.status === 401) {
|
|
128
|
+
// Get current auth token info
|
|
129
|
+
const authInfo = getAuthToken();
|
|
130
|
+
|
|
131
|
+
if (!authInfo || !authInfo.accessToken) {
|
|
132
|
+
throw new Error('Authentication token has expired. Please login again using "nestbox login <domain>".');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(chalk.yellow('Authentication token expired. Attempting to refresh...'));
|
|
136
|
+
|
|
137
|
+
// Try to refresh the token
|
|
138
|
+
const refreshResult = await refreshAuthToken(authInfo.serverUrl, authInfo.accessToken);
|
|
139
|
+
|
|
140
|
+
if (refreshResult.success) {
|
|
141
|
+
console.log(chalk.green('Token refreshed successfully. Retrying request...'));
|
|
142
|
+
|
|
143
|
+
// If onRetry callback is provided, call it to reinitialize API clients
|
|
144
|
+
if (onRetry) {
|
|
145
|
+
onRetry();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// Retry the original API call
|
|
150
|
+
return await apiCall();
|
|
151
|
+
} catch (retryError: any) {
|
|
152
|
+
// If retry also fails with 401, the refresh didn't work properly
|
|
153
|
+
if (retryError.response && retryError.response.status === 401) {
|
|
154
|
+
throw new Error('Authentication failed after token refresh. Please login again using "nestbox login <domain>".');
|
|
155
|
+
}
|
|
156
|
+
// Re-throw other errors
|
|
157
|
+
throw retryError;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
// Refresh failed
|
|
161
|
+
throw new Error('Authentication token has expired and automatic refresh failed. Please login again using "nestbox login <domain>".');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If not a 401 error, re-throw
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/utils/project.ts
CHANGED
|
@@ -2,11 +2,10 @@ import { ProjectsApi } from "@nestbox-ai/admin";
|
|
|
2
2
|
import ora from "ora";
|
|
3
3
|
import { readNestboxConfig } from "../commands/projects";
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
interface ProjectInfo {
|
|
7
6
|
id: string;
|
|
8
7
|
name: string;
|
|
9
|
-
|
|
8
|
+
}
|
|
10
9
|
|
|
11
10
|
interface CommandOptions {
|
|
12
11
|
instance: string;
|
|
@@ -17,15 +16,24 @@ interface CommandOptions {
|
|
|
17
16
|
[key: string]: any;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
interface ResolveProjectOptions extends CommandOptions {
|
|
20
|
+
showSpinner?: boolean; // New option to control spinner visibility
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function resolveProject(
|
|
24
|
+
projectsApi: ProjectsApi,
|
|
25
|
+
options: ResolveProjectOptions
|
|
26
|
+
): Promise<ProjectInfo> {
|
|
27
|
+
// Default to showing spinner if not specified
|
|
28
|
+
const showSpinner = options.showSpinner !== false;
|
|
29
|
+
const spinner = showSpinner ? ora('Resolving project...').start() : null;
|
|
22
30
|
|
|
23
31
|
try {
|
|
24
32
|
const projectsResponse = await projectsApi.projectControllerGetAllProjects();
|
|
25
33
|
const allProjects = projectsResponse.data?.data?.projects;
|
|
26
34
|
|
|
27
35
|
if (!allProjects || allProjects.length === 0) {
|
|
28
|
-
spinner.fail('No projects found.');
|
|
36
|
+
if (spinner) spinner.fail('No projects found.');
|
|
29
37
|
throw new Error('No projects found');
|
|
30
38
|
}
|
|
31
39
|
|
|
@@ -43,39 +51,38 @@ export async function resolveProject(projectsApi: ProjectsApi, options: CommandO
|
|
|
43
51
|
projectName = byName.name;
|
|
44
52
|
projectId = byName.id;
|
|
45
53
|
} else {
|
|
46
|
-
spinner.fail(`Project not found with ID or name: ${projectId}`);
|
|
54
|
+
if (spinner) spinner.fail(`Project not found with ID or name: ${projectId}`);
|
|
47
55
|
throw new Error(`Project not found with ID or name: ${projectId}`);
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
spinner.succeed(`Using project: ${projectName} (ID: ${projectId})`);
|
|
58
|
+
if (spinner) spinner.succeed(`Using project: ${projectName} (ID: ${projectId})`);
|
|
51
59
|
} else {
|
|
52
60
|
const config = readNestboxConfig();
|
|
53
61
|
const defaultProjectName = config.projects?.default;
|
|
54
62
|
|
|
55
63
|
if (!defaultProjectName) {
|
|
56
|
-
spinner.fail('No project specified and no default project set. Please provide a project ID or set a default project.');
|
|
64
|
+
if (spinner) spinner.fail('No project specified and no default project set. Please provide a project ID or set a default project.');
|
|
57
65
|
throw new Error('No project specified and no default project set');
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
const defaultProject = allProjects.find((p) => p.name === defaultProjectName);
|
|
61
69
|
|
|
62
70
|
if (!defaultProject) {
|
|
63
|
-
spinner.fail(`Default project "${defaultProjectName}" not found.`);
|
|
71
|
+
if (spinner) spinner.fail(`Default project "${defaultProjectName}" not found.`);
|
|
64
72
|
throw new Error(`Default project "${defaultProjectName}" not found.`);
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
projectId = defaultProject.id;
|
|
68
76
|
projectName = defaultProject.name;
|
|
69
|
-
spinner.succeed(`Using default project: ${projectName} (ID: ${projectId})`);
|
|
77
|
+
if (spinner) spinner.succeed(`Using default project: ${projectName} (ID: ${projectId})`);
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
return { id: projectId, name: projectName };
|
|
73
81
|
} catch (error) {
|
|
74
|
-
if (
|
|
82
|
+
if (spinner && spinner.isSpinning) {
|
|
75
83
|
// If spinner was already stopped with fail, we don't need to fail it again
|
|
76
|
-
|
|
84
|
+
spinner.fail('Failed to resolve project');
|
|
77
85
|
}
|
|
78
|
-
spinner.fail('Failed to resolve project');
|
|
79
86
|
throw error;
|
|
80
87
|
}
|
|
81
88
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|