@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.
@@ -0,0 +1,355 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ import { getAuthToken } from '../utils/auth';
6
+ import { Configuration, MachineAgentApi, ProjectsApi } from '@nestbox-ai/admin';
7
+ import { resolveProject } from '../utils/project';
8
+ import fs from 'fs';
9
+ import { createZipFromDirectory, findProjectRoot, isTypeScriptProject, loadNestboxConfig, runPredeployScripts } from '../utils/agent';
10
+ import axios from 'axios';
11
+
12
+
13
+
14
+ export function registerAgentCommands(program: Command): void {
15
+ // Get authentication token and create API configuration
16
+ const authToken = getAuthToken();
17
+ const configuration = new Configuration({
18
+ basePath: authToken?.serverUrl,
19
+ baseOptions: {
20
+ headers: {
21
+ "Authorization": authToken?.token,
22
+ }
23
+ }
24
+ });
25
+
26
+ const agentsApi = new MachineAgentApi(configuration);
27
+ const projectsApi = new ProjectsApi(configuration);
28
+
29
+ // Create the main agent command
30
+ const agentCommand = program
31
+ .command('agent')
32
+ .description('Manage Nestbox agents');
33
+
34
+ // Add the list subcommand
35
+ agentCommand
36
+ .command('list')
37
+ .description('List all AI agents associated with the authenticated user')
38
+ .option('--project <projectName>', 'Project name (defaults to the current project)')
39
+ .action(async (options) => {
40
+ try {
41
+ if (!authToken) {
42
+ console.error(chalk.red('No authentication token found. Please login first.'));
43
+ return;
44
+ }
45
+
46
+ try {
47
+ // Use the resolveProject helper to get project information
48
+ const projectData = await resolveProject(projectsApi, options);
49
+
50
+ const spinner = ora(`Listing agents in project ${projectData.name}...`).start();
51
+
52
+ try {
53
+ // Now get the agents for the specific project
54
+ const agentsResponse: any = await agentsApi.machineAgentControllerGetMachineAgentByProjectId(projectData.id, 0, 10, "");
55
+
56
+ spinner.succeed('Successfully retrieved agents');
57
+
58
+ // Display the results
59
+ const agents = agentsResponse.data?.machineAgents || [];
60
+
61
+ if (!agents || agents.length === 0) {
62
+ console.log(chalk.yellow(`No agents found in project ${projectData.name}`));
63
+ return;
64
+ }
65
+
66
+ console.log(chalk.blue(`\nAgents in project ${projectData.name}:\n`));
67
+
68
+ // Create a formatted table focusing on id, name, and URL
69
+ const table = new Table({
70
+ head: [
71
+ chalk.white.bold('ID'),
72
+ chalk.white.bold('Name'),
73
+ chalk.white.bold('Goal'),
74
+ chalk.white.bold('URL'),
75
+ chalk.white.bold('Created At')
76
+ ],
77
+ style: {
78
+ head: [], // Disable the default styling
79
+ border: []
80
+ }
81
+ });
82
+
83
+ // Add agents to the table with the requested info
84
+ agents.forEach((agent: any) => {
85
+ // Format the agent URL
86
+ let url = 'N/A';
87
+ if (agent.instanceIP) {
88
+ // Construct an agent-specific URL if possible
89
+ url = `${agent.instanceIP}`;
90
+ }
91
+
92
+ // Format date for readability
93
+ let createdAt = agent.createdAt || 'N/A';
94
+ if (createdAt !== 'N/A') {
95
+ createdAt = new Date(createdAt).toLocaleString();
96
+ }
97
+
98
+ table.push([
99
+ agent.id || 'N/A',
100
+ agent.agentName || 'N/A',
101
+ agent.goal || 'N/A',
102
+ url,
103
+ createdAt
104
+ ]);
105
+ });
106
+
107
+ // Display the table
108
+ console.log(table.toString());
109
+
110
+ // Display totals
111
+ console.log(`\nTotal agents: ${agents.length}`);
112
+
113
+ } catch (error: any) {
114
+ spinner.fail('Failed to retrieve agents');
115
+ if (error.response) {
116
+ console.error(chalk.red('API Error:'), error.response.data?.message || 'Unknown error');
117
+ } else {
118
+ console.error(chalk.red('Error:'), error.message || 'Unknown error');
119
+ }
120
+ }
121
+ } catch (error: any) {
122
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
123
+ }
124
+ } catch (error) {
125
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
126
+ }
127
+ });
128
+
129
+
130
+ // Remove agent
131
+ agentCommand
132
+ .command('remove')
133
+ .description('Remove an AI agent')
134
+ .requiredOption('--agent <agentId>', 'Agent ID to remove')
135
+ .option('--project <projectName>', 'Project name (defaults to the current project)')
136
+ .action(async (options) => {
137
+ try {
138
+ if (!authToken) {
139
+ console.error(chalk.red('No authentication token found. Please login first.'));
140
+ return;
141
+ }
142
+
143
+ const { agent } = options;
144
+
145
+ // Use the resolveProject helper to get project information
146
+ const projectData = await resolveProject(projectsApi, options);
147
+
148
+ const spinner = ora(`Finding agent ${agent} in project ${projectData.name}...`).start();
149
+
150
+ try {
151
+ // First, get the list of agents to find the correct modelbaseId
152
+ const agentsResponse: any = await agentsApi.machineAgentControllerGetMachineAgentByProjectId(
153
+ projectData.id,
154
+ 0,
155
+ 100, // Increased to make sure we get all agents
156
+ ""
157
+ );
158
+
159
+ // Get the agents array
160
+ const agents = agentsResponse.data?.machineAgents || [];
161
+
162
+ // Find the specific agent by ID
163
+ const targetAgent = agents.find((a: any) => a.id.toString() === agent.toString());
164
+
165
+ if (!targetAgent) {
166
+ spinner.fail(`Agent with ID ${agent} not found in project ${projectData.name}`);
167
+ return;
168
+ }
169
+
170
+ // Extract the modelbaseId from the found agent
171
+ const modelbaseId = targetAgent.modelBaseId;
172
+
173
+ if (!modelbaseId) {
174
+ spinner.fail(`Could not find modelbaseId for agent ${agent}. Please try again.`);
175
+ return;
176
+ }
177
+
178
+ spinner.text = `Removing agent ${agent} from project ${projectData.name}...`;
179
+
180
+ // Now remove the agent with the dynamically retrieved modelbaseId
181
+ const payload: any = [{
182
+ id: parseInt(agent, 10),
183
+ modelbaseId: modelbaseId
184
+ }];
185
+
186
+ const removeResponse = await agentsApi.machineAgentControllerDeleteMachineAgents(
187
+ projectData.id,
188
+ agent,
189
+ payload
190
+ );
191
+
192
+ spinner.succeed('Successfully removed agent');
193
+
194
+ // Display the results
195
+ console.log(chalk.green(`Agent ${agent} removed successfully from project ${projectData.name}`));
196
+
197
+ } catch (error: any) {
198
+ spinner.fail('Failed to remove agent');
199
+ if (error.response) {
200
+ console.error(chalk.red('API Error:'), error.response.data?.message || 'Unknown error');
201
+ } else {
202
+ console.error(chalk.red('Error:'), error.message || 'Unknown error');
203
+ }
204
+ }
205
+ } catch (error) {
206
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
207
+ }
208
+ });
209
+
210
+ agentCommand
211
+ .command('deploy')
212
+ .description('Deploy an AI agent to the Nestbox platform')
213
+ .requiredOption('--agent <agentId>', 'Agent ID to deploy')
214
+ .requiredOption('--instance <instanceId>', 'Instance ID')
215
+ .requiredOption('--zip <zipFileOrDirPath>', 'Path to the zip file or directory to upload')
216
+ .option('--project <projectName>', 'Project name (defaults to the current project)')
217
+ .option('--entry <entryFunction>', 'Entry function name', 'main')
218
+ .action(async (options) => {
219
+ try {
220
+ if (!authToken) {
221
+ console.error(chalk.red('No authentication token found. Please login first.'));
222
+ return;
223
+ }
224
+
225
+ const { agent: agentId, instance: instanceId, zip: customZipPath, entry } = options;
226
+
227
+ // Find project root (CLI tools directory)
228
+ const projectRoot = await findProjectRoot();
229
+ console.log(chalk.blue(`Project root detected at: ${projectRoot}`));
230
+
231
+ // Use the resolveProject helper to get project information
232
+ const projectData = await resolveProject(projectsApi, options);
233
+
234
+ // Load nestbox.config.json from CLI tools directory
235
+ const config = loadNestboxConfig(projectRoot);
236
+
237
+ // Start the deployment process
238
+ const spinner = ora(`Preparing to deploy agent ${agentId} to instance ${instanceId}...`).start();
239
+
240
+ try {
241
+ // Determine the source path (custom path or project root)
242
+ const sourcePath = customZipPath || projectRoot;
243
+
244
+ let zipFilePath;
245
+
246
+ // Check if the specified path exists
247
+ if (!fs.existsSync(sourcePath)) {
248
+ spinner.fail(`Path not found: ${sourcePath}`);
249
+ return;
250
+ }
251
+
252
+ // Check if the path is a zip file or directory
253
+ const stats = fs.statSync(sourcePath);
254
+
255
+ if (stats.isFile()) {
256
+ // Case 1: It's a file - verify it's a zip and use directly
257
+ if (!sourcePath.toLowerCase().endsWith('.zip')) {
258
+ spinner.fail(`File is not a zip archive: ${sourcePath}`);
259
+ return;
260
+ }
261
+
262
+ // Use the zip file directly
263
+ spinner.text = `Using provided zip file: ${sourcePath}`;
264
+ zipFilePath = sourcePath;
265
+
266
+ } else if (stats.isDirectory()) {
267
+ // Case 2: It's a directory - check for predeploy scripts in CLI config
268
+
269
+ // Determine if it's a TypeScript project
270
+ const isTypeScript = isTypeScriptProject(sourcePath);
271
+
272
+ if (isTypeScript) {
273
+ spinner.text = `TypeScript project detected. Checking for predeploy scripts...`;
274
+
275
+ // Run predeploy scripts if defined in CLI tools config
276
+ if (config?.agent?.predeploy || config?.agents?.predeploy) {
277
+ const predeployScripts = config?.agent?.predeploy || config?.agents?.predeploy;
278
+ spinner.text = `Running predeploy scripts on target directory...`;
279
+ await runPredeployScripts(predeployScripts, sourcePath);
280
+ } else {
281
+ spinner.info('No predeploy scripts found in CLI tools nestbox.config.json');
282
+ }
283
+ } else {
284
+ // JavaScript directory - just zip it
285
+ spinner.text = `JavaScript project detected. Skipping predeploy scripts.`;
286
+ }
287
+
288
+ // Create zip archive with node_modules excluded
289
+ spinner.text = `Creating zip archive from directory ${sourcePath}...`;
290
+ zipFilePath = createZipFromDirectory(sourcePath);
291
+ spinner.text = `Directory zipped successfully to ${zipFilePath}`;
292
+ } else {
293
+ spinner.fail(`Unsupported file type: ${sourcePath}`);
294
+ return;
295
+ }
296
+
297
+ spinner.text = `Deploying agent ${agentId} to instance ${instanceId}...`;
298
+
299
+ // Clean the base URL to avoid path duplication
300
+ const baseUrl = authToken?.serverUrl?.endsWith('/')
301
+ ? authToken.serverUrl.slice(0, -1)
302
+ : authToken?.serverUrl;
303
+
304
+ const FormData = require('form-data');
305
+ const form = new FormData();
306
+
307
+ // Add file as a readable stream
308
+ form.append('file', fs.createReadStream(zipFilePath));
309
+
310
+ // Add all the required fields
311
+ form.append('machineAgentId', agentId.toString());
312
+ form.append('instanceId', instanceId.toString());
313
+ form.append('entryFunctionName', entry);
314
+ form.append('isSourceCodeUpdate', 'true');
315
+ form.append('projectId', projectData.id);
316
+
317
+ // Create a custom axios instance with form-data headers
318
+ const axiosInstance = axios.create({
319
+ baseURL: baseUrl,
320
+ headers: {
321
+ ...form.getHeaders(),
322
+ "Authorization": authToken?.token
323
+ }
324
+ });
325
+
326
+ // Construct the endpoint URL
327
+ const endpoint = `/projects/${projectData.id}/agents/${agentId}`;
328
+
329
+ // Make direct axios request
330
+ await axiosInstance.patch(endpoint, form);
331
+
332
+ // Clean up temporary zip file if we created one
333
+ if (zipFilePath !== sourcePath && fs.existsSync(zipFilePath)) {
334
+ fs.unlinkSync(zipFilePath);
335
+ }
336
+
337
+ spinner.succeed(`Successfully deployed agent ${agentId} to instance ${instanceId}`);
338
+ console.log(chalk.green('Agent deployed successfully'));
339
+
340
+ } catch (error: any) {
341
+ spinner.fail('Failed to deploy agent');
342
+
343
+ if (error.response) {
344
+ console.error(chalk.red(`API Error (${error.response.status}): ${error.response.data?.message || 'Unknown error'}`));
345
+ } else {
346
+ console.error(chalk.red('Error:'), error.message || 'Unknown error');
347
+ }
348
+ }
349
+ } catch (error) {
350
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
351
+ }
352
+ })
353
+
354
+
355
+ }