@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,472 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { getAuthToken } from '../utils/auth';
5
+ import { Configuration, DocumentsApi, ProjectsApi } from '@nestbox-ai/admin';
6
+ import { readNestboxConfig } from './projects';
7
+ import { resolveProject } from '../utils/project';
8
+
9
+
10
+ /**
11
+ * Executes an async command with proper error handling and spinner feedback
12
+ */
13
+ async function executeCommand<T>(
14
+ description: string,
15
+ command: () => Promise<T>,
16
+ successMessage: string
17
+ ): Promise<T> {
18
+ const spinner = ora(description).start();
19
+
20
+ try {
21
+ const result = await command();
22
+ spinner.succeed(successMessage);
23
+ return result;
24
+ } catch (error: any) {
25
+ spinner.fail('Operation failed');
26
+
27
+ if (error.response?.data?.message) {
28
+ console.error(chalk.red('API Error:'), error.response.data.message);
29
+ } else {
30
+ console.error(chalk.red('Error:'), error.message || 'Unknown error');
31
+ }
32
+
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ export function registerDocumentCommands(program: Command): void {
38
+ const authToken = getAuthToken();
39
+
40
+ if (!authToken) {
41
+ console.error(chalk.red('No authentication token found. Please login first.'));
42
+ return;
43
+ }
44
+
45
+ const configuration = new Configuration({
46
+ basePath: authToken.serverUrl,
47
+ baseOptions: {
48
+ headers: {
49
+ Authorization: authToken.token,
50
+ },
51
+ },
52
+ });
53
+
54
+ const documentsApi = new DocumentsApi(configuration);
55
+ const projectsApi = new ProjectsApi(configuration);
56
+
57
+ const documentCommand = program.command('document').description('Manage Nestbox documents');
58
+ const docCommand = documentCommand.command('doc').description('Manage individual documents');
59
+ const collectionCommand = documentCommand.command('collection').description('Manage document collections');
60
+
61
+ // Add shared options to parent command that will be inherited by all subcommands
62
+ const addSharedOptions = (cmd: Command) =>
63
+ cmd
64
+ .requiredOption('--instance <instanceId>', 'Instance ID')
65
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
66
+
67
+ // LIST command
68
+ const listCmd = collectionCommand
69
+ .command('list')
70
+ .description('List document collections for a specific instance');
71
+
72
+ addSharedOptions(listCmd);
73
+
74
+ listCmd.action(async (options) => {
75
+ try {
76
+ const project = await resolveProject(projectsApi, options);
77
+
78
+ const collections = await executeCommand(
79
+ `Listing document collections for instance ${options.instance} in project ${project.name}...`,
80
+ async () => {
81
+ const response: any = await documentsApi.documentControllerGetAllCollections(project.id, options.instance);
82
+ return Array.isArray(response.data?.collections) ? response.data.collections : [];
83
+ },
84
+ 'Successfully retrieved document collections'
85
+ );
86
+
87
+ if (collections.length === 0) {
88
+ console.log(chalk.yellow(`No document collections found for instance ${options.instance} in project ${project.name}`));
89
+ return;
90
+ }
91
+
92
+ console.log(chalk.blue(`\nDocument collections for instance ${options.instance} in project ${project.name}:\n`));
93
+
94
+ collections.forEach((collection: any) => {
95
+ const name = typeof collection === 'string' ? collection : collection?.name || 'Unnamed Collection';
96
+ console.log(chalk.white.bold(name));
97
+ });
98
+ } catch (error) {
99
+ // Error already handled by executeCommand
100
+ }
101
+ });
102
+
103
+ // CREATE command
104
+ const createCmd = collectionCommand
105
+ .command('create')
106
+ .description('Create a new document collection for a specific instance')
107
+ .requiredOption('--name <name>', 'Name of the document collection')
108
+ .option('--metadata <json>', 'Metadata for the document collection in JSON format');
109
+
110
+ addSharedOptions(createCmd);
111
+
112
+ createCmd.action(async (options) => {
113
+ try {
114
+ const project = await resolveProject(projectsApi, options);
115
+
116
+ await executeCommand(
117
+ `Creating document collection "${options.name}" for instance ${options.instance} in project ${project.name}...`,
118
+ async () => {
119
+ const metadataObj = options.metadata ? JSON.parse(options.metadata) : {};
120
+ await documentsApi.documentControllerCreateCollection(project.id, options.instance, {
121
+ name: options.name,
122
+ metadata: metadataObj,
123
+ });
124
+ },
125
+ 'Successfully created document collection'
126
+ );
127
+
128
+ console.log(chalk.green(`Document collection "${options.name}" created successfully.`));
129
+ } catch (error) {
130
+ // Error already handled by executeCommand
131
+ }
132
+ });
133
+
134
+ // GET command
135
+ const getCmd = collectionCommand
136
+ .command('get')
137
+ .description('Get details of a specific document collection for a specific instance')
138
+ .requiredOption('--collection <collectionId>', 'ID of the document collection to get');
139
+
140
+ addSharedOptions(getCmd);
141
+
142
+ getCmd.action(async (options) => {
143
+ try {
144
+ const project = await resolveProject(projectsApi, options);
145
+
146
+ const collectionDetails = await executeCommand(
147
+ `Getting document collection "${options.collection}" for instance ${options.instance} in project ${project.name}...`,
148
+ async () => {
149
+ const response = await documentsApi.documentControllerGetCollectionInfo(
150
+ project.id,
151
+ options.instance,
152
+ options.collection
153
+ );
154
+ return response.data;
155
+ },
156
+ 'Successfully retrieved document collection'
157
+ );
158
+
159
+ console.log(chalk.blue(`\nDocument collection details for instance ${options.instance} in project ${project.name}:\n`));
160
+ console.log(JSON.stringify(collectionDetails, null, 2));
161
+ } catch (error) {
162
+ // Error already handled by executeCommand
163
+ }
164
+ });
165
+
166
+ // DELETE command
167
+ const deleteCmd = collectionCommand
168
+ .command('delete')
169
+ .description('Delete a document collection for a specific instance')
170
+ .requiredOption('--collection <collectionId>', 'ID of the document collection to delete');
171
+
172
+ addSharedOptions(deleteCmd);
173
+
174
+ deleteCmd.action(async (options) => {
175
+ try {
176
+ const project = await resolveProject(projectsApi, options);
177
+
178
+ await executeCommand(
179
+ `Deleting document collection "${options.collection}" for instance ${options.instance} in project ${project.name}...`,
180
+ async () => {
181
+ await documentsApi.documentControllerDeleteCollection(
182
+ project.id,
183
+ options.instance,
184
+ options.collection
185
+ );
186
+ },
187
+ 'Successfully deleted document collection'
188
+ );
189
+
190
+ console.log(chalk.green(`Document collection "${options.collection}" deleted successfully.`));
191
+ } catch (error) {
192
+ // Error already handled by executeCommand
193
+ }
194
+ });
195
+
196
+ // UPDATE command
197
+ const updateCmd = collectionCommand
198
+ .command('update')
199
+ .description('Update a document collection for a specific instance')
200
+ .requiredOption('--collection <collectionId>', 'ID of the document collection to update')
201
+ .option('--name <name>', 'New name of the document collection')
202
+ .option('--metadata <json>', 'New metadata for the document collection in JSON format');
203
+
204
+ addSharedOptions(updateCmd);
205
+
206
+ updateCmd.action(async (options) => {
207
+ try {
208
+ const project = await resolveProject(projectsApi, options);
209
+
210
+ await executeCommand(
211
+ `Updating document collection "${options.collection}" for instance ${options.instance} in project ${project.name}...`,
212
+ async () => {
213
+ const metadataObj = options.metadata ? JSON.parse(options.metadata) : {};
214
+
215
+ await documentsApi.documentControllerUpdateCollection(
216
+ project.id,
217
+ options.instance,
218
+ options.collection,
219
+ {
220
+ name: options.name,
221
+ metadata: metadataObj,
222
+ }
223
+ );
224
+ },
225
+ 'Successfully updated document collection'
226
+ );
227
+
228
+ console.log(chalk.green(`Document collection "${options.collection}" updated successfully.`));
229
+ } catch (error) {
230
+ // Error already handled by executeCommand
231
+ }
232
+ });
233
+
234
+
235
+ const addDocCmd = docCommand
236
+ .command('add')
237
+ .description('Add a new document to a collection')
238
+ .requiredOption('--instance <instanceId>', 'Instance ID')
239
+ .requiredOption('--collection <collectionId>', 'Collection ID')
240
+ .requiredOption('--id <id>', 'Document ID')
241
+ .requiredOption('--document <json>', 'Document content in JSON format')
242
+ .option('--metadata <json>', 'Document metadata in JSON format (optional)')
243
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
244
+
245
+ addDocCmd.action(async (options) => {
246
+ try {
247
+ const project = await resolveProject(projectsApi, options);
248
+
249
+ await executeCommand(
250
+ `Adding document to collection "${options.collection}" in instance ${options.instance}...`,
251
+ async () => {
252
+ const documentContent = JSON.parse(options.document);
253
+ const metadata = options.metadata ? JSON.parse(options.metadata) : {};
254
+
255
+ await documentsApi.documentControllerAddDocToCollection(
256
+ project.id,
257
+ options.instance,
258
+ options.collection,
259
+ {
260
+ id: options.id,
261
+ document: JSON.stringify(documentContent),
262
+ metadata: metadata
263
+ }
264
+ );
265
+ },
266
+ 'Successfully added document to collection'
267
+ );
268
+
269
+ console.log(chalk.green(`Document with ID "${options.id}" added successfully to collection "${options.collection}".`));
270
+ } catch (error) {
271
+ // Error already handled by executeCommand
272
+ }
273
+ });
274
+
275
+ const getDocCmd = docCommand
276
+ .command('get')
277
+ .description('Get a document from a collection')
278
+ .requiredOption('--instance <instanceId>', 'Instance ID')
279
+ .requiredOption('--collection <collectionId>', 'Collection ID')
280
+ .requiredOption('--doc <docId>', 'Document ID')
281
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
282
+ getDocCmd.action(async (options) => {
283
+ try {
284
+ const project = await resolveProject(projectsApi, options);
285
+
286
+ const document = await executeCommand(
287
+ `Getting document "${options.doc}" from collection "${options.collection}" in instance ${options.instance}...`,
288
+ async () => {
289
+ const response = await documentsApi.documentControllerGetDocById(
290
+ project.id,
291
+ options.instance,
292
+ options.collection,
293
+ options.doc
294
+ );
295
+ return response.data;
296
+ },
297
+ 'Successfully retrieved document'
298
+ );
299
+
300
+ console.log(chalk.blue(`\nDocument details for ID "${options.doc}" in collection "${options.collection}":\n`));
301
+ console.log(JSON.stringify(document, null, 2));
302
+ } catch (error) {
303
+ // Error already handled by executeCommand
304
+ }
305
+ });
306
+
307
+ const deleteDocCmd = docCommand
308
+ .command('delete')
309
+ .description('Delete a document from a collection')
310
+ .requiredOption('--instance <instanceId>', 'Instance ID')
311
+ .requiredOption('--collection <collectionId>', 'Collection ID')
312
+ .requiredOption('--doc <docId>', 'Document ID')
313
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
314
+ deleteDocCmd.action(async (options) => {
315
+ try {
316
+ const project = await resolveProject(projectsApi, options);
317
+
318
+ await executeCommand(
319
+ `Deleting document "${options.doc}" from collection "${options.collection}" in instance ${options.instance}...`,
320
+ async () => {
321
+ await documentsApi.documentControllerDeleteDocById(
322
+ project.id,
323
+ options.instance,
324
+ options.collection,
325
+ options.doc
326
+ );
327
+ },
328
+ 'Successfully deleted document'
329
+ );
330
+
331
+ console.log(chalk.green(`Document with ID "${options.doc}" deleted successfully from collection "${options.collection}".`));
332
+ } catch (error) {
333
+ // Error already handled by executeCommand
334
+ }
335
+ });
336
+
337
+ const updateDocCmd = docCommand
338
+ .command('update')
339
+ .description('Update a document in a collection')
340
+ .requiredOption('--instance <instanceId>', 'Instance ID')
341
+ .requiredOption('--collection <collectionId>', 'Collection ID')
342
+ .requiredOption('--doc <docId>', 'Document ID')
343
+ .requiredOption('--document <string>', 'Updated document content as a string')
344
+ .option('--metadata <json>', 'Updated document metadata in JSON format (optional)')
345
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
346
+
347
+ updateDocCmd.action(async (options) => {
348
+ try {
349
+ const project = await resolveProject(projectsApi, options);
350
+
351
+ await executeCommand(
352
+ `Updating document "${options.doc}" in collection "${options.collection}" in instance ${options.instance}...`,
353
+ async () => {
354
+ const documentContent = options.document;
355
+ const metadata = options.metadata ? JSON.parse(options.metadata) : {};
356
+
357
+ await documentsApi.documentControllerUpdateDoc(
358
+ project.id,
359
+ options.instance,
360
+ options.collection,
361
+ options.doc,
362
+ {
363
+ document: documentContent,
364
+ metadata: metadata
365
+ }
366
+ );
367
+ },
368
+ 'Successfully updated document'
369
+ );
370
+
371
+ console.log(chalk.green(`Document with ID "${options.doc}" updated successfully in collection "${options.collection}".`));
372
+ } catch (error) {
373
+ // Error already handled by executeCommand
374
+ }
375
+ });
376
+
377
+ const uploadFileCmd = docCommand
378
+ .command('upload-file')
379
+ .description('Add documents by file chunking')
380
+ .requiredOption('--instance <instanceId>', 'Instance ID')
381
+ .requiredOption('--collection <collectionId>', 'Collection ID')
382
+ .requiredOption('--file <path>', 'Path to the file to upload')
383
+ .option('--type <fileType>', 'Type of the file (e.g., pdf, txt, doc)')
384
+ .option('--options <json>', 'Additional options for file processing in JSON format')
385
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
386
+
387
+ uploadFileCmd.action(async (options) => {
388
+ try {
389
+ const project = await resolveProject(projectsApi, options);
390
+
391
+ await executeCommand(
392
+ `Processing file "${options.file}" for collection "${options.collection}" in instance ${options.instance}...`,
393
+ async () => {
394
+
395
+ const requestData = {
396
+ type: options.type,
397
+ url: options.file,
398
+ options: options.options ? JSON.parse(options.options) : {},
399
+ };
400
+
401
+ await documentsApi.documentControllerAddDocToCollectionFromFile(
402
+ project.id,
403
+ options.instance,
404
+ options.collection,
405
+ requestData
406
+ );
407
+ },
408
+ 'Successfully processed file for document chunking'
409
+ );
410
+
411
+ console.log(chalk.green(`File "${options.file}" processed successfully for collection "${options.collection}".`));
412
+ } catch (error) {
413
+ // Error already handled by executeCommand
414
+ }
415
+ });
416
+
417
+ const searchCmd = docCommand
418
+ .command('search')
419
+ .description('Search for documents in a collection')
420
+ .requiredOption('--instance <instanceId>', 'Instance ID')
421
+ .requiredOption('--collection <collectionId>', 'Collection ID')
422
+ .requiredOption('--query <query>', 'Search query')
423
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)')
424
+ .option('--filter <json>', 'Filter criteria as JSON string');
425
+
426
+ searchCmd.action(async (options) => {
427
+ try {
428
+ const project = await resolveProject(projectsApi, options);
429
+
430
+ // Build the request body
431
+ const requestBody = {
432
+ query: options.query,
433
+ params: {},
434
+ filter: {},
435
+ include: ["embedding"]
436
+ };
437
+
438
+
439
+ // Parse filter JSON if provided
440
+ if (options.filter) {
441
+ try {
442
+ requestBody.filter = JSON.parse(options.filter);
443
+ } catch (e: any) {
444
+ console.error(chalk.red('Error parsing filter JSON:'), e.message);
445
+ return;
446
+ }
447
+ }
448
+
449
+ console.log("REQUEST BODY", requestBody);
450
+
451
+ const results = await executeCommand(
452
+ `Searching for documents in collection "${options.collection}" in instance ${options.instance}...`,
453
+ async () => {
454
+ const response = await documentsApi.documentControllerSimilaritySearch(
455
+ project.id,
456
+ options.instance,
457
+ options.collection,
458
+ requestBody
459
+ );
460
+ return response.data;
461
+ },
462
+ 'Successfully retrieved search results'
463
+ );
464
+
465
+ console.log(chalk.blue(`\nSearch results for query "${options.query}" in collection "${options.collection}":\n`));
466
+ console.log(JSON.stringify(results, null, 2));
467
+ } catch (error) {
468
+ // Error already handled by executeCommand
469
+ }
470
+ });
471
+
472
+ }
@@ -0,0 +1,155 @@
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, MiscellaneousApi, ProjectsApi } from '@nestbox-ai/admin';
7
+ import { resolveProject } from '../utils/project';
8
+
9
+ /**
10
+ * Executes an async command with proper error handling and spinner feedback
11
+ */
12
+ async function executeCommand<T>(
13
+ description: string,
14
+ command: () => Promise<T>,
15
+ successMessage: string
16
+ ): Promise<T> {
17
+ const spinner = ora(description).start();
18
+
19
+ try {
20
+ const result = await command();
21
+ spinner.succeed(successMessage);
22
+ return result;
23
+ } catch (error: any) {
24
+ spinner.fail('Operation failed');
25
+
26
+ if (error.response?.data?.message) {
27
+ console.error(chalk.red('API Error:'), error.response.data.message);
28
+ } else {
29
+ console.error(chalk.red('Error:'), error.message || 'Unknown error');
30
+ }
31
+
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ export function registerImageCommands(program: Command): void {
37
+ const authToken = getAuthToken();
38
+
39
+ if (!authToken) {
40
+ console.error(chalk.red('No authentication token found. Please login first.'));
41
+ return;
42
+ }
43
+
44
+ const configuration = new Configuration({
45
+ basePath: authToken.serverUrl,
46
+ baseOptions: {
47
+ headers: {
48
+ Authorization: authToken.token,
49
+ },
50
+ },
51
+ });
52
+
53
+ const miscellaneousApi = new MiscellaneousApi(configuration);
54
+ const projectsApi = new ProjectsApi(configuration);
55
+
56
+ const imageCommand = program.command('image').description('Manage Nestbox images');
57
+
58
+ // LIST command
59
+ const listCmd = imageCommand
60
+ .command('list')
61
+ .description('List images for a project')
62
+ .option('--project <projectId>', 'Project ID or name (defaults to the current project)');
63
+
64
+ listCmd.action(async (options) => {
65
+ try {
66
+ const project = await resolveProject(projectsApi, options);
67
+
68
+ const images: any = await executeCommand(
69
+ `Listing images for project ${project.name}...`,
70
+ async () => {
71
+ const response = await miscellaneousApi.miscellaneousControllerGetMachineInstanceByImageId(project.id);
72
+ return response.data;
73
+ },
74
+ 'Successfully retrieved images'
75
+ );
76
+
77
+ if (!images || images.length === 0) {
78
+ console.log(chalk.yellow('No images found for this project.'));
79
+ return;
80
+ }
81
+
82
+ // Create a table for displaying the image data
83
+ const table = new Table({
84
+ head: [
85
+ chalk.white.bold('ID'),
86
+ chalk.white.bold('Name'),
87
+ chalk.white.bold('Machine Type'),
88
+ chalk.white.bold('Status'),
89
+ chalk.white.bold('Region'),
90
+ chalk.white.bold('API Key'),
91
+ chalk.white.bold('Internal IP')
92
+ ],
93
+ style: {
94
+ head: [], // Disable the default styling
95
+ border: []
96
+ }
97
+ });
98
+
99
+ // Status mappings
100
+ const statusMappings: Record<string, string> = {
101
+ 'Job Scheduled': 'Scheduled',
102
+ 'Job Executed': 'Ready',
103
+ 'Job in Progress': 'Initializing',
104
+ 'Job Failed': 'Failed',
105
+ 'Deleting': 'Deleting',
106
+ };
107
+
108
+ // Add rows to the table
109
+ images.forEach((image: any) => {
110
+ // Map the status if a mapping exists
111
+ const originalStatus = image.runningStatus || 'unknown';
112
+ const displayStatus = statusMappings[originalStatus] || originalStatus;
113
+
114
+ // Color the status based on its mapped value
115
+ let statusColor;
116
+
117
+ switch(displayStatus.toLowerCase()) {
118
+ case 'ready':
119
+ statusColor = chalk.green(displayStatus);
120
+ break;
121
+ case 'failed':
122
+ statusColor = chalk.red(displayStatus);
123
+ break;
124
+ case 'initializing':
125
+ statusColor = chalk.yellow(displayStatus);
126
+ break;
127
+ case 'scheduled':
128
+ statusColor = chalk.blue(displayStatus);
129
+ break;
130
+ case 'deleting':
131
+ statusColor = chalk.red(displayStatus);
132
+ break;
133
+ default:
134
+ statusColor = chalk.gray(displayStatus);
135
+ }
136
+
137
+ table.push([
138
+ image.id || 'N/A',
139
+ image.instanceName || 'N/A',
140
+ image.machineTitle || 'N/A',
141
+ statusColor,
142
+ image.region || 'N/A',
143
+ image.instanceApiKey || 'N/A',
144
+ image.internalIP || 'N/A'
145
+ ]);
146
+ });
147
+
148
+ // Display the table
149
+ console.log(table.toString());
150
+
151
+ } catch (error) {
152
+ // Error already handled by executeCommand
153
+ }
154
+ });
155
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@ import { Command } from 'commander';
3
3
  import { registerAuthCommands } from './commands/auth';
4
4
  import { registerProjectCommands } from './commands/projects';
5
5
  import { registerComputeProgram } from './commands/compute';
6
+ import { registerAgentCommands } from './commands/agent';
7
+ import { registerDocumentCommands } from './commands/document';
8
+ import { registerImageCommands } from './commands/image';
6
9
 
7
10
  // Setup the CLI program
8
11
  const program = new Command();
@@ -15,6 +18,9 @@ program
15
18
  registerAuthCommands(program);
16
19
  registerProjectCommands(program);
17
20
  registerComputeProgram(program);
21
+ registerAgentCommands(program);
22
+ registerDocumentCommands(program);
23
+ registerImageCommands(program);
18
24
 
19
25
  // Parse command line arguments
20
26
  program.parse(process.argv);