claude-comms 1.0.0 → 1.0.2

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,361 @@
1
+ /**
2
+ * Custom error classes for standardized error handling
3
+ * Provides consistent error codes and messages across the NPX installer
4
+ */
5
+
6
+ /**
7
+ * Base installer error with error codes and details
8
+ */
9
+ class InstallerError extends Error {
10
+ constructor(message, code, details = null, cause = null) {
11
+ super(message);
12
+ this.name = this.constructor.name;
13
+ this.code = code;
14
+ this.details = details;
15
+ this.cause = cause;
16
+ this.timestamp = new Date().toISOString();
17
+
18
+ // Maintain stack trace for V8
19
+ if (Error.captureStackTrace) {
20
+ Error.captureStackTrace(this, this.constructor);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Convert error to JSON for logging/debugging
26
+ */
27
+ toJSON() {
28
+ return {
29
+ name: this.name,
30
+ message: this.message,
31
+ code: this.code,
32
+ details: this.details,
33
+ timestamp: this.timestamp,
34
+ stack: this.stack
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Get user-friendly error message
40
+ */
41
+ getUserMessage() {
42
+ return this.message;
43
+ }
44
+
45
+ /**
46
+ * Check if error is recoverable
47
+ */
48
+ isRecoverable() {
49
+ return false; // Override in subclasses
50
+ }
51
+ }
52
+
53
+ /**
54
+ * GitHub API related errors
55
+ */
56
+ class GitHubAPIError extends InstallerError {
57
+ constructor(message, details = null, cause = null) {
58
+ const errorCode = GitHubAPIError.getErrorCode(details);
59
+ super(message, errorCode, details, cause);
60
+ }
61
+
62
+ static getErrorCode(details) {
63
+ if (details?.status === 403 && details?.headers?.['x-ratelimit-remaining'] === '0') {
64
+ return 'GITHUB_RATE_LIMIT';
65
+ }
66
+ if (details?.status === 404) {
67
+ return 'GITHUB_NOT_FOUND';
68
+ }
69
+ if (details?.status >= 500) {
70
+ return 'GITHUB_SERVER_ERROR';
71
+ }
72
+ if (details?.code === 'ENOTFOUND' || details?.code === 'ECONNREFUSED') {
73
+ return 'GITHUB_CONNECTION_ERROR';
74
+ }
75
+ return 'GITHUB_API_ERROR';
76
+ }
77
+
78
+ getUserMessage() {
79
+ switch (this.code) {
80
+ case 'GITHUB_RATE_LIMIT':
81
+ return 'GitHub API rate limit exceeded. Please wait a few minutes and try again, or use a GitHub token for higher limits.';
82
+ case 'GITHUB_NOT_FOUND':
83
+ return 'Repository or file not found on GitHub. Please check the repository URL and try again.';
84
+ case 'GITHUB_SERVER_ERROR':
85
+ return 'GitHub is experiencing server issues. Please try again in a few minutes.';
86
+ case 'GITHUB_CONNECTION_ERROR':
87
+ return 'Unable to connect to GitHub. Please check your internet connection and try again.';
88
+ default:
89
+ return 'Failed to fetch files from GitHub. Please try again or check your internet connection.';
90
+ }
91
+ }
92
+
93
+ isRecoverable() {
94
+ return ['GITHUB_RATE_LIMIT', 'GITHUB_SERVER_ERROR', 'GITHUB_CONNECTION_ERROR'].includes(this.code);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * File system operation errors
100
+ */
101
+ class FileSystemError extends InstallerError {
102
+ constructor(message, details = null, cause = null) {
103
+ const errorCode = FileSystemError.getErrorCode(details);
104
+ super(message, errorCode, details, cause);
105
+ }
106
+
107
+ static getErrorCode(details) {
108
+ if (details?.code === 'EACCES' || details?.code === 'EPERM') {
109
+ return 'FILESYSTEM_PERMISSION_DENIED';
110
+ }
111
+ if (details?.code === 'ENOENT') {
112
+ return 'FILESYSTEM_NOT_FOUND';
113
+ }
114
+ if (details?.code === 'EEXIST') {
115
+ return 'FILESYSTEM_ALREADY_EXISTS';
116
+ }
117
+ if (details?.code === 'ENOSPC') {
118
+ return 'FILESYSTEM_NO_SPACE';
119
+ }
120
+ if (details?.code === 'EMFILE' || details?.code === 'ENFILE') {
121
+ return 'FILESYSTEM_TOO_MANY_FILES';
122
+ }
123
+ return 'FILESYSTEM_ERROR';
124
+ }
125
+
126
+ getUserMessage() {
127
+ switch (this.code) {
128
+ case 'FILESYSTEM_PERMISSION_DENIED':
129
+ return 'Permission denied. Please run with appropriate permissions or choose a different directory.';
130
+ case 'FILESYSTEM_NOT_FOUND':
131
+ return 'Directory or file not found. Please check the path and try again.';
132
+ case 'FILESYSTEM_ALREADY_EXISTS':
133
+ return 'File or directory already exists. Use --force to overwrite or choose a different location.';
134
+ case 'FILESYSTEM_NO_SPACE':
135
+ return 'Not enough disk space to complete the installation.';
136
+ case 'FILESYSTEM_TOO_MANY_FILES':
137
+ return 'Too many open files. Please close some applications and try again.';
138
+ default:
139
+ return 'File system operation failed. Please check permissions and available space.';
140
+ }
141
+ }
142
+
143
+ isRecoverable() {
144
+ return ['FILESYSTEM_TOO_MANY_FILES'].includes(this.code);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Network related errors
150
+ */
151
+ class NetworkError extends InstallerError {
152
+ constructor(message, details = null, cause = null) {
153
+ const errorCode = NetworkError.getErrorCode(details);
154
+ super(message, errorCode, details, cause);
155
+ }
156
+
157
+ static getErrorCode(details) {
158
+ if (details?.code === 'ENOTFOUND') {
159
+ return 'NETWORK_DNS_ERROR';
160
+ }
161
+ if (details?.code === 'ECONNREFUSED') {
162
+ return 'NETWORK_CONNECTION_REFUSED';
163
+ }
164
+ if (details?.code === 'ETIMEDOUT') {
165
+ return 'NETWORK_TIMEOUT';
166
+ }
167
+ if (details?.code === 'CERT_HAS_EXPIRED' || details?.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
168
+ return 'NETWORK_SSL_ERROR';
169
+ }
170
+ return 'NETWORK_ERROR';
171
+ }
172
+
173
+ getUserMessage() {
174
+ switch (this.code) {
175
+ case 'NETWORK_DNS_ERROR':
176
+ return 'DNS resolution failed. Please check your internet connection and DNS settings.';
177
+ case 'NETWORK_CONNECTION_REFUSED':
178
+ return 'Connection refused. The server may be down or unreachable.';
179
+ case 'NETWORK_TIMEOUT':
180
+ return 'Network request timed out. Please check your internet connection and try again.';
181
+ case 'NETWORK_SSL_ERROR':
182
+ return 'SSL certificate error. Please check your system time and certificate store.';
183
+ default:
184
+ return 'Network error occurred. Please check your internet connection and try again.';
185
+ }
186
+ }
187
+
188
+ isRecoverable() {
189
+ return ['NETWORK_TIMEOUT', 'NETWORK_CONNECTION_REFUSED'].includes(this.code);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Input validation errors
195
+ */
196
+ class ValidationError extends InstallerError {
197
+ constructor(message, field = null, details = null) {
198
+ super(message, 'VALIDATION_ERROR', { field, ...details });
199
+ this.field = field;
200
+ }
201
+
202
+ getUserMessage() {
203
+ if (this.field) {
204
+ return `Invalid ${this.field}: ${this.message}`;
205
+ }
206
+ return `Validation error: ${this.message}`;
207
+ }
208
+
209
+ isRecoverable() {
210
+ return true; // User can correct input
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Dependency check errors
216
+ */
217
+ class DependencyError extends InstallerError {
218
+ constructor(message, dependency, details = null) {
219
+ super(message, 'DEPENDENCY_ERROR', { dependency, ...details });
220
+ this.dependency = dependency;
221
+ }
222
+
223
+ getUserMessage() {
224
+ return `Missing dependency: ${this.dependency}. ${this.message}`;
225
+ }
226
+
227
+ isRecoverable() {
228
+ return true; // User can install dependencies
229
+ }
230
+ }
231
+
232
+ /**
233
+ * User cancellation error
234
+ */
235
+ class UserCancelledError extends InstallerError {
236
+ constructor(message = 'Installation cancelled by user') {
237
+ super(message, 'USER_CANCELLED');
238
+ }
239
+
240
+ getUserMessage() {
241
+ return this.message;
242
+ }
243
+
244
+ isRecoverable() {
245
+ return false;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Configuration errors
251
+ */
252
+ class ConfigurationError extends InstallerError {
253
+ constructor(message, configKey = null, details = null) {
254
+ super(message, 'CONFIGURATION_ERROR', { configKey, ...details });
255
+ this.configKey = configKey;
256
+ }
257
+
258
+ getUserMessage() {
259
+ if (this.configKey) {
260
+ return `Configuration error in '${this.configKey}': ${this.message}`;
261
+ }
262
+ return `Configuration error: ${this.message}`;
263
+ }
264
+
265
+ isRecoverable() {
266
+ return true; // User can fix configuration
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Error factory for creating appropriate error types
272
+ */
273
+ class ErrorFactory {
274
+ /**
275
+ * Create an error from a caught exception
276
+ */
277
+ static fromCaught(error, context = null) {
278
+ if (error instanceof InstallerError) {
279
+ return error;
280
+ }
281
+
282
+ // Network errors
283
+ if (error.code && ['ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT'].includes(error.code)) {
284
+ return new NetworkError(error.message, { code: error.code, context }, error);
285
+ }
286
+
287
+ // File system errors
288
+ if (error.code && ['EACCES', 'EPERM', 'ENOENT', 'EEXIST', 'ENOSPC'].includes(error.code)) {
289
+ return new FileSystemError(error.message, { code: error.code, context }, error);
290
+ }
291
+
292
+ // GitHub API errors (based on fetch response)
293
+ if (error.status || (context && context.includes('github'))) {
294
+ return new GitHubAPIError(error.message, { status: error.status, context }, error);
295
+ }
296
+
297
+ // Generic installer error
298
+ return new InstallerError(error.message, 'UNKNOWN_ERROR', { context }, error);
299
+ }
300
+
301
+ /**
302
+ * Create a GitHub API error
303
+ */
304
+ static github(message, details) {
305
+ return new GitHubAPIError(message, details);
306
+ }
307
+
308
+ /**
309
+ * Create a file system error
310
+ */
311
+ static filesystem(message, details) {
312
+ return new FileSystemError(message, details);
313
+ }
314
+
315
+ /**
316
+ * Create a network error
317
+ */
318
+ static network(message, details) {
319
+ return new NetworkError(message, details);
320
+ }
321
+
322
+ /**
323
+ * Create a validation error
324
+ */
325
+ static validation(message, field, details) {
326
+ return new ValidationError(message, field, details);
327
+ }
328
+
329
+ /**
330
+ * Create a dependency error
331
+ */
332
+ static dependency(message, dependency, details) {
333
+ return new DependencyError(message, dependency, details);
334
+ }
335
+
336
+ /**
337
+ * Create a user cancelled error
338
+ */
339
+ static userCancelled(message) {
340
+ return new UserCancelledError(message);
341
+ }
342
+
343
+ /**
344
+ * Create a configuration error
345
+ */
346
+ static configuration(message, configKey, details) {
347
+ return new ConfigurationError(message, configKey, details);
348
+ }
349
+ }
350
+
351
+ module.exports = {
352
+ InstallerError,
353
+ GitHubAPIError,
354
+ FileSystemError,
355
+ NetworkError,
356
+ ValidationError,
357
+ DependencyError,
358
+ UserCancelledError,
359
+ ConfigurationError,
360
+ ErrorFactory
361
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * NPX Installer Utilities Module
3
+ * Centralized export for all utility functions and classes
4
+ */
5
+
6
+ // Core utilities
7
+ const logger = require('./logger');
8
+ const errors = require('./errors');
9
+ const platform = require('./platform');
10
+ const config = require('./config');
11
+ const retry = require('./retry');
12
+ const constants = require('./constants');
13
+
14
+ // Re-export all utilities for team integration
15
+ module.exports = {
16
+ // Logger utilities
17
+ logger,
18
+ Logger: logger.Logger,
19
+
20
+ // Error handling
21
+ ...errors,
22
+ ErrorFactory: errors.ErrorFactory,
23
+
24
+ // Platform utilities
25
+ platform,
26
+ Platform: platform.Platform,
27
+
28
+ // Configuration management
29
+ config,
30
+ Config: config.Config,
31
+
32
+ // Retry mechanisms
33
+ retry: retry.retry,
34
+ RetryWrapper: retry.RetryWrapper,
35
+ CircuitBreaker: retry.CircuitBreaker,
36
+ retryPresets: retry.retryPresets,
37
+
38
+ // Constants and configuration
39
+ constants,
40
+
41
+ // Convenience exports for common patterns
42
+ GITHUB_URLS: constants.GITHUB_URLS,
43
+ ERROR_CODES: constants.ERROR_CODES,
44
+ FILE_PATHS: constants.FILE_PATHS,
45
+ RETRY_CONFIG: constants.RETRY_CONFIG
46
+ };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Logger utility for colored console output
3
+ * Provides consistent logging across the NPX installer with chalk-based colors
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+
8
+ /**
9
+ * Log levels with corresponding colors and symbols
10
+ */
11
+ const LOG_LEVELS = {
12
+ DEBUG: { color: chalk.gray, symbol: '🔧', level: 0 },
13
+ INFO: { color: chalk.blue, symbol: 'ℹ️', level: 1 },
14
+ SUCCESS: { color: chalk.green, symbol: '✅', level: 2 },
15
+ WARN: { color: chalk.yellow, symbol: '⚠️', level: 3 },
16
+ ERROR: { color: chalk.red, symbol: '❌', level: 4 },
17
+ SPINNER: { color: chalk.cyan, symbol: '⏳', level: 1 }
18
+ };
19
+
20
+ /**
21
+ * Logger class with colored output and level filtering
22
+ */
23
+ class Logger {
24
+ constructor(options = {}) {
25
+ this.level = options.level || 'INFO';
26
+ this.prefix = options.prefix || '@claude-code/setup-installer';
27
+ this.timestamps = options.timestamps || false;
28
+ this.silent = options.silent || false;
29
+ }
30
+
31
+ /**
32
+ * Check if message should be logged based on level
33
+ */
34
+ shouldLog(messageLevel) {
35
+ if (this.silent) return false;
36
+ const currentLevel = LOG_LEVELS[this.level]?.level || 1;
37
+ const msgLevel = LOG_LEVELS[messageLevel]?.level || 1;
38
+ return msgLevel >= currentLevel;
39
+ }
40
+
41
+ /**
42
+ * Format a log message with prefix, timestamp, and color
43
+ */
44
+ formatMessage(level, message, data = null) {
45
+ const config = LOG_LEVELS[level] || LOG_LEVELS.INFO;
46
+ const timestamp = this.timestamps ? chalk.gray(`[${new Date().toISOString()}]`) : '';
47
+ const prefix = chalk.bold(`[${this.prefix}]`);
48
+ const symbol = config.symbol;
49
+ const coloredMessage = config.color(message);
50
+
51
+ let formatted = `${timestamp} ${prefix} ${symbol} ${coloredMessage}`;
52
+
53
+ if (data) {
54
+ formatted += '\n' + this.formatData(data);
55
+ }
56
+
57
+ return formatted;
58
+ }
59
+
60
+ /**
61
+ * Format additional data for logging
62
+ */
63
+ formatData(data) {
64
+ if (typeof data === 'object') {
65
+ return chalk.gray(JSON.stringify(data, null, 2));
66
+ }
67
+ return chalk.gray(String(data));
68
+ }
69
+
70
+ /**
71
+ * Log a debug message
72
+ */
73
+ debug(message, data) {
74
+ if (this.shouldLog('DEBUG')) {
75
+ console.log(this.formatMessage('DEBUG', message, data));
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Log an info message
81
+ */
82
+ info(message, data) {
83
+ if (this.shouldLog('INFO')) {
84
+ console.log(this.formatMessage('INFO', message, data));
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Log a success message
90
+ */
91
+ success(message, data) {
92
+ if (this.shouldLog('SUCCESS')) {
93
+ console.log(this.formatMessage('SUCCESS', message, data));
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Log a warning message
99
+ */
100
+ warn(message, data) {
101
+ if (this.shouldLog('WARN')) {
102
+ console.warn(this.formatMessage('WARN', message, data));
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Log an error message
108
+ */
109
+ error(message, data) {
110
+ if (this.shouldLog('ERROR')) {
111
+ console.error(this.formatMessage('ERROR', message, data));
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Log a spinner/progress message
117
+ */
118
+ spinner(message, data) {
119
+ if (this.shouldLog('SPINNER')) {
120
+ console.log(this.formatMessage('SPINNER', message, data));
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Create a new logger with different options
126
+ */
127
+ child(options) {
128
+ return new Logger({
129
+ level: this.level,
130
+ prefix: this.prefix,
131
+ timestamps: this.timestamps,
132
+ silent: this.silent,
133
+ ...options
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Set the log level
139
+ */
140
+ setLevel(level) {
141
+ if (LOG_LEVELS[level]) {
142
+ this.level = level;
143
+ } else {
144
+ this.warn(`Invalid log level: ${level}. Using INFO instead.`);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Enable or disable silent mode
150
+ */
151
+ setSilent(silent) {
152
+ this.silent = silent;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Default logger instance
158
+ */
159
+ const defaultLogger = new Logger();
160
+
161
+ /**
162
+ * Convenience functions using default logger
163
+ */
164
+ const logger = {
165
+ debug: (msg, data) => defaultLogger.debug(msg, data),
166
+ info: (msg, data) => defaultLogger.info(msg, data),
167
+ success: (msg, data) => defaultLogger.success(msg, data),
168
+ warn: (msg, data) => defaultLogger.warn(msg, data),
169
+ error: (msg, data) => defaultLogger.error(msg, data),
170
+ spinner: (msg, data) => defaultLogger.spinner(msg, data),
171
+ setLevel: (level) => defaultLogger.setLevel(level),
172
+ setSilent: (silent) => defaultLogger.setSilent(silent),
173
+ child: (options) => defaultLogger.child(options),
174
+ Logger
175
+ };
176
+
177
+ module.exports = logger;