@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
@@ -1,35 +1,159 @@
1
1
  import { Command } from "commander";
2
+ import { handle401Error, withTokenRefresh } from "../utils/error";
2
3
  import chalk from "chalk";
3
4
  import ora from "ora";
4
5
  import Table from "cli-table3";
5
6
  import { getAuthToken } from "../utils/auth";
6
- import { Configuration, MachineAgentApi, ProjectsApi } from "@nestbox-ai/admin";
7
+ import { Configuration, MachineAgentApi, MachineInstancesApi, ProjectsApi } from "@nestbox-ai/admin";
7
8
  import { resolveProject } from "../utils/project";
8
9
  import fs from "fs";
10
+ import yaml from 'js-yaml';
9
11
  import {
12
+ createNestboxConfig,
10
13
  createZipFromDirectory,
14
+ downloadFromGoogleDrive,
15
+ extractZip,
11
16
  findProjectRoot,
12
17
  isTypeScriptProject,
13
18
  loadNestboxConfig,
14
19
  runPredeployScripts,
20
+ TEMPLATES,
15
21
  } from "../utils/agent";
16
22
  import axios from "axios";
17
23
  import { AgentType } from "../types/agentType";
18
-
19
- export function registerAgentCommands(program: Command): void {
20
- // Get authentication token and create API configuration
24
+ import { AgentYamlConfig } from "../types/agentYaml";
25
+ import inquirer from "inquirer";
26
+ import path from "path";
27
+ import { userData } from "../utils/user";
28
+
29
+ /**
30
+ * Create a new agent in the Nestbox platform
31
+ *
32
+ * @param agentName The name of the agent
33
+ * @param options Options including lang, template, and project
34
+ * @param agentsApi The MachineAgentApi instance
35
+ * @param projectsApi The ProjectsApi instance
36
+ */
37
+ async function createAgent(
38
+ agentName: string,
39
+ options: {
40
+ lang?: string;
41
+ template?: string;
42
+ project?: string;
43
+ instanceName?: string;
44
+ machineManifestId?: string;
45
+ type?: string;
46
+ goal?: string;
47
+ modelBaseId?: string;
48
+ machineName?: string;
49
+ machineInstanceId?: number;
50
+ instanceIP?: string;
51
+ userId?: number;
52
+ parameters?: Array<{name: string; description: string; default: any}>;
53
+ },
54
+ agentsApi?: MachineAgentApi,
55
+ projectsApi?: ProjectsApi
56
+ ): Promise<any> {
21
57
  const authToken = getAuthToken();
22
- const configuration = new Configuration({
23
- basePath: authToken?.serverUrl,
24
- baseOptions: {
25
- headers: {
26
- Authorization: authToken?.token,
58
+ if (!authToken) {
59
+ throw new Error("No authentication token found. Please login first.");
60
+ }
61
+
62
+ // Create API instances if not provided
63
+ if (!agentsApi || !projectsApi) {
64
+ const configuration = new Configuration({
65
+ basePath: authToken?.serverUrl,
66
+ baseOptions: {
67
+ headers: {
68
+ Authorization: authToken?.token,
69
+ },
27
70
  },
28
- },
71
+ });
72
+
73
+ agentsApi = agentsApi || new MachineAgentApi(configuration);
74
+ projectsApi = projectsApi || new ProjectsApi(configuration);
75
+ }
76
+
77
+ // Resolve project - convert options to match CommandOptions interface
78
+ const projectData = await resolveProject(projectsApi, {
79
+ project: options.project,
80
+ instance: options.instanceName || '',
81
+ ...options
29
82
  });
30
83
 
31
- const agentsApi = new MachineAgentApi(configuration);
32
- const projectsApi = new ProjectsApi(configuration);
84
+ // Prepare agent creation payload
85
+ // Determine the correct type value based on options.type
86
+ const agentTypeValue = options.type?.includes("AGENT") ? "REGULAR" : options.type || "CHAT";
87
+
88
+ const payload: any = {
89
+ agentName,
90
+ goal: options.goal || `AI agent for ${agentName}`,
91
+ modelBaseId: options.modelBaseId || "",
92
+ machineName: options.machineName,
93
+ machineInstanceId: options.machineInstanceId,
94
+ instanceIP: options.instanceIP,
95
+ machineManifestId: options.machineManifestId,
96
+ parameters: options.parameters?.map((param: any) => ({
97
+ name: param.name,
98
+ description: param.description,
99
+ default_value: param.default || "",
100
+ isUserParam: param.isUserParam !== undefined ? param.isUserParam : true
101
+ })) || [],
102
+ projectId: projectData.id,
103
+ type: agentTypeValue,
104
+ userId: options.userId || 0,
105
+ };
106
+
107
+ // Determine the type of resource (Agent or Chat)
108
+ const agentType = options.type || "CHAT";
109
+ const resourceType = agentType === "AGENT" || agentType === "REGULAR" ? "Agent" : "Chatbot";
110
+
111
+ // Create the agent
112
+ const spinner = ora(`Creating ${resourceType.toLowerCase()} ${agentName}...`).start();
113
+
114
+ try {
115
+ const response = await agentsApi.machineAgentControllerCreateMachineAgent(
116
+ projectData.id,
117
+ payload
118
+ );
119
+ spinner.succeed(`${resourceType} '${agentName}' created successfully`);
120
+ return response.data;
121
+ } catch (error: any) {
122
+ spinner.fail(`Failed to create ${resourceType.toLowerCase()} ${agentName}`);
123
+
124
+ if (error.response && error.response.status === 401) {
125
+ throw new Error('Authentication token has expired. Please login again using "nestbox login <domain>".');
126
+ } else if (error.response) {
127
+ throw new Error(`API Error (${error.response.status}): ${error.response.data?.message || "Unknown error"}`);
128
+ } else {
129
+ throw new Error(error.message || "Unknown error");
130
+ }
131
+ }
132
+ }
133
+
134
+ export function registerAgentCommands(program: Command): void {
135
+ // Function to create/recreate API instances
136
+ const createApis = () => {
137
+ const authToken = getAuthToken();
138
+ if (!authToken) {
139
+ throw new Error('No authentication token found. Please log in first.');
140
+ }
141
+
142
+ const configuration = new Configuration({
143
+ basePath: authToken.serverUrl,
144
+ baseOptions: {
145
+ headers: {
146
+ Authorization: authToken.token,
147
+ },
148
+ },
149
+ });
150
+
151
+ return {
152
+ agentsApi: new MachineAgentApi(configuration),
153
+ projectsApi: new ProjectsApi(configuration),
154
+ instanceApi: new MachineInstancesApi(configuration)
155
+ };
156
+ };
33
157
 
34
158
  // Create the main agent command
35
159
  const agentCommand = program
@@ -46,108 +170,88 @@ export function registerAgentCommands(program: Command): void {
46
170
  )
47
171
  .action(async (options) => {
48
172
  try {
49
- if (!authToken) {
50
- console.error(
51
- chalk.red("No authentication token found. Please login first.")
52
- );
53
- return;
54
- }
55
-
56
- try {
57
- // Use the resolveProject helper to get project information
58
- const projectData = await resolveProject(projectsApi, options);
173
+ let apis = createApis();
174
+
175
+ // Execute with token refresh support
176
+ await withTokenRefresh(
177
+ async () => {
178
+ // Resolve project
179
+ const projectData = await resolveProject(apis.projectsApi, options);
180
+
181
+ const spinner = ora(
182
+ `Listing agents in project ${projectData.name}...`
183
+ ).start();
184
+
185
+ try {
186
+ // Get the agents for the specific project
187
+ const agentsResponse: any =
188
+ await apis.agentsApi.machineAgentControllerGetMachineAgentByProjectId(
189
+ projectData.id,
190
+ 0,
191
+ 10,
192
+ AgentType.REGULAR
193
+ );
59
194
 
60
- const spinner = ora(
61
- `Listing agents in project ${projectData.name}...`
62
- ).start();
195
+ spinner.succeed("Successfully retrieved agents");
63
196
 
64
- try {
65
- // Now get the agents for the specific project
66
- const agentsResponse: any =
67
- await agentsApi.machineAgentControllerGetMachineAgentByProjectId(
68
- projectData.id,
69
- 0,
70
- 10,
71
- AgentType.REGULAR
72
- );
197
+ // Display the results
198
+ const agents = agentsResponse.data?.machineAgents || [];
73
199
 
74
- spinner.succeed("Successfully retrieved agents");
75
-
76
- // Display the results
77
- const agents = agentsResponse.data?.machineAgents || [];
200
+ if (!agents || agents.length === 0) {
201
+ console.log(
202
+ chalk.yellow(`No agents found in project ${projectData.name}`)
203
+ );
204
+ return;
205
+ }
78
206
 
79
- if (!agents || agents.length === 0) {
80
207
  console.log(
81
- chalk.yellow(`No agents found in project ${projectData.name}`)
208
+ chalk.blue(`\nAgents in project ${projectData.name}:\n`)
82
209
  );
83
- return;
84
- }
85
210
 
86
- console.log(
87
- chalk.blue(`\nAgents in project ${projectData.name}:\n`)
88
- );
89
-
90
- // Create a formatted table focusing on id, name, and URL
91
- const table = new Table({
92
- head: [
93
- chalk.white.bold("ID"),
94
- chalk.white.bold("Name"),
95
- chalk.white.bold("URL"),
96
- ],
97
- style: {
98
- head: [], // Disable the default styling
99
- border: [],
100
- },
101
- });
102
-
103
- // Add agents to the table with the requested info
104
- agents.forEach((agent: any) => {
105
- // Format the agent URL
106
- let url = "N/A";
107
- if (agent.instanceIP) {
108
- // Construct an agent-specific URL if possible
109
- url = `${agent.instanceIP}/v1/agents/${agent.modelBaseId}/query`;
110
- }
111
-
112
- // Format date for readability
113
- let createdAt = agent.createdAt || "N/A";
114
- if (createdAt !== "N/A") {
115
- createdAt = new Date(createdAt).toLocaleString();
116
- }
117
-
118
- table.push([agent.id || "N/A", agent.agentName || "N/A", url]);
119
- });
120
-
121
- // Display the table
122
- console.log(table.toString());
123
-
124
- // Display totals
125
- console.log(`\nTotal agents: ${agents.length}`);
126
- } catch (error: any) {
127
- spinner.fail("Failed to retrieve agents");
128
- if (error.response) {
129
- console.error(
130
- chalk.red("API Error:"),
131
- error.response.data?.message || "Unknown error"
132
- );
133
- } else {
134
- console.error(
135
- chalk.red("Error:"),
136
- error.message || "Unknown error"
137
- );
211
+ // Create a formatted table
212
+ const table = new Table({
213
+ head: [
214
+ chalk.white.bold("ID"),
215
+ chalk.white.bold("Name"),
216
+ chalk.white.bold("URL"),
217
+ ],
218
+ style: {
219
+ head: [],
220
+ border: [],
221
+ },
222
+ });
223
+
224
+ // Add agents to the table
225
+ agents.forEach((agent: any) => {
226
+ let url = "N/A";
227
+ if (agent.instanceIP) {
228
+ url = `${agent.instanceIP}/v1/agents/${agent.modelBaseId}/query`;
229
+ }
230
+
231
+ table.push([agent.id || "N/A", agent.agentName || "N/A", url]);
232
+ });
233
+
234
+ console.log(table.toString());
235
+ console.log(`\nTotal agents: ${agents.length}`);
236
+
237
+ } catch (error: any) {
238
+ spinner.fail("Failed to retrieve agents");
239
+ throw error;
138
240
  }
241
+ },
242
+ () => {
243
+ // Recreate APIs after token refresh
244
+ apis = createApis();
139
245
  }
140
- } catch (error: any) {
141
- console.error(
142
- chalk.red("Error:"),
143
- error instanceof Error ? error.message : "Unknown error"
144
- );
145
- }
146
- } catch (error) {
147
- console.error(
148
- chalk.red("Error:"),
149
- error instanceof Error ? error.message : "Unknown error"
150
246
  );
247
+ } catch (error: any) {
248
+ if (error.message && error.message.includes('Authentication')) {
249
+ console.error(chalk.red(error.message));
250
+ } else if (error.response?.data?.message) {
251
+ console.error(chalk.red("API Error:"), error.response.data.message);
252
+ } else {
253
+ console.error(chalk.red("Error:"), error.message || "Unknown error");
254
+ }
151
255
  }
152
256
  });
153
257
 
@@ -162,109 +266,96 @@ export function registerAgentCommands(program: Command): void {
162
266
  )
163
267
  .action(async (options) => {
164
268
  try {
165
- if (!authToken) {
166
- console.error(
167
- chalk.red("No authentication token found. Please login first.")
168
- );
169
- return;
170
- }
171
-
269
+ let apis = createApis();
172
270
  const { agent } = options;
173
271
 
174
- // Use the resolveProject helper to get project information
175
- const projectData = await resolveProject(projectsApi, options);
176
-
177
- const spinner = ora(
178
- `Finding agent ${agent} in project ${projectData.name}...`
179
- ).start();
180
-
181
- try {
182
- // First, get the list of agents to find the correct modelbaseId
183
- const agentsResponse: any =
184
- await agentsApi.machineAgentControllerGetMachineAgentByProjectId(
185
- projectData.id,
186
- 0,
187
- 100,
188
- AgentType.REGULAR
189
- );
190
-
191
- // Get the agents array
192
- const agents = agentsResponse.data?.machineAgents || [];
193
-
194
- // Find the specific agent by ID
195
- const targetAgent = agents.find(
196
- (a: any) => a.id.toString() === agent.toString()
197
- );
198
-
199
- if (!targetAgent) {
200
- spinner.fail(
201
- `Agent with ID ${agent} not found in project ${projectData.name}`
202
- );
203
- return;
204
- }
272
+ await withTokenRefresh(
273
+ async () => {
274
+ // Resolve project
275
+ const projectData = await resolveProject(apis.projectsApi, options);
276
+
277
+ const spinner = ora(
278
+ `Finding agent ${agent} in project ${projectData.name}...`
279
+ ).start();
280
+
281
+ try {
282
+ // Get the list of agents to find the correct modelbaseId
283
+ const agentsResponse: any =
284
+ await apis.agentsApi.machineAgentControllerGetMachineAgentByProjectId(
285
+ projectData.id,
286
+ 0,
287
+ 100,
288
+ AgentType.REGULAR
289
+ );
205
290
 
206
- // Extract the modelbaseId from the found agent
207
- const modelbaseId = targetAgent.modelBaseId;
291
+ const agents = agentsResponse.data?.machineAgents || [];
292
+ const targetAgent = agents.find(
293
+ (a: any) => a.id.toString() === agent.toString()
294
+ );
208
295
 
209
- if (!modelbaseId) {
210
- spinner.fail(
211
- `Could not find modelbaseId for agent ${agent}. Please try again.`
212
- );
213
- return;
214
- }
296
+ if (!targetAgent) {
297
+ spinner.fail(
298
+ `Agent with ID ${agent} not found in project ${projectData.name}`
299
+ );
300
+ return;
301
+ }
215
302
 
216
- spinner.text = `Removing agent ${agent} from project ${projectData.name}...`;
303
+ const modelbaseId = targetAgent.modelBaseId;
304
+ if (!modelbaseId) {
305
+ spinner.fail(
306
+ `Could not find modelbaseId for agent ${agent}. Please try again.`
307
+ );
308
+ return;
309
+ }
217
310
 
218
- // Now remove the agent with the dynamically retrieved modelbaseId
219
- const payload: any = [
220
- {
221
- id: parseInt(agent, 10),
222
- modelbaseId: modelbaseId,
223
- },
224
- ];
311
+ spinner.text = `Removing agent ${agent} from project ${projectData.name}...`;
225
312
 
226
- const removeResponse =
227
- await agentsApi.machineAgentControllerDeleteMachineAgents(
228
- projectData.id,
229
- agent,
230
- payload
231
- );
313
+ // Remove the agent
314
+ const payload: any = [
315
+ {
316
+ id: parseInt(agent, 10),
317
+ modelbaseId: modelbaseId,
318
+ },
319
+ ];
232
320
 
233
- spinner.succeed("Successfully removed agent");
321
+ await apis.agentsApi.machineAgentControllerDeleteMachineAgents(
322
+ projectData.id,
323
+ agent,
324
+ payload
325
+ );
234
326
 
235
- // Display the results
236
- console.log(
237
- chalk.green(
238
- `Agent ${agent} removed successfully from project ${projectData.name}`
239
- )
240
- );
241
- } catch (error: any) {
242
- spinner.fail("Failed to remove agent");
243
- if (error.response) {
244
- console.error(
245
- chalk.red("API Error:"),
246
- error.response.data?.message || "Unknown error"
247
- );
248
- } else {
249
- console.error(
250
- chalk.red("Error:"),
251
- error.message || "Unknown error"
252
- );
327
+ spinner.succeed("Successfully removed agent");
328
+ console.log(
329
+ chalk.green(
330
+ `Agent ${agent} removed successfully from project ${projectData.name}`
331
+ )
332
+ );
333
+ } catch (error: any) {
334
+ spinner.fail("Failed to remove agent");
335
+ throw error;
336
+ }
337
+ },
338
+ () => {
339
+ apis = createApis();
253
340
  }
254
- }
255
- } catch (error) {
256
- console.error(
257
- chalk.red("Error:"),
258
- error instanceof Error ? error.message : "Unknown error"
259
341
  );
342
+ } catch (error: any) {
343
+ if (error.message && error.message.includes('Authentication')) {
344
+ console.error(chalk.red(error.message));
345
+ } else if (error.response?.data?.message) {
346
+ console.error(chalk.red("API Error:"), error.response.data.message);
347
+ } else {
348
+ console.error(chalk.red("Error:"), error.message || "Unknown error");
349
+ }
260
350
  }
261
351
  });
262
352
 
263
353
  agentCommand
264
354
  .command("deploy")
265
355
  .description("Deploy an AI agent to the Nestbox platform")
266
- .requiredOption("--agent <agentId>", "Agent ID to deploy")
267
- .requiredOption("--instance <instanceId>", "Instance ID")
356
+ .option("--agent <agentName>", "Agent name to deploy")
357
+ .option("--chatbot <chatbotName>", "Chatbot name to deploy")
358
+ .requiredOption("--instance <instanceName>", "Instance name")
268
359
  .option(
269
360
  "--zip <zipFileOrDirPath>",
270
361
  "Path to the zip file or directory to upload"
@@ -276,147 +367,519 @@ export function registerAgentCommands(program: Command): void {
276
367
  .option("--entry <entryFunction>", "Entry function name", "main")
277
368
  .action(async (options) => {
278
369
  try {
279
- if (!authToken) {
370
+ const {
371
+ agent: agentName,
372
+ chatbot: chatbotName,
373
+ instance: instanceName,
374
+ zip: customZipPath,
375
+ entry,
376
+ } = options;
377
+
378
+ // Ensure either agent or chatbot is provided, but not both
379
+ if ((!agentName && !chatbotName) || (agentName && chatbotName)) {
280
380
  console.error(
281
- chalk.red("No authentication token found. Please login first.")
381
+ chalk.red("Please provide either --agent OR --chatbot option, but not both.")
282
382
  );
283
383
  return;
284
384
  }
285
385
 
286
- const {
287
- agent: agentId,
288
- instance: instanceId,
289
- zip: customZipPath,
290
- entry,
291
- } = options;
386
+ let apis = createApis();
292
387
 
293
- // Find project root (CLI tools directory)
388
+ // Find project root
294
389
  const projectRoot = await findProjectRoot();
295
390
  console.log(chalk.blue(`Project root detected at: ${projectRoot}`));
296
391
 
297
- // Use the resolveProject helper to get project information
298
- const projectData = await resolveProject(projectsApi, options);
392
+ // Main deployment logic with token refresh
393
+ await withTokenRefresh(
394
+ async () => {
395
+ // Resolve project
396
+ const projectData = await resolveProject(apis.projectsApi, options);
397
+
398
+ // Determine if we're deploying an agent or chatbot
399
+ const isAgent = !!agentName;
400
+ const resourceName = isAgent ? agentName : chatbotName;
401
+ const resourceType = isAgent ? "Agent" : "Chatbot";
402
+ const agentType = isAgent ? AgentType.REGULAR : "CHAT";
403
+
404
+ // Get agents data and find agent/chatbot by name
405
+ const agentsData: any = await apis.agentsApi.machineAgentControllerGetMachineAgentByProjectId(
406
+ projectData.id,
407
+ 0,
408
+ 10,
409
+ agentType
410
+ );
299
411
 
300
- // Load nestbox.config.json from CLI tools directory
301
- const config = loadNestboxConfig(projectRoot);
412
+ const targetAgent = agentsData.data.machineAgents.find(
413
+ (agent: any) => agent.agentName === resourceName
414
+ );
302
415
 
303
- // Start the deployment process
304
- const spinner = ora(
305
- `Preparing to deploy agent ${agentId} to instance ${instanceId}...`
306
- ).start();
416
+ if (!targetAgent) {
417
+ console.error(
418
+ chalk.red(`${resourceType} with name "${resourceName}" not found in project "${projectData.name}".`)
419
+ );
420
+ console.log(chalk.yellow(`Available ${resourceType.toLowerCase()}s:`));
421
+ agentsData.data.machineAgents.forEach((agent: any) => {
422
+ console.log(chalk.yellow(` - ${agent.agentName} (ID: ${agent.id})`));
423
+ });
424
+ return;
425
+ }
307
426
 
308
- try {
309
- // Determine the source path (custom path or project root)
310
- const sourcePath = customZipPath || projectRoot;
427
+ // Get instance data and find instance by name
428
+ const instanceData: any = await apis.instanceApi.machineInstancesControllerGetMachineInstanceByUserId(
429
+ projectData.id,
430
+ 0,
431
+ 10
432
+ );
433
+
434
+ const targetInstance = instanceData.data.machineInstances.find(
435
+ (instance: any) => instance.instanceName === instanceName
436
+ );
311
437
 
312
- let zipFilePath;
438
+ if (!targetInstance) {
439
+ console.error(
440
+ chalk.red(`Instance with name "${instanceName}" not found in project "${projectData.name}".`)
441
+ );
442
+ console.log(chalk.yellow("Available instances:"));
443
+ instanceData.data.machineInstances.forEach((instance: any) => {
444
+ console.log(chalk.yellow(` - ${instance.instanceName} (ID: ${instance.id})`));
445
+ });
446
+ return;
447
+ }
313
448
 
314
- // Check if the specified path exists
315
- if (!fs.existsSync(sourcePath)) {
316
- spinner.fail(`Path not found: ${sourcePath}`);
317
- return;
318
- }
449
+ // Extract IDs
450
+ const agentId = targetAgent.id;
451
+ const instanceId = targetInstance.id;
452
+
453
+ // Load nestbox.config.json
454
+ const config = loadNestboxConfig(projectRoot);
455
+
456
+ // Start the deployment process
457
+ const spinner = ora(
458
+ `Preparing to deploy ${resourceType.toLowerCase()} ${agentId} to instance ${instanceId}...`
459
+ ).start();
460
+
461
+ try {
462
+ let zipFilePath;
463
+
464
+ if (customZipPath) {
465
+ // Process custom zip path
466
+ if (!fs.existsSync(customZipPath)) {
467
+ spinner.fail(`Path not found: ${customZipPath}`);
468
+ return;
469
+ }
470
+
471
+ const stats = fs.statSync(customZipPath);
472
+
473
+ if (stats.isFile()) {
474
+ if (!customZipPath.toLowerCase().endsWith(".zip")) {
475
+ spinner.fail(`File is not a zip archive: ${customZipPath}`);
476
+ return;
477
+ }
478
+ spinner.text = `Using provided zip file: ${customZipPath}`;
479
+ zipFilePath = customZipPath;
480
+ } else if (stats.isDirectory()) {
481
+ // Process directory
482
+ spinner.text = `Processing directory: ${customZipPath}`;
483
+
484
+ const isTypeScript = isTypeScriptProject(customZipPath);
485
+
486
+ if (isTypeScript && (config?.agent?.predeploy || config?.agents?.predeploy)) {
487
+ const predeployScripts = config?.agent?.predeploy || config?.agents?.predeploy;
488
+ spinner.text = `Running predeploy scripts on target directory...`;
489
+ await runPredeployScripts(predeployScripts, customZipPath);
490
+ }
491
+
492
+ spinner.text = `Creating zip archive from directory ${customZipPath}...`;
493
+ zipFilePath = createZipFromDirectory(customZipPath);
494
+ spinner.text = `Directory zipped successfully to ${zipFilePath}`;
495
+ }
496
+ } else {
497
+ // Use project root
498
+ spinner.text = `Using project root: ${projectRoot}`;
499
+
500
+ const isTypeScript = isTypeScriptProject(projectRoot);
501
+
502
+ if (isTypeScript && (config?.agent?.predeploy || config?.agents?.predeploy)) {
503
+ const predeployScripts = config?.agent?.predeploy || config?.agents?.predeploy;
504
+ spinner.text = `Running predeploy scripts on project root...`;
505
+ await runPredeployScripts(predeployScripts, projectRoot);
506
+ }
507
+
508
+ spinner.text = `Creating zip archive from project root ${projectRoot}...`;
509
+ zipFilePath = createZipFromDirectory(projectRoot);
510
+ spinner.text = `Directory zipped successfully to ${zipFilePath}`;
511
+ }
319
512
 
320
- // Check if the path is a zip file or directory
321
- const stats = fs.statSync(sourcePath);
513
+ spinner.text = `Deploying ${resourceType.toLowerCase()} ${agentId} to instance ${instanceId}...`;
514
+
515
+ // Prepare deployment
516
+ const authToken = getAuthToken();
517
+ const baseUrl = authToken?.serverUrl?.endsWith("/")
518
+ ? authToken.serverUrl.slice(0, -1)
519
+ : authToken?.serverUrl;
520
+
521
+ const { default: FormData } = await import("form-data");
522
+ const form = new FormData();
523
+
524
+ form.append("file", fs.createReadStream(zipFilePath));
525
+ form.append("machineAgentId", agentId.toString());
526
+ form.append("instanceId", instanceId.toString());
527
+ form.append("entryFunctionName", entry);
528
+ form.append("isSourceCodeUpdate", "true");
529
+ form.append("projectId", projectData.id);
530
+
531
+ const axiosInstance = axios.create({
532
+ baseURL: baseUrl,
533
+ headers: {
534
+ ...form.getHeaders(),
535
+ Authorization: authToken?.token,
536
+ },
537
+ });
538
+
539
+ const endpoint = `/projects/${projectData.id}/agents/${agentId}`;
540
+
541
+ console.log(chalk.blue("\nMaking API request:"));
542
+ console.log(chalk.blue(` URL: ${baseUrl}${endpoint}`));
543
+ console.log(chalk.blue(` Method: PATCH`));
544
+ console.log(chalk.blue(` File: ${path.basename(zipFilePath)}`));
545
+
546
+ spinner.text = `Sending API request to deploy ${resourceType.toLowerCase()}...`;
547
+ const res = await axiosInstance.patch(endpoint, form);
548
+
549
+ console.log(chalk.green("\nAPI Response received:"));
550
+ console.log(chalk.green(` Status: ${res.status} ${res.statusText}`));
551
+
552
+ if (!customZipPath && zipFilePath && fs.existsSync(zipFilePath)) {
553
+ fs.unlinkSync(zipFilePath);
554
+ }
322
555
 
323
- if (stats.isFile()) {
324
- // Case 1: It's a file - verify it's a zip and use directly
325
- if (!sourcePath.toLowerCase().endsWith(".zip")) {
326
- spinner.fail(`File is not a zip archive: ${sourcePath}`);
327
- return;
556
+ spinner.succeed(
557
+ `Successfully deployed ${resourceType.toLowerCase()} ${agentId} to instance ${instanceId}`
558
+ );
559
+ console.log(chalk.green(`${resourceType} deployed successfully`));
560
+ } catch (error: any) {
561
+ spinner.fail(`Failed to deploy ${resourceType.toLowerCase()}`);
562
+ throw error;
328
563
  }
564
+ },
565
+ () => {
566
+ apis = createApis();
567
+ }
568
+ );
569
+ } catch (error: any) {
570
+ if (error.message && error.message.includes('Authentication')) {
571
+ console.error(chalk.red(error.message));
572
+ } else if (error.response) {
573
+ console.error(
574
+ chalk.red(
575
+ `API Error (${error.response.status}): ${error.response.data?.message || "Unknown error"}`
576
+ )
577
+ );
578
+ if (error.response.data) {
579
+ console.error(chalk.red(`Error Data: ${JSON.stringify(error.response.data, null, 2)}`));
580
+ }
581
+ } else {
582
+ console.error(chalk.red("Error:"), error.message || "Unknown error");
583
+ }
584
+ }
585
+ });
329
586
 
330
- // Use the zip file directly
331
- spinner.text = `Using provided zip file: ${sourcePath}`;
332
- zipFilePath = sourcePath;
333
- } else if (stats.isDirectory()) {
334
- // Case 2: It's a directory - check for predeploy scripts in CLI config
587
+ agentCommand
588
+ .command("generate <folder>")
589
+ .description("Generate a new project from templates")
590
+ .option("--lang <language>", "Project language (ts|js)")
591
+ .option("--template <type>", "Template type (agent|chatbot)")
592
+ .option("--project <projectId>", "Project ID")
593
+ .action(async (folder, options) => {
594
+ try {
595
+ const spinner = ora("Initializing project generation...").start();
335
596
 
336
- // Determine if it's a TypeScript project
337
- const isTypeScript = isTypeScriptProject(sourcePath);
597
+ // Ensure target folder doesn't exist
598
+ if (fs.existsSync(folder)) {
599
+ spinner.fail(`Folder ${folder} already exists`);
600
+ return;
601
+ }
338
602
 
339
- if (isTypeScript) {
340
- spinner.text = `TypeScript project detected. Checking for predeploy scripts...`;
603
+ let selectedLang = options.lang;
604
+ let selectedTemplate = options.template;
341
605
 
342
- // Run predeploy scripts if defined in CLI tools config
343
- if (config?.agent?.predeploy || config?.agents?.predeploy) {
344
- const predeployScripts =
345
- config?.agent?.predeploy || config?.agents?.predeploy;
346
- spinner.text = `Running predeploy scripts on target directory...`;
347
- await runPredeployScripts(predeployScripts, sourcePath);
348
- } else {
349
- spinner.info(
350
- "No predeploy scripts found in CLI tools nestbox.config.json"
351
- );
352
- }
353
- } else {
354
- // JavaScript directory - just zip it
355
- spinner.text = `JavaScript project detected. Skipping predeploy scripts.`;
606
+ // Interactive selection if not provided
607
+ if (!selectedLang || !selectedTemplate) {
608
+ spinner.stop();
609
+
610
+ const answers = await inquirer.prompt([
611
+ {
612
+ type: 'list',
613
+ name: 'lang',
614
+ message: 'Select project language:',
615
+ choices: [
616
+ { name: 'TypeScript', value: 'ts' },
617
+ { name: 'JavaScript', value: 'js' }
618
+ ],
619
+ when: () => !selectedLang
620
+ },
621
+ {
622
+ type: 'list',
623
+ name: 'template',
624
+ message: 'Select template type:',
625
+ choices: [
626
+ { name: 'Agent', value: 'agent' },
627
+ { name: 'Chatbot', value: 'chatbot' }
628
+ ],
629
+ when: () => !selectedTemplate
356
630
  }
631
+ ]);
357
632
 
358
- // Create zip archive with node_modules excluded
359
- spinner.text = `Creating zip archive from directory ${sourcePath}...`;
360
- zipFilePath = createZipFromDirectory(sourcePath);
361
- spinner.text = `Directory zipped successfully to ${zipFilePath}`;
362
- } else {
363
- spinner.fail(`Unsupported file type: ${sourcePath}`);
364
- return;
365
- }
366
-
367
- spinner.text = `Deploying agent ${agentId} to instance ${instanceId}...`;
633
+ selectedLang = selectedLang || answers.lang;
634
+ selectedTemplate = selectedTemplate || answers.template;
635
+
636
+ spinner.start("Generating project...");
637
+ }
368
638
 
369
- // Clean the base URL to avoid path duplication
370
- const baseUrl = authToken?.serverUrl?.endsWith("/")
371
- ? authToken.serverUrl.slice(0, -1)
372
- : authToken?.serverUrl;
373
639
 
374
- const FormData = require("form-data");
375
- const form = new FormData();
640
+ // Find matching template in local templates folder
641
+ const templateMapping: Record<string, string> = {
642
+ 'agent': 'base',
643
+ 'chatbot': 'chatbot'
644
+ };
645
+ const mappedTemplateType = templateMapping[selectedTemplate] || selectedTemplate;
646
+ const templateKey = `template-${mappedTemplateType}-${selectedLang}.zip`;
647
+ // Try process.cwd() first, then __dirname fallback
648
+ let templatePath = path.resolve(process.cwd(), 'templates', templateKey);
649
+ if (!fs.existsSync(templatePath)) {
650
+ // fallback to __dirname
651
+ templatePath = path.resolve(__dirname, '../../templates', templateKey);
652
+ }
653
+ if (!fs.existsSync(templatePath)) {
654
+ spinner.fail(`Template not found: ${templatePath}`);
655
+ // Show available templates in both locations
656
+ const cwdTemplates = path.resolve(process.cwd(), 'templates');
657
+ const dirTemplates = path.resolve(__dirname, '../../templates');
658
+ let shown = false;
659
+ if (fs.existsSync(cwdTemplates)) {
660
+ console.log(chalk.yellow('Available templates in ./templates:'));
661
+ fs.readdirSync(cwdTemplates).forEach(file => {
662
+ console.log(chalk.yellow(` - ${file}`));
663
+ });
664
+ shown = true;
665
+ }
666
+ if (fs.existsSync(dirTemplates)) {
667
+ console.log(chalk.yellow('Available templates in src/commands/../../templates:'));
668
+ fs.readdirSync(dirTemplates).forEach(file => {
669
+ console.log(chalk.yellow(` - ${file}`));
670
+ });
671
+ shown = true;
672
+ }
673
+ if (!shown) {
674
+ console.log(chalk.red('No templates directory found. Please add your templates.'));
675
+ }
676
+ return;
677
+ }
376
678
 
377
- // Add file as a readable stream
378
- form.append("file", fs.createReadStream(zipFilePath));
679
+ spinner.text = `Extracting template to ${folder}...`;
379
680
 
380
- // Add all the required fields
381
- form.append("machineAgentId", agentId.toString());
382
- form.append("instanceId", instanceId.toString());
383
- form.append("entryFunctionName", entry);
384
- form.append("isSourceCodeUpdate", "true");
385
- form.append("projectId", projectData.id);
681
+ try {
682
+ // Extract template to target folder
683
+ extractZip(templatePath, folder);
684
+
685
+ // Create nestbox.config.json for TypeScript projects
686
+ createNestboxConfig(folder, selectedLang === 'ts');
687
+
688
+ // Update package.json with project name if it exists
689
+ const packageJsonPath = path.join(folder, 'package.json');
690
+ if (fs.existsSync(packageJsonPath)) {
691
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
692
+ packageJson.name = path.basename(folder);
693
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
694
+ }
386
695
 
387
- // Create a custom axios instance with form-data headers
388
- const axiosInstance = axios.create({
389
- baseURL: baseUrl,
390
- headers: {
391
- ...form.getHeaders(),
392
- Authorization: authToken?.token,
393
- },
394
- });
696
+ spinner.succeed(`Successfully generated ${mappedTemplateType} project in ${folder}`);
697
+
698
+ console.log(chalk.green("\nNext steps:"));
699
+ console.log(chalk.yellow(` cd ${folder}`));
700
+ console.log(chalk.yellow(" npm install"));
701
+ if (selectedLang === 'ts') {
702
+ console.log(chalk.yellow(" npm run build"));
703
+ }
704
+ console.log(chalk.yellow(" nestbox agent deploy --agent <agent-name> --instance <instance-name>"));
395
705
 
396
- // Construct the endpoint URL
397
- const endpoint = `/projects/${projectData.id}/agents/${agentId}`;
706
+ } catch (error) {
707
+ // Clean up on error
708
+ if (fs.existsSync(folder)) {
709
+ fs.rmSync(folder, { recursive: true, force: true });
710
+ }
711
+ throw error;
712
+ }
398
713
 
399
- // Make direct axios request
400
- await axiosInstance.patch(endpoint, form);
714
+ } catch (error: any) {
715
+ console.error(
716
+ chalk.red("Error:"),
717
+ error.message || "Failed to generate project"
718
+ );
719
+ }
720
+ });
401
721
 
402
- // Clean up temporary zip file if we created one
403
- if (zipFilePath !== sourcePath && fs.existsSync(zipFilePath)) {
404
- fs.unlinkSync(zipFilePath);
722
+ // Command for creating agents from YAML files
723
+ agentCommand
724
+ .command("create [firstArg] [secondArg]")
725
+ .description("Create multiple agents from a YAML configuration file")
726
+ .option("--project <projectId>", "Project ID (defaults to the current project)")
727
+ .action(async (firstArg: string, secondArg: any, options: any) => {
728
+ try {
729
+ let apis = createApis();
730
+
731
+ // Determine which argument is the YAML file path
732
+ let yamlFilePath: string;
733
+
734
+ if (firstArg === 'file' && secondArg) {
735
+ yamlFilePath = secondArg;
736
+ } else if (firstArg) {
737
+ yamlFilePath = firstArg;
738
+ if (typeof secondArg === 'object' && !options) {
739
+ options = secondArg;
405
740
  }
741
+ } else {
742
+ console.error(chalk.red("Missing YAML file path. Usage: nestbox agent create <yamlFile> OR nestbox agent create file <yamlFile>"));
743
+ return;
744
+ }
406
745
 
407
- spinner.succeed(
408
- `Successfully deployed agent ${agentId} to instance ${instanceId}`
409
- );
410
- console.log(chalk.green("Agent deployed successfully"));
411
- } catch (error: any) {
412
- spinner.fail("Failed to deploy agent");
746
+ // Check if file exists
747
+ if (!fs.existsSync(yamlFilePath)) {
748
+ console.error(chalk.red(`YAML file not found: ${yamlFilePath}`));
749
+ return;
750
+ }
413
751
 
414
- if (error.response) {
415
- console.error(
416
- chalk.red(
417
- `API Error (${error.response.status}): ${error.response.data?.message || "Unknown error"}`
418
- )
419
- );
752
+ // Read and parse the YAML file
753
+ const spinner = ora(`Reading agents configuration from ${yamlFilePath}...`).start();
754
+
755
+ try {
756
+ const fileContents = fs.readFileSync(yamlFilePath, 'utf8');
757
+ const config = yaml.load(fileContents) as AgentYamlConfig;
758
+
759
+ if (!config || !config.agents || !Array.isArray(config.agents)) {
760
+ spinner.fail("Invalid YAML configuration: Missing 'agents' array");
761
+ console.error(chalk.red("The YAML file should contain an 'agents' array with agent configurations"));
762
+ return;
763
+ }
764
+
765
+ spinner.succeed(`Found ${config.agents.length} agents in configuration file`);
766
+
767
+ // Process each agent with token refresh support
768
+ const results = {
769
+ success: 0,
770
+ failed: 0,
771
+ agents: [] as Array<{name: string; success: boolean; message: string}>
772
+ };
773
+
774
+ // Get user data once
775
+ const user = await userData();
776
+
777
+ for (const agent of config.agents) {
778
+ if (!agent.name) {
779
+ console.log(chalk.yellow("Skipping agent with no name defined"));
780
+ results.failed++;
781
+ results.agents.push({
782
+ name: "unnamed",
783
+ success: false,
784
+ message: "Name is required"
785
+ });
786
+ continue;
787
+ }
788
+
789
+ let agentType = agent.type || "CHAT";
790
+ const resourceType = agentType === "AGENT" ? "Agent" : "Chatbot";
791
+
792
+ const agentSpinner = ora(`Creating ${resourceType.toLowerCase()} '${agent.name}'...`).start();
793
+
794
+ try {
795
+ // Create agent with token refresh support
796
+ await withTokenRefresh(
797
+ async () => {
798
+ // Map YAML config to createAgent options
799
+ const createOptions = {
800
+ ...options,
801
+ goal: agent.goal || "No goal specified",
802
+ modelBaseId: agent.modelBaseId || "",
803
+ instanceIP: agent.instanceIP || "localhost",
804
+ machineInstanceId: agent.machineInstanceId || 1,
805
+ machineManifestId: agent.machineManifestId || "default",
806
+ machineName: agent.machineName || `agent-${agent.name.toLowerCase()}`,
807
+ type: agentType,
808
+ userId: user.id,
809
+ parameters: agent.parameters ? agent.parameters.map((p: any) => {
810
+ return {
811
+ name: p.name || "unnamed",
812
+ description: p.description || "",
813
+ default: p.default || "",
814
+ isUserParam: p.isUserParam !== undefined ? p.isUserParam : true
815
+ };
816
+ }) : []
817
+ };
818
+
819
+ await createAgent(agent.name, createOptions, apis.agentsApi, apis.projectsApi);
820
+ },
821
+ () => {
822
+ apis = createApis();
823
+ }
824
+ );
825
+
826
+ agentSpinner.stop();
827
+
828
+ results.success++;
829
+ results.agents.push({
830
+ name: agent.name,
831
+ success: true,
832
+ message: `Created successfully`
833
+ });
834
+ } catch (error: any) {
835
+ agentSpinner.fail(`Failed to create ${resourceType.toLowerCase()} '${agent.name}'`);
836
+ console.error(chalk.red(`Error: ${error.message}`));
837
+ results.failed++;
838
+ results.agents.push({
839
+ name: agent.name,
840
+ success: false,
841
+ message: error.message
842
+ });
843
+ }
844
+ }
845
+
846
+ // Final summary
847
+ console.log(chalk.blue("\nResource creation summary:"));
848
+ const table = new Table({
849
+ head: [
850
+ chalk.white.bold("Name"),
851
+ chalk.white.bold("Type"),
852
+ chalk.white.bold("Status"),
853
+ chalk.white.bold("Message"),
854
+ ],
855
+ style: {
856
+ head: [],
857
+ border: [],
858
+ },
859
+ });
860
+
861
+ results.agents.forEach((agent, index) => {
862
+ const agentConfig = config.agents.find(a => a.name === agent.name) || config.agents[index];
863
+ const agentType = agentConfig?.type || "CHAT";
864
+ const resourceType = agentType === "AGENT" ? "Agent" : "Chatbot";
865
+
866
+ table.push([
867
+ agent.name,
868
+ resourceType,
869
+ agent.success ? chalk.green("Success") : chalk.red("Failed"),
870
+ agent.message
871
+ ]);
872
+ });
873
+
874
+ console.log(table.toString());
875
+ console.log(`\nTotal: ${results.success + results.failed}, Successful: ${results.success}, Failed: ${results.failed}`);
876
+
877
+ } catch (error: any) {
878
+ spinner.fail("Failed to process YAML file");
879
+ if (error.code === 'ENOENT') {
880
+ console.error(chalk.red(`File not found: ${yamlFilePath}`));
881
+ } else if (error.name === 'YAMLException') {
882
+ console.error(chalk.red(`Invalid YAML format: ${error.message}`));
420
883
  } else {
421
884
  console.error(
422
885
  chalk.red("Error:"),
@@ -424,11 +887,15 @@ export function registerAgentCommands(program: Command): void {
424
887
  );
425
888
  }
426
889
  }
427
- } catch (error) {
428
- console.error(
429
- chalk.red("Error:"),
430
- error instanceof Error ? error.message : "Unknown error"
431
- );
890
+ } catch (error: any) {
891
+ if (error.message && error.message.includes('Authentication')) {
892
+ console.error(chalk.red(error.message));
893
+ } else {
894
+ console.error(
895
+ chalk.red("Error:"),
896
+ error instanceof Error ? error.message : "Unknown error"
897
+ );
898
+ }
432
899
  }
433
900
  });
434
- }
901
+ }