@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.
Files changed (53) hide show
  1. package/.nestboxrc +5 -0
  2. package/dist/commands/agent.js +592 -173
  3. package/dist/commands/agent.js.map +1 -1
  4. package/dist/commands/auth.js +1 -0
  5. package/dist/commands/auth.js.map +1 -1
  6. package/dist/commands/compute.js +38 -8
  7. package/dist/commands/compute.js.map +1 -1
  8. package/dist/commands/document.js +2 -0
  9. package/dist/commands/document.js.map +1 -1
  10. package/dist/commands/image.js +107 -110
  11. package/dist/commands/image.js.map +1 -1
  12. package/dist/commands/projects.js +118 -30
  13. package/dist/commands/projects.js.map +1 -1
  14. package/dist/types/agentType.d.ts +1 -1
  15. package/dist/types/agentType.js +1 -1
  16. package/dist/types/agentType.js.map +1 -1
  17. package/dist/types/agentYaml.d.ts +91 -0
  18. package/dist/types/agentYaml.js +6 -0
  19. package/dist/types/agentYaml.js.map +1 -0
  20. package/dist/types/auth.d.ts +4 -6
  21. package/dist/types/auth.js +1 -0
  22. package/dist/types/auth.js.map +1 -1
  23. package/dist/utils/agent.d.ts +11 -0
  24. package/dist/utils/agent.js +72 -0
  25. package/dist/utils/agent.js.map +1 -1
  26. package/dist/utils/auth.d.ts +4 -0
  27. package/dist/utils/auth.js +31 -3
  28. package/dist/utils/auth.js.map +1 -1
  29. package/dist/utils/error.d.ts +8 -0
  30. package/dist/utils/error.js +163 -0
  31. package/dist/utils/error.js.map +1 -0
  32. package/dist/utils/project.d.ts +4 -1
  33. package/dist/utils/project.js +17 -10
  34. package/dist/utils/project.js.map +1 -1
  35. package/package.json +3 -1
  36. package/sample-agents.yaml +0 -0
  37. package/src/commands/agent.ts +770 -303
  38. package/src/commands/auth.ts +1 -0
  39. package/src/commands/compute.ts +30 -8
  40. package/src/commands/document.ts +2 -2
  41. package/src/commands/image.ts +121 -129
  42. package/src/commands/projects.ts +125 -34
  43. package/src/types/agentType.ts +1 -1
  44. package/src/types/agentYaml.ts +107 -0
  45. package/src/types/auth.ts +10 -10
  46. package/src/utils/agent.ts +82 -0
  47. package/src/utils/auth.ts +33 -3
  48. package/src/utils/error.ts +168 -0
  49. package/src/utils/project.ts +20 -13
  50. package/templates/template-base-js.zip +0 -0
  51. package/templates/template-base-ts.zip +0 -0
  52. package/templates/template-chatbot-js.zip +0 -0
  53. 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
- * User credentials stored in the config file
3
- */
1
+ // types/auth.ts
2
+
4
3
  export interface UserCredentials {
5
- domain: string;
6
- email: string;
7
- token: string;
8
- apiServerUrl: string;
9
- name: string;
10
- picture: string;
11
- timestamp?: number;
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
  }
@@ -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
- targetFiles = files.filter(file => file === `${email}_${domain}.json`);
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 last used, most recent first
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
- domainFiles = files.filter(file => file === `${email}_${domain}.json`);
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
+ }
@@ -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
- export async function resolveProject(projectsApi: ProjectsApi, options: CommandOptions): Promise<ProjectInfo> {
21
- const spinner = ora('Resolving project...').start();
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 (!spinner.isSpinning) {
82
+ if (spinner && spinner.isSpinning) {
75
83
  // If spinner was already stopped with fail, we don't need to fail it again
76
- throw error;
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