@nestbox-ai/cli 1.0.2 → 1.0.3

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,27 @@
1
+ import { UserCredentials } from '../types/auth';
2
+ /**
3
+ * Get authentication token for a specific domain
4
+ */
5
+ export declare function getAuthToken(domain?: string): {
6
+ token: string;
7
+ serverUrl: string;
8
+ accessToken?: string;
9
+ } | null;
10
+ /**
11
+ * Get user credentials for a specific domain and email
12
+ */
13
+ export declare function getUserCredentials(domain: string, email?: string): UserCredentials | null;
14
+ /**
15
+ * List all saved credentials
16
+ */
17
+ export declare function listCredentials(): Array<{
18
+ domain: string;
19
+ email: string;
20
+ name?: string;
21
+ authMethod?: string;
22
+ lastUsed?: number;
23
+ }>;
24
+ /**
25
+ * Remove credentials for a domain/email combination
26
+ */
27
+ export declare function removeCredentials(domain: string, email?: string): boolean;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAuthToken = getAuthToken;
7
+ exports.getUserCredentials = getUserCredentials;
8
+ exports.listCredentials = listCredentials;
9
+ exports.removeCredentials = removeCredentials;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ // Config path
14
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.config', '.nestbox');
15
+ // Ensure config directory exists
16
+ if (!fs_1.default.existsSync(CONFIG_DIR)) {
17
+ fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
18
+ }
19
+ /**
20
+ * Get authentication token for a specific domain
21
+ */
22
+ function getAuthToken(domain) {
23
+ try {
24
+ const files = fs_1.default.readdirSync(CONFIG_DIR);
25
+ if (!domain) {
26
+ // If no domain is provided, return the first token found
27
+ const tokenFiles = files.filter(file => file.endsWith('.json'));
28
+ if (tokenFiles.length === 0) {
29
+ return null;
30
+ }
31
+ const configData = JSON.parse(fs_1.default.readFileSync(path_1.default.join(CONFIG_DIR, tokenFiles[0])).toString());
32
+ return {
33
+ token: configData.token,
34
+ serverUrl: configData.apiServerUrl,
35
+ accessToken: configData.accessToken,
36
+ };
37
+ }
38
+ const domainFiles = files.filter(file => file.endsWith(`_${domain}.json`));
39
+ if (domainFiles.length === 0) {
40
+ return null;
41
+ }
42
+ // If multiple accounts, sort by last used and take the most recent
43
+ let configData;
44
+ if (domainFiles.length > 1) {
45
+ const allConfigs = domainFiles.map(file => {
46
+ const data = JSON.parse(fs_1.default.readFileSync(path_1.default.join(CONFIG_DIR, file)).toString());
47
+ return {
48
+ file,
49
+ data,
50
+ };
51
+ });
52
+ // Sort by last used, most recent first
53
+ configData = allConfigs[0].data;
54
+ }
55
+ else {
56
+ // Just one file
57
+ const configFile = domainFiles[0];
58
+ configData = JSON.parse(fs_1.default.readFileSync(path_1.default.join(CONFIG_DIR, configFile)).toString());
59
+ }
60
+ return {
61
+ token: configData.token,
62
+ serverUrl: configData.apiServerUrl,
63
+ };
64
+ }
65
+ catch (error) {
66
+ console.error('Error getting auth token:', error);
67
+ return null;
68
+ }
69
+ }
70
+ /**
71
+ * Get user credentials for a specific domain and email
72
+ */
73
+ function getUserCredentials(domain, email) {
74
+ try {
75
+ const files = fs_1.default.readdirSync(CONFIG_DIR);
76
+ let targetFiles;
77
+ if (email) {
78
+ // Get specific email for domain
79
+ targetFiles = files.filter(file => file === `${email}_${domain}.json`);
80
+ }
81
+ else {
82
+ // Get all for domain, sort by last used
83
+ targetFiles = files.filter(file => file.endsWith(`_${domain}.json`));
84
+ if (targetFiles.length > 1) {
85
+ const allConfigs = targetFiles.map(file => {
86
+ const data = JSON.parse(fs_1.default.readFileSync(path_1.default.join(CONFIG_DIR, file)).toString());
87
+ return {
88
+ file,
89
+ };
90
+ });
91
+ // Sort by last used, most recent first
92
+ targetFiles = [allConfigs[0].file];
93
+ }
94
+ }
95
+ if (targetFiles.length === 0) {
96
+ return null;
97
+ }
98
+ return JSON.parse(fs_1.default.readFileSync(path_1.default.join(CONFIG_DIR, targetFiles[0])).toString());
99
+ }
100
+ catch (error) {
101
+ console.error('Error getting user credentials:', error);
102
+ return null;
103
+ }
104
+ }
105
+ /**
106
+ * List all saved credentials
107
+ */
108
+ function listCredentials() {
109
+ try {
110
+ const files = fs_1.default.readdirSync(CONFIG_DIR);
111
+ return files.map(file => {
112
+ try {
113
+ const data = JSON.parse(fs_1.default.readFileSync(path_1.default.join(CONFIG_DIR, file)).toString());
114
+ return {
115
+ domain: data.domain,
116
+ email: data.email,
117
+ name: data.name,
118
+ };
119
+ }
120
+ catch (error) {
121
+ // Skip invalid files
122
+ return null;
123
+ }
124
+ }).filter(Boolean);
125
+ }
126
+ catch (error) {
127
+ console.error('Error listing credentials:', error);
128
+ return [];
129
+ }
130
+ }
131
+ /**
132
+ * Remove credentials for a domain/email combination
133
+ */
134
+ function removeCredentials(domain, email) {
135
+ try {
136
+ const files = fs_1.default.readdirSync(CONFIG_DIR);
137
+ let domainFiles;
138
+ if (email) {
139
+ // Remove specific email for domain
140
+ domainFiles = files.filter(file => file === `${email}_${domain}.json`);
141
+ }
142
+ else {
143
+ // Remove all for domain
144
+ domainFiles = files.filter(file => file.endsWith(`_${domain}.json`));
145
+ }
146
+ if (domainFiles.length === 0) {
147
+ return false;
148
+ }
149
+ domainFiles.forEach(file => {
150
+ fs_1.default.unlinkSync(path_1.default.join(CONFIG_DIR, file));
151
+ });
152
+ return true;
153
+ }
154
+ catch (error) {
155
+ console.error('Error removing credentials:', error);
156
+ return false;
157
+ }
158
+ }
159
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@nestbox-ai/cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "The cli tools that helps developers to build agents",
5
5
  "main": "dist/index.js",
6
+ "bin": {
7
+ "nestbox": "./dist/index.js"
8
+ },
6
9
  "scripts": {
7
- "dev": "tsx watch src/index.ts",
10
+ "dev": "tsx src/index.ts",
8
11
  "build": "tsc",
9
12
  "start": "node dist/index.js",
10
13
  "lint": "eslint src --ext .ts",
@@ -18,7 +21,11 @@
18
21
  "url": "git+https://github.com/NestboxAI/nestbox-ai-cli-tools.git"
19
22
  },
20
23
  "keywords": [
21
- "cli"
24
+ "cli",
25
+ "nestbox",
26
+ "ai",
27
+ "agent",
28
+ "deployment"
22
29
  ],
23
30
  "author": "Nestbox AI",
24
31
  "license": "ISC",
@@ -27,9 +34,27 @@
27
34
  },
28
35
  "homepage": "https://github.com/NestboxAI/nestbox-ai-cli-tools#readme",
29
36
  "devDependencies": {
30
- "typescript": "^5.8.3"
37
+ "@types/inquirer": "^9.0.7",
38
+ "@types/node": "^20.10.5",
39
+ "eslint": "^8.56.0",
40
+ "prettier": "^3.1.1",
41
+ "rimraf": "^5.0.5",
42
+ "tsx": "^4.7.0",
43
+ "typescript": "^5.8.3",
44
+ "vitest": "^1.1.0"
31
45
  },
32
46
  "dependencies": {
33
- "@nestbox-ai/admin": "^1.0.38"
47
+ "@nestbox-ai/admin": "^1.0.38",
48
+ "axios": "^1.8.4",
49
+ "chalk": "^5.3.0",
50
+ "cli-table3": "^0.6.5",
51
+ "commander": "^11.1.0",
52
+ "inquirer": "^9.2.12",
53
+ "open": "^10.0.0",
54
+ "ora": "^7.0.1"
55
+ },
56
+ "type": "module",
57
+ "engines": {
58
+ "node": ">=16.0.0"
34
59
  }
35
60
  }
@@ -0,0 +1,267 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import open from 'open';
5
+ import ora from 'ora';
6
+ import fs from 'fs';
7
+ import os from 'os';
8
+ import path from 'path';
9
+ import { getAuthToken, listCredentials, removeCredentials } from '../utils/auth';
10
+ import { AuthApi, Configuration, OAuthLoginRequestDTOTypeEnum, ProjectsApi } from '@nestbox-ai/admin';
11
+ import axios from 'axios';
12
+
13
+
14
+ export function registerAuthCommands(program: Command): void {
15
+ // Login command
16
+ program
17
+ .command('login <nestbox-domain>')
18
+ .description('Login using Google SSO')
19
+ .action(async (domain: string) => {
20
+ console.log('Login command triggered for domain:', domain);
21
+ const spinner = ora('Initiating Google login...').start();
22
+
23
+ try {
24
+ // Determine the protocol and construct the auth URL based on the provided domain
25
+ let authUrl;
26
+ if (domain.includes('localhost')) {
27
+ // Use HTTP for localhost and specific port
28
+ authUrl = `http://${domain}/cli/auth`;
29
+ } else {
30
+ // Use HTTPS for all other domains
31
+ authUrl = `https://${domain}/cli/auth`;
32
+ }
33
+
34
+ spinner.text = 'Opening browser for Google authentication...';
35
+
36
+ // Open the browser for authentication
37
+ await open(authUrl);
38
+ spinner.succeed('Browser opened for authentication');
39
+
40
+ // Prompt user to paste the combined token and API URL
41
+ const { combinedInput } = await inquirer.prompt<{ combinedInput: string }>([
42
+ {
43
+ type: 'input',
44
+ name: 'combinedInput',
45
+ message: 'After authenticating, please paste the data here:',
46
+ validate: (input) => input.trim().length > 0 || 'Input is required'
47
+ }
48
+ ]);
49
+
50
+ // Split the input by comma
51
+ const [accessToken, apiServerUrl] = combinedInput.split(',').map(item => item.trim());
52
+
53
+ if (!accessToken || !apiServerUrl) {
54
+ spinner.fail('Invalid input format. Expected: token,apiServerUrl');
55
+ return;
56
+ }
57
+
58
+ console.log(chalk.green('Credentials received. Extracting user information...'));
59
+
60
+ // Fetch user data from the token
61
+ let email = '';
62
+ let name = '';
63
+ let picture = '';
64
+ try {
65
+ // Try to decode JWT to get user data (email, name, picture, etc.)
66
+ const tokenParts = accessToken.split('.');
67
+ if (tokenParts.length === 3) {
68
+ // Base64 decode the payload part of JWT
69
+ const base64Payload = tokenParts[1].replace(/-/g, '+').replace(/_/g, '/');
70
+ const decodedPayload = Buffer.from(base64Payload, 'base64').toString('utf-8');
71
+ const tokenPayload = JSON.parse(decodedPayload);
72
+
73
+ // Extract user information
74
+ email = tokenPayload.email || '';
75
+ name = tokenPayload.name || '';
76
+ picture = tokenPayload.picture || '';
77
+ }
78
+ } catch (e) {
79
+ console.log(chalk.yellow('Could not decode token payload. Will prompt for email.'));
80
+ }
81
+
82
+ // If email couldn't be extracted from token, prompt user
83
+ if (!email) {
84
+ const response = await inquirer.prompt<{ email: string }>([
85
+ {
86
+ type: 'input',
87
+ name: 'email',
88
+ message: 'Enter your email address:',
89
+ validate: (input) => /\S+@\S+\.\S+/.test(input) || 'Please enter a valid email'
90
+ }
91
+ ]);
92
+ email = response.email;
93
+ }
94
+
95
+ spinner.start('Verifying access token...');
96
+
97
+ if (apiServerUrl && email && accessToken) {
98
+ // Verify the access token
99
+ const configuration = new Configuration({
100
+ basePath: apiServerUrl,
101
+ accessToken: accessToken,
102
+ });
103
+ const authApi = new AuthApi(configuration);
104
+ try {
105
+ const response = await authApi.authControllerOAuthLogin({
106
+ providerId: accessToken,
107
+ type: OAuthLoginRequestDTOTypeEnum.Google,
108
+ email,
109
+ profilePictureUrl: picture || '',
110
+ });
111
+ const authResponse = response.data;
112
+
113
+ // Save credentials to file
114
+ try {
115
+ // Create directory structure
116
+ const configDir = path.join(os.homedir(), '.config', '.nestbox');
117
+ if (!fs.existsSync(configDir)) {
118
+ fs.mkdirSync(configDir, { recursive: true });
119
+ }
120
+
121
+ // Create the file path
122
+ const fileName = `${email.replace('@', '_at_')}_${domain}.json`;
123
+ const filePath = path.join(configDir, fileName);
124
+
125
+ // Create credentials object
126
+ const credentials = {
127
+ domain,
128
+ email,
129
+ token: authResponse.token,
130
+ apiServerUrl,
131
+ name,
132
+ picture,
133
+ timestamp: new Date().toISOString()
134
+ };
135
+
136
+ // Write to file
137
+ fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2));
138
+
139
+ spinner.succeed('Authentication successful');
140
+ console.log(chalk.green(`Successfully logged in as ${email}`));
141
+ console.log(chalk.blue(`Credentials saved to: ${filePath}`));
142
+ } catch (fileError) {
143
+ spinner.warn('Authentication successful, but failed to save credentials file');
144
+ console.error(chalk.yellow('File error:'), fileError instanceof Error ? fileError.message : 'Unknown error');
145
+
146
+ }
147
+ } catch (authError) {
148
+ spinner.fail('Failed to verify access token');
149
+ if (axios.isAxiosError(authError) && authError.response) {
150
+ console.error(chalk.red('Authentication Error:'), authError.response.data?.message || 'Unknown authentication error');
151
+ } else {
152
+ console.error(chalk.red('Authentication Error:'), authError instanceof Error ? authError.message : 'Unknown error');
153
+ }
154
+ }
155
+ } else {
156
+ spinner.fail('Missing required information for authentication');
157
+ }
158
+ } catch (error) {
159
+ spinner.fail('Authentication failed');
160
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
161
+ }
162
+ });
163
+
164
+
165
+
166
+ // Logout command
167
+ program
168
+ .command('logout [nestbox-domain]')
169
+ .description('Logout from Nestbox platform')
170
+ .action(async (domain?: string) => {
171
+ try {
172
+ const authToken = getAuthToken(domain);
173
+ if (!authToken) {
174
+ console.log(chalk.yellow('No authentication token found. Please log in first.'));
175
+ return;
176
+ }
177
+
178
+ // Function to remove all credential files for a domain
179
+ const removeCredentialFiles = (domain: string) => {
180
+ try {
181
+ const configDir = path.join(os.homedir(), '.config', '.nestbox');
182
+ if (!fs.existsSync(configDir)) {
183
+ return false;
184
+ }
185
+
186
+ // Sanitize domain for file matching
187
+ // Replace characters that are problematic in filenames
188
+ const sanitizedDomain = domain.replace(/:/g, '_');
189
+
190
+ // Get all files in the directory
191
+ const files = fs.readdirSync(configDir);
192
+
193
+ // Find and remove all files that match the domain
194
+ let removedCount = 0;
195
+ for (const file of files) {
196
+ // Check if the file matches any of the possible domain formats
197
+ if (
198
+ file.endsWith(`_${domain}.json`) ||
199
+ file.endsWith(`_${sanitizedDomain}.json`)
200
+ ) {
201
+ fs.unlinkSync(path.join(configDir, file));
202
+ removedCount++;
203
+ }
204
+ }
205
+
206
+ return removedCount > 0;
207
+ } catch (error) {
208
+ console.warn(chalk.yellow(`Warning: Could not remove credential files. ${error instanceof Error ? error.message : ''}`));
209
+ return false;
210
+ }
211
+ };
212
+
213
+ if (domain) {
214
+ // Logout from specific domain
215
+ // Remove credentials using utility function
216
+ const removed = removeCredentials(domain);
217
+
218
+ // Also remove all credential files for this domain
219
+ const filesRemoved = removeCredentialFiles(domain);
220
+
221
+ if (removed || filesRemoved) {
222
+ console.log(chalk.green(`Successfully logged out from ${domain}`));
223
+ } else {
224
+ console.log(chalk.yellow(`No credentials found for ${domain}`));
225
+ }
226
+ } else {
227
+ // Ask which domain to logout from
228
+ const credentials = listCredentials();
229
+
230
+ if (credentials.length === 0) {
231
+ console.log(chalk.yellow('No credentials found'));
232
+ return;
233
+ }
234
+
235
+ // Group credentials by domain
236
+ const domains = Array.from(new Set(credentials.map(cred => cred.domain)));
237
+ const domainChoices = domains.map(domain => {
238
+ const accounts = credentials.filter(cred => cred.domain === domain);
239
+ return `${domain} (${accounts.length} account${accounts.length > 1 ? 's' : ''})`;
240
+ });
241
+
242
+ const { selected } = await inquirer.prompt<{ selected: string }>([
243
+ {
244
+ type: 'list',
245
+ name: 'selected',
246
+ message: 'Select domain to logout from:',
247
+ choices: domainChoices,
248
+ }
249
+ ]);
250
+
251
+ // Extract domain from the selected choice
252
+ const selectedDomain = selected.split(' ')[0];
253
+
254
+ // Remove credentials using utility function
255
+ removeCredentials(selectedDomain);
256
+
257
+ // Also remove all credential files for this domain
258
+ removeCredentialFiles(selectedDomain);
259
+
260
+ console.log(chalk.green(`Successfully logged out from ${selectedDomain}`));
261
+ }
262
+ } catch (error) {
263
+ const err = error as Error;
264
+ console.error(chalk.red('Error:'), err.message);
265
+ }
266
+ });
267
+ }
@@ -0,0 +1,147 @@
1
+ import { Command } from "commander";
2
+ import { getAuthToken } from "../utils/auth";
3
+ import { Configuration, ProjectsApi, MachineInstancesApi } from "@nestbox-ai/admin";
4
+ import { readNestboxConfig } from "./projects";
5
+ import chalk from "chalk";
6
+ import Table from "cli-table3";
7
+
8
+ export function registerComputeProgram(program: Command): void {
9
+
10
+ const authToken = getAuthToken();
11
+ const configuration = new Configuration({
12
+ basePath: authToken?.serverUrl,
13
+ baseOptions: {
14
+ headers: {
15
+ "Authorization": authToken?.token,
16
+ }
17
+ }
18
+ });
19
+
20
+ const machineInstanceApi = new MachineInstancesApi(configuration);
21
+ const projectsApi = new ProjectsApi(configuration);
22
+
23
+ // Create the main project command
24
+ const projectCommand = program
25
+ .command('compute')
26
+ .description('Manage Nestbox computes');
27
+
28
+ projectCommand
29
+ .command('list')
30
+ .description('list all instances')
31
+ .action(() => {
32
+ try {
33
+ if (!authToken) {
34
+ console.error(chalk.red('No authentication token found. Please login first.'));
35
+ return;
36
+ }
37
+ const config = readNestboxConfig();
38
+
39
+ // Set the default project
40
+ config.projects = config.projects || {};
41
+
42
+ // Get the default project name
43
+ const defaultProjectName = config.projects.default;
44
+
45
+ if (!defaultProjectName) {
46
+ console.log(chalk.yellow('No default project set. Use "nestbox project set-default <project-name>" to set a default project.'));
47
+ return;
48
+ }
49
+
50
+ console.log(chalk.blue(`Fetching compute instances for project: ${defaultProjectName}`));
51
+
52
+ // Call API to get all projects
53
+ const response = projectsApi.projectControllerGetAllProjects();
54
+
55
+ response.then((response) => {
56
+ // Check if the response contains projects
57
+ if (response.data && response.data.data && response.data.data.projects && response.data.data.projects.length > 0) {
58
+ const defaultProject = response.data.data.projects.find(
59
+ (project) => project.name === defaultProjectName
60
+ );
61
+
62
+ if (defaultProject) {
63
+ const machineInstancesResponse = machineInstanceApi.machineInstancesControllerGetMachineInstanceByUserId(
64
+ defaultProject.id,
65
+ 0,
66
+ 10
67
+ );
68
+
69
+ machineInstancesResponse.then((instancesResponse: any) => {
70
+ if (instancesResponse.data && instancesResponse.data.machineInstances) {
71
+ const instances = instancesResponse.data.machineInstances;
72
+
73
+ if (instances.length === 0) {
74
+ console.log(chalk.yellow('No compute instances found for this project.'));
75
+ return;
76
+ }
77
+
78
+ const table = new Table({
79
+ head: [
80
+ chalk.white.bold('ID'),
81
+ chalk.white.bold('Name'),
82
+ chalk.white.bold('Status'),
83
+ chalk.white.bold('API Key')
84
+ ],
85
+ style: {
86
+ head: [], // Disable the default styling
87
+ border: []
88
+ }
89
+ });
90
+
91
+ // Add rows to the table
92
+ instances.forEach((instance: any) => {
93
+ // Color the status based on its value
94
+ let statusColor;
95
+ const status = instance.runningStatus?.toLowerCase() || 'unknown';
96
+
97
+ switch(true) {
98
+ case status.includes('executed'):
99
+ statusColor = chalk.green(instance.runningStatus);
100
+ break;
101
+ case status.includes('failed') || status.includes('error'):
102
+ statusColor = chalk.red(instance.runningStatus);
103
+ break;
104
+ case status.includes('running') || status.includes('started'):
105
+ statusColor = chalk.green(instance.runningStatus);
106
+ break;
107
+ case status.includes('stopped'):
108
+ statusColor = chalk.red(instance.runningStatus);
109
+ break;
110
+ case status.includes('starting') || status.includes('pending'):
111
+ statusColor = chalk.yellow(instance.runningStatus);
112
+ break;
113
+ default:
114
+ statusColor = chalk.gray(instance.runningStatus || 'unknown');
115
+ }
116
+
117
+ table.push([
118
+ instance.id || 'N/A',
119
+ instance.instanceName || 'N/A',
120
+ statusColor,
121
+ instance.instanceApiKey || 'N/A'
122
+ ]);
123
+ });
124
+
125
+ // Display the table
126
+ console.log(table.toString());
127
+ } else {
128
+ console.log(chalk.yellow('No compute instance data returned from the API.'));
129
+ }
130
+ }).catch((error) => {
131
+ console.error(chalk.red('Error fetching compute instances:'), error instanceof Error ? error.message : 'Unknown error');
132
+ });
133
+ } else {
134
+ console.log(chalk.yellow(`Default project "${defaultProjectName}" not found among available projects.`));
135
+ }
136
+ } else {
137
+ console.log(chalk.yellow('No projects found.'));
138
+ }
139
+ }).catch((error) => {
140
+ console.error(chalk.red('Error fetching projects:'), error instanceof Error ? error.message : 'Unknown error');
141
+ });
142
+
143
+ } catch (error) {
144
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
145
+ }
146
+ });
147
+ }