@wundr.io/cli 1.0.11 → 1.0.12
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.
- package/bin/wundr.js +8 -4
- package/package.json +23 -23
- package/src/ai/ai-service.ts +16 -17
- package/src/ai/claude-client.ts +16 -16
- package/src/ai/conversation-manager.ts +29 -29
- package/src/cli.ts +4 -4
- package/src/commands/ai.ts +246 -78
- package/src/commands/alignment.ts +74 -74
- package/src/commands/analyze-optimized.ts +111 -78
- package/src/commands/analyze.ts +14 -14
- package/src/commands/batch.ts +179 -42
- package/src/commands/chat.ts +37 -30
- package/src/commands/claude-init.ts +41 -45
- package/src/commands/claude-setup.ts +204 -119
- package/src/commands/computer-setup.ts +85 -43
- package/src/commands/create-command.ts +4 -4
- package/src/commands/create.ts +27 -27
- package/src/commands/dashboard.ts +24 -24
- package/src/commands/govern.ts +25 -25
- package/src/commands/governance.ts +34 -34
- package/src/commands/guardian.ts +56 -56
- package/src/commands/init.ts +25 -22
- package/src/commands/orchestrator.ts +68 -41
- package/src/commands/performance-optimizer.ts +34 -35
- package/src/commands/plugins.ts +27 -27
- package/src/commands/project-update.ts +175 -72
- package/src/commands/rag.ts +185 -78
- package/src/commands/session.ts +35 -35
- package/src/commands/setup.ts +40 -344
- package/src/commands/test-init.ts +3 -3
- package/src/commands/test.ts +4 -4
- package/src/commands/watch.ts +28 -29
- package/src/commands/worktree.ts +49 -49
- package/src/context/context-manager.ts +10 -10
- package/src/context/session-manager.ts +41 -41
- package/src/framework/command-interface.ts +520 -0
- package/src/framework/command-registry.ts +942 -0
- package/src/framework/completion-exporter.ts +383 -0
- package/src/framework/debug-logger.ts +519 -0
- package/src/framework/error-handler.ts +867 -0
- package/src/framework/help-generator.ts +540 -0
- package/src/framework/index.ts +169 -0
- package/src/framework/interactive-repl.ts +703 -0
- package/src/framework/output-formatter.ts +834 -0
- package/src/framework/progress-manager.ts +539 -0
- package/src/index.ts +4 -4
- package/src/interactive/interactive-mode.ts +16 -16
- package/src/lib/conflict-resolution.ts +799 -9
- package/src/lib/merge-strategy.ts +529 -7
- package/src/lib/safety-mechanisms.ts +422 -18
- package/src/lib/state-detection.ts +1015 -13
- package/src/nlp/command-mapper.ts +29 -29
- package/src/nlp/command-parser.ts +17 -17
- package/src/nlp/intent-classifier.ts +7 -7
- package/src/nlp/intent-parser.ts +54 -52
- package/src/plugins/plugin-manager.ts +61 -39
- package/src/tests/computer-setup-integration.test.ts +46 -15
- package/src/types/modules.d.ts +424 -1
- package/src/utils/backup-rollback-manager.ts +11 -8
- package/src/utils/config-manager.ts +3 -3
- package/src/utils/error-handler.ts +2 -2
- package/src/utils/logger.ts +22 -22
- package/templates/batch/ci-cd.yaml +7 -7
- package/test-suites/api/health.spec.ts +20 -23
- package/test-suites/helpers/test-config.ts +14 -13
- package/test-suites/ui/accessibility.spec.ts +27 -22
- package/test-suites/ui/smoke.spec.ts +26 -21
- package/LICENSE +0 -21
- package/dist/ai/ai-service.d.ts +0 -152
- package/dist/ai/ai-service.d.ts.map +0 -1
- package/dist/ai/ai-service.js +0 -430
- package/dist/ai/ai-service.js.map +0 -1
- package/dist/ai/claude-client.d.ts +0 -130
- package/dist/ai/claude-client.d.ts.map +0 -1
- package/dist/ai/claude-client.js +0 -340
- package/dist/ai/claude-client.js.map +0 -1
- package/dist/ai/conversation-manager.d.ts +0 -164
- package/dist/ai/conversation-manager.d.ts.map +0 -1
- package/dist/ai/conversation-manager.js +0 -614
- package/dist/ai/conversation-manager.js.map +0 -1
- package/dist/ai/index.d.ts +0 -5
- package/dist/ai/index.d.ts.map +0 -1
- package/dist/ai/index.js +0 -8
- package/dist/ai/index.js.map +0 -1
- package/dist/cli.d.ts +0 -36
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -192
- package/dist/cli.js.map +0 -1
- package/dist/commands/ai.d.ts +0 -89
- package/dist/commands/ai.d.ts.map +0 -1
- package/dist/commands/ai.js +0 -799
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/alignment.d.ts +0 -78
- package/dist/commands/alignment.d.ts.map +0 -1
- package/dist/commands/alignment.js +0 -817
- package/dist/commands/alignment.js.map +0 -1
- package/dist/commands/analyze-optimized.d.ts +0 -14
- package/dist/commands/analyze-optimized.d.ts.map +0 -1
- package/dist/commands/analyze-optimized.js +0 -600
- package/dist/commands/analyze-optimized.js.map +0 -1
- package/dist/commands/analyze.d.ts +0 -65
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js +0 -435
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/batch.d.ts +0 -71
- package/dist/commands/batch.d.ts.map +0 -1
- package/dist/commands/batch.js +0 -738
- package/dist/commands/batch.js.map +0 -1
- package/dist/commands/chat.d.ts +0 -71
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js +0 -674
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/claude-init.d.ts +0 -28
- package/dist/commands/claude-init.d.ts.map +0 -1
- package/dist/commands/claude-init.js +0 -591
- package/dist/commands/claude-init.js.map +0 -1
- package/dist/commands/claude-setup.d.ts +0 -119
- package/dist/commands/claude-setup.d.ts.map +0 -1
- package/dist/commands/claude-setup.js +0 -1073
- package/dist/commands/claude-setup.js.map +0 -1
- package/dist/commands/computer-setup-commands.d.ts +0 -53
- package/dist/commands/computer-setup-commands.d.ts.map +0 -1
- package/dist/commands/computer-setup-commands.js +0 -705
- package/dist/commands/computer-setup-commands.js.map +0 -1
- package/dist/commands/computer-setup.d.ts +0 -7
- package/dist/commands/computer-setup.d.ts.map +0 -1
- package/dist/commands/computer-setup.js +0 -849
- package/dist/commands/computer-setup.js.map +0 -1
- package/dist/commands/create-command.d.ts +0 -7
- package/dist/commands/create-command.d.ts.map +0 -1
- package/dist/commands/create-command.js +0 -158
- package/dist/commands/create-command.js.map +0 -1
- package/dist/commands/create.d.ts +0 -74
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -556
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/dashboard.d.ts +0 -91
- package/dist/commands/dashboard.d.ts.map +0 -1
- package/dist/commands/dashboard.js +0 -538
- package/dist/commands/dashboard.js.map +0 -1
- package/dist/commands/govern.d.ts +0 -70
- package/dist/commands/govern.d.ts.map +0 -1
- package/dist/commands/govern.js +0 -481
- package/dist/commands/govern.js.map +0 -1
- package/dist/commands/governance.d.ts +0 -17
- package/dist/commands/governance.d.ts.map +0 -1
- package/dist/commands/governance.js +0 -703
- package/dist/commands/governance.js.map +0 -1
- package/dist/commands/guardian.d.ts +0 -20
- package/dist/commands/guardian.d.ts.map +0 -1
- package/dist/commands/guardian.js +0 -597
- package/dist/commands/guardian.js.map +0 -1
- package/dist/commands/init.d.ts +0 -59
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -650
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/orchestrator.d.ts +0 -7
- package/dist/commands/orchestrator.d.ts.map +0 -1
- package/dist/commands/orchestrator.js +0 -571
- package/dist/commands/orchestrator.js.map +0 -1
- package/dist/commands/performance-optimizer.d.ts +0 -30
- package/dist/commands/performance-optimizer.d.ts.map +0 -1
- package/dist/commands/performance-optimizer.js +0 -650
- package/dist/commands/performance-optimizer.js.map +0 -1
- package/dist/commands/plugins.d.ts +0 -87
- package/dist/commands/plugins.d.ts.map +0 -1
- package/dist/commands/plugins.js +0 -685
- package/dist/commands/plugins.js.map +0 -1
- package/dist/commands/rag.d.ts +0 -7
- package/dist/commands/rag.d.ts.map +0 -1
- package/dist/commands/rag.js +0 -748
- package/dist/commands/rag.js.map +0 -1
- package/dist/commands/session.d.ts +0 -41
- package/dist/commands/session.d.ts.map +0 -1
- package/dist/commands/session.js +0 -441
- package/dist/commands/session.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -397
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/test-init.d.ts +0 -9
- package/dist/commands/test-init.d.ts.map +0 -1
- package/dist/commands/test-init.js +0 -222
- package/dist/commands/test-init.js.map +0 -1
- package/dist/commands/test.d.ts +0 -25
- package/dist/commands/test.d.ts.map +0 -1
- package/dist/commands/test.js +0 -217
- package/dist/commands/test.js.map +0 -1
- package/dist/commands/vp.d.ts +0 -7
- package/dist/commands/vp.d.ts.map +0 -1
- package/dist/commands/vp.js +0 -571
- package/dist/commands/vp.js.map +0 -1
- package/dist/commands/watch.d.ts +0 -76
- package/dist/commands/watch.d.ts.map +0 -1
- package/dist/commands/watch.js +0 -613
- package/dist/commands/watch.js.map +0 -1
- package/dist/commands/worktree.d.ts +0 -63
- package/dist/commands/worktree.d.ts.map +0 -1
- package/dist/commands/worktree.js +0 -774
- package/dist/commands/worktree.js.map +0 -1
- package/dist/context/context-manager.d.ts +0 -155
- package/dist/context/context-manager.d.ts.map +0 -1
- package/dist/context/context-manager.js +0 -383
- package/dist/context/context-manager.js.map +0 -1
- package/dist/context/index.d.ts +0 -3
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js +0 -6
- package/dist/context/index.js.map +0 -1
- package/dist/context/session-manager.d.ts +0 -207
- package/dist/context/session-manager.d.ts.map +0 -1
- package/dist/context/session-manager.js +0 -686
- package/dist/context/session-manager.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/interactive/interactive-mode.d.ts +0 -76
- package/dist/interactive/interactive-mode.d.ts.map +0 -1
- package/dist/interactive/interactive-mode.js +0 -732
- package/dist/interactive/interactive-mode.js.map +0 -1
- package/dist/nlp/command-mapper.d.ts +0 -174
- package/dist/nlp/command-mapper.d.ts.map +0 -1
- package/dist/nlp/command-mapper.js +0 -624
- package/dist/nlp/command-mapper.js.map +0 -1
- package/dist/nlp/command-parser.d.ts +0 -106
- package/dist/nlp/command-parser.d.ts.map +0 -1
- package/dist/nlp/command-parser.js +0 -417
- package/dist/nlp/command-parser.js.map +0 -1
- package/dist/nlp/index.d.ts +0 -5
- package/dist/nlp/index.d.ts.map +0 -1
- package/dist/nlp/index.js +0 -8
- package/dist/nlp/index.js.map +0 -1
- package/dist/nlp/intent-classifier.d.ts +0 -59
- package/dist/nlp/intent-classifier.d.ts.map +0 -1
- package/dist/nlp/intent-classifier.js +0 -384
- package/dist/nlp/intent-classifier.js.map +0 -1
- package/dist/nlp/intent-parser.d.ts +0 -152
- package/dist/nlp/intent-parser.d.ts.map +0 -1
- package/dist/nlp/intent-parser.js +0 -744
- package/dist/nlp/intent-parser.js.map +0 -1
- package/dist/plugins/plugin-manager.d.ts +0 -120
- package/dist/plugins/plugin-manager.d.ts.map +0 -1
- package/dist/plugins/plugin-manager.js +0 -595
- package/dist/plugins/plugin-manager.js.map +0 -1
- package/dist/types/index.d.ts +0 -224
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/utils/backup-rollback-manager.d.ts +0 -72
- package/dist/utils/backup-rollback-manager.d.ts.map +0 -1
- package/dist/utils/backup-rollback-manager.js +0 -289
- package/dist/utils/backup-rollback-manager.js.map +0 -1
- package/dist/utils/claude-config-installer.d.ts +0 -98
- package/dist/utils/claude-config-installer.d.ts.map +0 -1
- package/dist/utils/claude-config-installer.js +0 -678
- package/dist/utils/claude-config-installer.js.map +0 -1
- package/dist/utils/config-manager.d.ts +0 -73
- package/dist/utils/config-manager.d.ts.map +0 -1
- package/dist/utils/config-manager.js +0 -339
- package/dist/utils/config-manager.js.map +0 -1
- package/dist/utils/error-handler.d.ts +0 -46
- package/dist/utils/error-handler.d.ts.map +0 -1
- package/dist/utils/error-handler.js +0 -169
- package/dist/utils/error-handler.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -25
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -105
- package/dist/utils/logger.js.map +0 -1
- package/src/commands/computer-setup-commands.ts +0 -872
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Error Handler - Centralized error handling with recovery suggestions.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Typed error codes with hierarchical naming
|
|
6
|
+
* - Contextual recovery suggestions for every error
|
|
7
|
+
* - Consistent formatting across all commands
|
|
8
|
+
* - Verbose mode stack traces
|
|
9
|
+
* - Error wrapping for async operations
|
|
10
|
+
* - Custom error handler registration for extensions
|
|
11
|
+
*
|
|
12
|
+
* @module framework/error-handler
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
|
|
17
|
+
import type { CommandContext, CommandDefinition } from './command-interface';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Error Codes
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hierarchical error code type.
|
|
25
|
+
*
|
|
26
|
+
* Format: WUNDR_{DOMAIN}_{NUMBER}
|
|
27
|
+
* Domain groups: CLI, DAEMON, CONFIG, AGENT, MEMORY, PLUGIN, BATCH, SETUP, AUDIT
|
|
28
|
+
*/
|
|
29
|
+
export type ErrorCode =
|
|
30
|
+
// CLI errors
|
|
31
|
+
| 'WUNDR_CLI_001' // Command not found
|
|
32
|
+
| 'WUNDR_CLI_002' // Invalid arguments
|
|
33
|
+
| 'WUNDR_CLI_003' // Missing required option
|
|
34
|
+
| 'WUNDR_CLI_004' // Operation cancelled by user
|
|
35
|
+
| 'WUNDR_CLI_005' // Not in a Wundr project directory
|
|
36
|
+
// Daemon errors
|
|
37
|
+
| 'WUNDR_DAEMON_001' // Daemon not running
|
|
38
|
+
| 'WUNDR_DAEMON_002' // Daemon already running
|
|
39
|
+
| 'WUNDR_DAEMON_003' // Port already in use
|
|
40
|
+
| 'WUNDR_DAEMON_004' // Failed to start daemon
|
|
41
|
+
| 'WUNDR_DAEMON_005' // Failed to stop daemon
|
|
42
|
+
| 'WUNDR_DAEMON_006' // Daemon health check failed
|
|
43
|
+
// Config errors
|
|
44
|
+
| 'WUNDR_CONFIG_001' // Config file not found
|
|
45
|
+
| 'WUNDR_CONFIG_002' // Config validation failed
|
|
46
|
+
| 'WUNDR_CONFIG_003' // Config write failed
|
|
47
|
+
| 'WUNDR_CONFIG_004' // Invalid config key path
|
|
48
|
+
// Agent errors
|
|
49
|
+
| 'WUNDR_AGENT_001' // Agent spawn failed
|
|
50
|
+
| 'WUNDR_AGENT_002' // Agent not found
|
|
51
|
+
| 'WUNDR_AGENT_003' // Agent already exists
|
|
52
|
+
| 'WUNDR_AGENT_004' // Maximum agents exceeded
|
|
53
|
+
| 'WUNDR_AGENT_005' // Agent communication failed
|
|
54
|
+
// Memory errors
|
|
55
|
+
| 'WUNDR_MEMORY_001' // Memory store not initialized
|
|
56
|
+
| 'WUNDR_MEMORY_002' // Memory query failed
|
|
57
|
+
| 'WUNDR_MEMORY_003' // Memory sync failed
|
|
58
|
+
| 'WUNDR_MEMORY_004' // Embedding generation failed
|
|
59
|
+
// Plugin errors
|
|
60
|
+
| 'WUNDR_PLUGIN_001' // Plugin not found
|
|
61
|
+
| 'WUNDR_PLUGIN_002' // Plugin install failed
|
|
62
|
+
| 'WUNDR_PLUGIN_003' // Plugin incompatible
|
|
63
|
+
| 'WUNDR_PLUGIN_004' // Plugin activation failed
|
|
64
|
+
// Batch errors
|
|
65
|
+
| 'WUNDR_BATCH_001' // Batch file not found
|
|
66
|
+
| 'WUNDR_BATCH_002' // Batch validation failed
|
|
67
|
+
| 'WUNDR_BATCH_003' // Batch execution failed
|
|
68
|
+
| 'WUNDR_BATCH_004' // Batch timeout
|
|
69
|
+
// Setup errors
|
|
70
|
+
| 'WUNDR_SETUP_001' // Tool installation failed
|
|
71
|
+
| 'WUNDR_SETUP_002' // Profile not found
|
|
72
|
+
| 'WUNDR_SETUP_003' // Platform not supported
|
|
73
|
+
| 'WUNDR_SETUP_004' // Insufficient permissions
|
|
74
|
+
// Audit errors
|
|
75
|
+
| 'WUNDR_AUDIT_001' // Audit scan failed
|
|
76
|
+
| 'WUNDR_AUDIT_002' // Critical vulnerability found
|
|
77
|
+
// System errors
|
|
78
|
+
| 'WUNDR_SYS_001' // File not found
|
|
79
|
+
| 'WUNDR_SYS_002' // Permission denied
|
|
80
|
+
| 'WUNDR_SYS_003' // Network error
|
|
81
|
+
| 'WUNDR_SYS_004' // Disk space insufficient
|
|
82
|
+
// Generic fallback
|
|
83
|
+
| string;
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Error Details
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Additional context attached to a CLI error.
|
|
91
|
+
*/
|
|
92
|
+
export interface ErrorDetails {
|
|
93
|
+
/** The command that was running when the error occurred */
|
|
94
|
+
command?: string;
|
|
95
|
+
|
|
96
|
+
/** Relevant file path */
|
|
97
|
+
file?: string;
|
|
98
|
+
|
|
99
|
+
/** Relevant port number */
|
|
100
|
+
port?: number;
|
|
101
|
+
|
|
102
|
+
/** Process ID */
|
|
103
|
+
pid?: number;
|
|
104
|
+
|
|
105
|
+
/** Agent or session ID */
|
|
106
|
+
entityId?: string;
|
|
107
|
+
|
|
108
|
+
/** Plugin name */
|
|
109
|
+
plugin?: string;
|
|
110
|
+
|
|
111
|
+
/** Original error that caused this one */
|
|
112
|
+
cause?: Error;
|
|
113
|
+
|
|
114
|
+
/** Arbitrary additional context */
|
|
115
|
+
[key: string]: unknown;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Recovery suggestion displayed to the user.
|
|
120
|
+
*/
|
|
121
|
+
export interface RecoverySuggestion {
|
|
122
|
+
/** Human-readable description of what to do */
|
|
123
|
+
description: string;
|
|
124
|
+
|
|
125
|
+
/** Optional command the user can run */
|
|
126
|
+
command?: string;
|
|
127
|
+
|
|
128
|
+
/** Optional documentation link */
|
|
129
|
+
docLink?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// CLI Error
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Typed CLI error with error code, details, and recovery suggestions.
|
|
138
|
+
*
|
|
139
|
+
* All framework-level errors should use this type. Commands create them
|
|
140
|
+
* via `CliErrorHandler.createError()` and throw them; the handler
|
|
141
|
+
* formats them for display.
|
|
142
|
+
*/
|
|
143
|
+
export class CliError extends Error {
|
|
144
|
+
public readonly code: ErrorCode;
|
|
145
|
+
public readonly details: ErrorDetails;
|
|
146
|
+
public readonly suggestions: RecoverySuggestion[];
|
|
147
|
+
public readonly recoverable: boolean;
|
|
148
|
+
public readonly timestamp: Date;
|
|
149
|
+
|
|
150
|
+
constructor(
|
|
151
|
+
code: ErrorCode,
|
|
152
|
+
message: string,
|
|
153
|
+
details: ErrorDetails = {},
|
|
154
|
+
suggestions: RecoverySuggestion[] = [],
|
|
155
|
+
recoverable: boolean = false
|
|
156
|
+
) {
|
|
157
|
+
super(message);
|
|
158
|
+
this.name = 'CliError';
|
|
159
|
+
this.code = code;
|
|
160
|
+
this.details = details;
|
|
161
|
+
this.suggestions = suggestions;
|
|
162
|
+
this.recoverable = recoverable;
|
|
163
|
+
this.timestamp = new Date();
|
|
164
|
+
|
|
165
|
+
// Maintain proper stack trace
|
|
166
|
+
if (Error.captureStackTrace) {
|
|
167
|
+
Error.captureStackTrace(this, CliError);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Error Classification
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* High-level error classification for routing error handling behavior.
|
|
178
|
+
*/
|
|
179
|
+
export type ErrorClassification =
|
|
180
|
+
| 'user'
|
|
181
|
+
| 'system'
|
|
182
|
+
| 'network'
|
|
183
|
+
| 'config'
|
|
184
|
+
| 'plugin'
|
|
185
|
+
| 'unknown';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Classified error with category and recommended action.
|
|
189
|
+
*/
|
|
190
|
+
export interface ClassifiedError {
|
|
191
|
+
classification: ErrorClassification;
|
|
192
|
+
retryable: boolean;
|
|
193
|
+
userFacing: boolean;
|
|
194
|
+
exitCode: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Classify an error code into a high-level category.
|
|
199
|
+
*
|
|
200
|
+
* User errors: bad input, missing args, invalid config values
|
|
201
|
+
* System errors: file not found, permission denied, disk full
|
|
202
|
+
* Network errors: connection refused, timeout, DNS failure
|
|
203
|
+
* Config errors: missing config, validation failure
|
|
204
|
+
* Plugin errors: plugin not found, incompatible, activation failure
|
|
205
|
+
*/
|
|
206
|
+
export function classifyError(code: ErrorCode): ClassifiedError {
|
|
207
|
+
if (code.includes('_CLI_')) {
|
|
208
|
+
return {
|
|
209
|
+
classification: 'user',
|
|
210
|
+
retryable: false,
|
|
211
|
+
userFacing: true,
|
|
212
|
+
exitCode: 1,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (code.includes('_CONFIG_')) {
|
|
216
|
+
return {
|
|
217
|
+
classification: 'config',
|
|
218
|
+
retryable: false,
|
|
219
|
+
userFacing: true,
|
|
220
|
+
exitCode: 1,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (code.includes('_PLUGIN_')) {
|
|
224
|
+
return {
|
|
225
|
+
classification: 'plugin',
|
|
226
|
+
retryable: true,
|
|
227
|
+
userFacing: true,
|
|
228
|
+
exitCode: 1,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (
|
|
232
|
+
code === 'WUNDR_SYS_003' ||
|
|
233
|
+
code.includes('ECONNREFUSED') ||
|
|
234
|
+
code.includes('ETIMEDOUT') ||
|
|
235
|
+
code.includes('ENOTFOUND')
|
|
236
|
+
) {
|
|
237
|
+
return {
|
|
238
|
+
classification: 'network',
|
|
239
|
+
retryable: true,
|
|
240
|
+
userFacing: true,
|
|
241
|
+
exitCode: 2,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (code.startsWith('WUNDR_SYS_')) {
|
|
245
|
+
return {
|
|
246
|
+
classification: 'system',
|
|
247
|
+
retryable: false,
|
|
248
|
+
userFacing: true,
|
|
249
|
+
exitCode: 3,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (code.includes('_DAEMON_003')) {
|
|
253
|
+
return {
|
|
254
|
+
classification: 'system',
|
|
255
|
+
retryable: false,
|
|
256
|
+
userFacing: true,
|
|
257
|
+
exitCode: 3,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
classification: 'unknown',
|
|
262
|
+
retryable: false,
|
|
263
|
+
userFacing: true,
|
|
264
|
+
exitCode: 1,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Classify any Error (including non-CliError) into a high-level category.
|
|
270
|
+
*/
|
|
271
|
+
export function classifyAnyError(error: Error): ClassifiedError {
|
|
272
|
+
if (error instanceof CliError) {
|
|
273
|
+
return classifyError(error.code);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for system error codes
|
|
277
|
+
const systemError = error as Error & { code?: string };
|
|
278
|
+
if (systemError.code) {
|
|
279
|
+
const mapping = SYSTEM_ERROR_MAP[systemError.code];
|
|
280
|
+
if (mapping) {
|
|
281
|
+
return classifyError(mapping.code);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check message heuristics
|
|
286
|
+
const msg = error.message.toLowerCase();
|
|
287
|
+
if (
|
|
288
|
+
msg.includes('network') ||
|
|
289
|
+
msg.includes('timeout') ||
|
|
290
|
+
msg.includes('connection')
|
|
291
|
+
) {
|
|
292
|
+
return {
|
|
293
|
+
classification: 'network',
|
|
294
|
+
retryable: true,
|
|
295
|
+
userFacing: true,
|
|
296
|
+
exitCode: 2,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (msg.includes('permission') || msg.includes('access denied')) {
|
|
300
|
+
return {
|
|
301
|
+
classification: 'system',
|
|
302
|
+
retryable: false,
|
|
303
|
+
userFacing: true,
|
|
304
|
+
exitCode: 3,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
classification: 'unknown',
|
|
310
|
+
retryable: false,
|
|
311
|
+
userFacing: true,
|
|
312
|
+
exitCode: 1,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Error Recovery Handler
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Custom error recovery handler for extending error handling.
|
|
322
|
+
*/
|
|
323
|
+
export interface ErrorRecoveryHandler {
|
|
324
|
+
/** Whether this handler can handle the given error code */
|
|
325
|
+
canHandle(code: ErrorCode): boolean;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Attempt to recover from the error.
|
|
329
|
+
* Returns true if recovery succeeded, false otherwise.
|
|
330
|
+
*/
|
|
331
|
+
recover(error: CliError, context: CommandContext): Promise<boolean>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// Built-in Recovery Suggestions
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
const BUILT_IN_SUGGESTIONS: Record<string, RecoverySuggestion[]> = {
|
|
339
|
+
WUNDR_CLI_001: [
|
|
340
|
+
{ description: 'Check available commands', command: 'wundr --help' },
|
|
341
|
+
{
|
|
342
|
+
description: 'Search for similar commands',
|
|
343
|
+
command: 'wundr --help | grep <term>',
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
WUNDR_CLI_002: [
|
|
347
|
+
{ description: 'Check command syntax', command: 'wundr <command> --help' },
|
|
348
|
+
],
|
|
349
|
+
WUNDR_CLI_005: [
|
|
350
|
+
{
|
|
351
|
+
description: 'Initialize a Wundr project',
|
|
352
|
+
command: 'wundr init project',
|
|
353
|
+
},
|
|
354
|
+
{ description: 'Navigate to your project directory first' },
|
|
355
|
+
],
|
|
356
|
+
WUNDR_DAEMON_001: [
|
|
357
|
+
{ description: 'Start the orchestrator daemon', command: 'wundr start' },
|
|
358
|
+
{
|
|
359
|
+
description: 'Check daemon logs for errors',
|
|
360
|
+
command: 'wundr orchestrator logs',
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
WUNDR_DAEMON_002: [
|
|
364
|
+
{ description: 'Check current daemon status', command: 'wundr status' },
|
|
365
|
+
{ description: 'Stop the running daemon first', command: 'wundr stop' },
|
|
366
|
+
],
|
|
367
|
+
WUNDR_DAEMON_003: [
|
|
368
|
+
{ description: 'Use a different port', command: 'wundr start --port 9000' },
|
|
369
|
+
{ description: 'Check what is using the port', command: 'lsof -i :<port>' },
|
|
370
|
+
],
|
|
371
|
+
WUNDR_DAEMON_004: [
|
|
372
|
+
{
|
|
373
|
+
description: 'Check if the orchestrator daemon module is installed',
|
|
374
|
+
command: 'npm ls @wundr/orchestrator-daemon',
|
|
375
|
+
},
|
|
376
|
+
{ description: 'View daemon logs', command: 'wundr orchestrator logs' },
|
|
377
|
+
],
|
|
378
|
+
WUNDR_CONFIG_001: [
|
|
379
|
+
{ description: 'Create a default config', command: 'wundr config reset' },
|
|
380
|
+
{
|
|
381
|
+
description: 'Initialize a project with config',
|
|
382
|
+
command: 'wundr init config',
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
WUNDR_CONFIG_002: [
|
|
386
|
+
{
|
|
387
|
+
description: 'Validate your configuration',
|
|
388
|
+
command: 'wundr config validate',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
description: 'Reset to default configuration',
|
|
392
|
+
command: 'wundr config reset --force',
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
WUNDR_CONFIG_004: [
|
|
396
|
+
{
|
|
397
|
+
description: 'View current config to find valid keys',
|
|
398
|
+
command: 'wundr config show',
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
WUNDR_AGENT_001: [
|
|
402
|
+
{
|
|
403
|
+
description: 'Check that the daemon is running',
|
|
404
|
+
command: 'wundr status',
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
description: 'View agent limits in config',
|
|
408
|
+
command: 'wundr config show',
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
WUNDR_AGENT_002: [
|
|
412
|
+
{ description: 'List active agents', command: 'wundr agent list' },
|
|
413
|
+
],
|
|
414
|
+
WUNDR_AGENT_004: [
|
|
415
|
+
{
|
|
416
|
+
description: 'Stop idle agents to free slots',
|
|
417
|
+
command: 'wundr agent list',
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
description: 'Increase maxSessions in config',
|
|
421
|
+
command: 'wundr orchestrator config set daemon.maxSessions=200',
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
WUNDR_MEMORY_001: [
|
|
425
|
+
{
|
|
426
|
+
description: 'Initialize the memory/RAG store',
|
|
427
|
+
command: 'wundr init rag',
|
|
428
|
+
},
|
|
429
|
+
{ description: 'Check RAG store status', command: 'wundr rag status' },
|
|
430
|
+
],
|
|
431
|
+
WUNDR_MEMORY_003: [
|
|
432
|
+
{ description: 'Retry the sync operation', command: 'wundr rag sync' },
|
|
433
|
+
{
|
|
434
|
+
description: 'Prune and rebuild the store',
|
|
435
|
+
command: 'wundr rag prune && wundr rag sync',
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
WUNDR_PLUGIN_001: [
|
|
439
|
+
{
|
|
440
|
+
description: 'Search available plugins',
|
|
441
|
+
command: 'wundr plugin search <name>',
|
|
442
|
+
},
|
|
443
|
+
{ description: 'List installed plugins', command: 'wundr plugin list' },
|
|
444
|
+
],
|
|
445
|
+
WUNDR_PLUGIN_002: [
|
|
446
|
+
{ description: 'Check your network connection' },
|
|
447
|
+
{
|
|
448
|
+
description: 'Try installing with --force',
|
|
449
|
+
command: 'wundr plugin install <name> --force',
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
WUNDR_BATCH_001: [
|
|
453
|
+
{ description: 'List available batch jobs', command: 'wundr batch list' },
|
|
454
|
+
{
|
|
455
|
+
description: 'Create a new batch job',
|
|
456
|
+
command: 'wundr batch create <name>',
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
WUNDR_BATCH_002: [
|
|
460
|
+
{
|
|
461
|
+
description: 'Validate the batch file',
|
|
462
|
+
command: 'wundr batch validate <file>',
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
WUNDR_SETUP_002: [
|
|
466
|
+
{
|
|
467
|
+
description: 'List available profiles',
|
|
468
|
+
command: 'wundr setup --interactive',
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
WUNDR_SETUP_004: [
|
|
472
|
+
{ description: 'Run with elevated permissions if needed' },
|
|
473
|
+
{ description: 'Check file/directory permissions' },
|
|
474
|
+
],
|
|
475
|
+
WUNDR_SYS_001: [
|
|
476
|
+
{ description: 'Check the file path and try again' },
|
|
477
|
+
{ description: 'Ensure you are in the correct directory' },
|
|
478
|
+
],
|
|
479
|
+
WUNDR_SYS_002: [
|
|
480
|
+
{ description: 'Check file and directory permissions' },
|
|
481
|
+
{ description: 'Run with appropriate user privileges' },
|
|
482
|
+
],
|
|
483
|
+
WUNDR_SYS_003: [
|
|
484
|
+
{ description: 'Check your internet connection' },
|
|
485
|
+
{ description: 'Check if a proxy is configured correctly' },
|
|
486
|
+
{ description: 'Retry the operation' },
|
|
487
|
+
],
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
491
|
+
// System Error Mappings
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
|
|
494
|
+
const SYSTEM_ERROR_MAP: Record<string, { code: ErrorCode; message: string }> = {
|
|
495
|
+
ENOENT: { code: 'WUNDR_SYS_001', message: 'File or directory not found' },
|
|
496
|
+
EACCES: { code: 'WUNDR_SYS_002', message: 'Permission denied' },
|
|
497
|
+
EPERM: { code: 'WUNDR_SYS_002', message: 'Operation not permitted' },
|
|
498
|
+
ECONNREFUSED: { code: 'WUNDR_SYS_003', message: 'Connection refused' },
|
|
499
|
+
ENOTFOUND: { code: 'WUNDR_SYS_003', message: 'Host not found' },
|
|
500
|
+
ETIMEDOUT: { code: 'WUNDR_SYS_003', message: 'Connection timed out' },
|
|
501
|
+
ENOSPC: { code: 'WUNDR_SYS_004', message: 'Insufficient disk space' },
|
|
502
|
+
EADDRINUSE: { code: 'WUNDR_DAEMON_003', message: 'Port already in use' },
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
// CLI Error Handler
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
export class CliErrorHandler {
|
|
510
|
+
private customHandlers: ErrorRecoveryHandler[] = [];
|
|
511
|
+
private customSuggestions: Map<string, RecoverySuggestion[]> = new Map();
|
|
512
|
+
|
|
513
|
+
// -------------------------------------------------------------------------
|
|
514
|
+
// Error Creation
|
|
515
|
+
// -------------------------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Create a typed CLI error.
|
|
519
|
+
*
|
|
520
|
+
* @param code - Error code from the registry
|
|
521
|
+
* @param message - Human-readable error message
|
|
522
|
+
* @param details - Additional context
|
|
523
|
+
* @param recoverable - Whether the error is potentially recoverable
|
|
524
|
+
* @returns A CliError instance
|
|
525
|
+
*/
|
|
526
|
+
createError(
|
|
527
|
+
code: ErrorCode,
|
|
528
|
+
message: string,
|
|
529
|
+
details: ErrorDetails = {},
|
|
530
|
+
recoverable: boolean = false
|
|
531
|
+
): CliError {
|
|
532
|
+
const suggestions = this.getSuggestions(code);
|
|
533
|
+
return new CliError(code, message, details, suggestions, recoverable);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Wrap a system error (ENOENT, EACCES, etc.) into a CliError.
|
|
538
|
+
*
|
|
539
|
+
* @param error - The original system error
|
|
540
|
+
* @param context - Additional context about what operation was being performed
|
|
541
|
+
* @returns A CliError with appropriate code and suggestions
|
|
542
|
+
*/
|
|
543
|
+
wrapSystemError(
|
|
544
|
+
error: Error & { code?: string },
|
|
545
|
+
context: string = ''
|
|
546
|
+
): CliError {
|
|
547
|
+
const systemCode = error.code ?? '';
|
|
548
|
+
const mapping = SYSTEM_ERROR_MAP[systemCode];
|
|
549
|
+
|
|
550
|
+
if (mapping) {
|
|
551
|
+
return this.createError(
|
|
552
|
+
mapping.code,
|
|
553
|
+
context ? `${context}: ${mapping.message}` : mapping.message,
|
|
554
|
+
{ cause: error },
|
|
555
|
+
true
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Unknown system error
|
|
560
|
+
return this.createError(
|
|
561
|
+
'WUNDR_CLI_002',
|
|
562
|
+
context ? `${context}: ${error.message}` : error.message,
|
|
563
|
+
{ cause: error },
|
|
564
|
+
false
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// -------------------------------------------------------------------------
|
|
569
|
+
// Error Handling
|
|
570
|
+
// -------------------------------------------------------------------------
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Handle a command execution error.
|
|
574
|
+
*
|
|
575
|
+
* Formats the error for display, including:
|
|
576
|
+
* - Error code and message
|
|
577
|
+
* - Context details
|
|
578
|
+
* - Recovery suggestions
|
|
579
|
+
* - Stack trace (in verbose mode)
|
|
580
|
+
*
|
|
581
|
+
* @param error - The error to handle
|
|
582
|
+
* @param command - The command that was executing
|
|
583
|
+
* @param context - The command context
|
|
584
|
+
*/
|
|
585
|
+
handleCommandError(
|
|
586
|
+
error: Error,
|
|
587
|
+
command: CommandDefinition | undefined,
|
|
588
|
+
context: CommandContext
|
|
589
|
+
): void {
|
|
590
|
+
const verbose = context.globalOptions.verbose;
|
|
591
|
+
const json = context.globalOptions.json;
|
|
592
|
+
|
|
593
|
+
// JSON mode: output structured error
|
|
594
|
+
if (json) {
|
|
595
|
+
const errorData = this.toJson(error, command);
|
|
596
|
+
console.error(JSON.stringify(errorData, null, 2));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// CLI error with full metadata
|
|
601
|
+
if (error instanceof CliError) {
|
|
602
|
+
this.formatCliError(error, verbose, command?.name);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Plain error
|
|
607
|
+
this.formatPlainError(error, verbose, command?.name);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Wrap an async operation with consistent error handling.
|
|
612
|
+
*
|
|
613
|
+
* @param operation - The async operation to execute
|
|
614
|
+
* @param errorContext - Context string for error messages
|
|
615
|
+
* @param code - Error code to use if the operation fails
|
|
616
|
+
* @returns The operation result
|
|
617
|
+
*/
|
|
618
|
+
async withErrorHandling<T>(
|
|
619
|
+
operation: () => Promise<T>,
|
|
620
|
+
errorContext: string,
|
|
621
|
+
code: ErrorCode = 'WUNDR_CLI_002'
|
|
622
|
+
): Promise<T> {
|
|
623
|
+
try {
|
|
624
|
+
return await operation();
|
|
625
|
+
} catch (error) {
|
|
626
|
+
if (error instanceof CliError) {
|
|
627
|
+
throw error;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (error instanceof Error) {
|
|
631
|
+
const systemError = error as Error & { code?: string };
|
|
632
|
+
if (systemError.code && SYSTEM_ERROR_MAP[systemError.code]) {
|
|
633
|
+
throw this.wrapSystemError(systemError, errorContext);
|
|
634
|
+
}
|
|
635
|
+
throw this.createError(
|
|
636
|
+
code,
|
|
637
|
+
`${errorContext}: ${error.message}`,
|
|
638
|
+
{ cause: error },
|
|
639
|
+
true
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
throw this.createError(
|
|
644
|
+
code,
|
|
645
|
+
`${errorContext}: ${String(error)}`,
|
|
646
|
+
{},
|
|
647
|
+
false
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// -------------------------------------------------------------------------
|
|
653
|
+
// Custom Handlers
|
|
654
|
+
// -------------------------------------------------------------------------
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Register a custom error recovery handler.
|
|
658
|
+
*
|
|
659
|
+
* @param handler - The recovery handler
|
|
660
|
+
*/
|
|
661
|
+
registerHandler(handler: ErrorRecoveryHandler): void {
|
|
662
|
+
this.customHandlers.push(handler);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Register custom recovery suggestions for an error code.
|
|
667
|
+
*
|
|
668
|
+
* @param code - Error code
|
|
669
|
+
* @param suggestions - Recovery suggestions
|
|
670
|
+
*/
|
|
671
|
+
registerSuggestions(code: string, suggestions: RecoverySuggestion[]): void {
|
|
672
|
+
this.customSuggestions.set(code, suggestions);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Attempt to recover from an error using registered handlers.
|
|
677
|
+
*
|
|
678
|
+
* @param error - The error to recover from
|
|
679
|
+
* @param context - Command context
|
|
680
|
+
* @returns Whether recovery was successful
|
|
681
|
+
*/
|
|
682
|
+
async attemptRecovery(
|
|
683
|
+
error: CliError,
|
|
684
|
+
context: CommandContext
|
|
685
|
+
): Promise<boolean> {
|
|
686
|
+
for (const handler of this.customHandlers) {
|
|
687
|
+
if (handler.canHandle(error.code)) {
|
|
688
|
+
try {
|
|
689
|
+
const recovered = await handler.recover(error, context);
|
|
690
|
+
if (recovered) {
|
|
691
|
+
context.logger.success('Error recovered successfully.');
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
} catch {
|
|
695
|
+
// Recovery handler itself failed; continue to next
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// -------------------------------------------------------------------------
|
|
703
|
+
// Private Helpers
|
|
704
|
+
// -------------------------------------------------------------------------
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Get recovery suggestions for an error code, merging built-in and custom.
|
|
708
|
+
*/
|
|
709
|
+
private getSuggestions(code: ErrorCode): RecoverySuggestion[] {
|
|
710
|
+
const builtIn = BUILT_IN_SUGGESTIONS[code] ?? [];
|
|
711
|
+
const custom = this.customSuggestions.get(code) ?? [];
|
|
712
|
+
return [...builtIn, ...custom];
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Format a CliError for terminal display.
|
|
717
|
+
*/
|
|
718
|
+
private formatCliError(
|
|
719
|
+
error: CliError,
|
|
720
|
+
verbose: boolean,
|
|
721
|
+
commandName?: string
|
|
722
|
+
): void {
|
|
723
|
+
// Header
|
|
724
|
+
console.error('');
|
|
725
|
+
console.error(chalk.red.bold('Error'));
|
|
726
|
+
console.error(chalk.red(` Code: ${error.code}`));
|
|
727
|
+
console.error(chalk.red(` Message: ${error.message}`));
|
|
728
|
+
|
|
729
|
+
if (commandName) {
|
|
730
|
+
console.error(chalk.gray(` Command: ${commandName}`));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Details
|
|
734
|
+
if (verbose && Object.keys(error.details).length > 0) {
|
|
735
|
+
console.error(chalk.gray('\n Details:'));
|
|
736
|
+
for (const [key, value] of Object.entries(error.details)) {
|
|
737
|
+
if (key === 'cause') continue; // Show cause separately
|
|
738
|
+
console.error(
|
|
739
|
+
chalk.gray(` ${key}: ${this.formatDetailValue(value)}`)
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Suggestions
|
|
745
|
+
if (error.suggestions.length > 0) {
|
|
746
|
+
console.error('');
|
|
747
|
+
if (error.recoverable) {
|
|
748
|
+
console.error(chalk.yellow(' This error may be recoverable. Try:'));
|
|
749
|
+
} else {
|
|
750
|
+
console.error(chalk.yellow(' Suggestions:'));
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
for (const suggestion of error.suggestions) {
|
|
754
|
+
if (suggestion.command) {
|
|
755
|
+
console.error(chalk.yellow(` - ${suggestion.description}`));
|
|
756
|
+
console.error(chalk.cyan(` $ ${suggestion.command}`));
|
|
757
|
+
} else {
|
|
758
|
+
console.error(chalk.yellow(` - ${suggestion.description}`));
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (suggestion.docLink) {
|
|
762
|
+
console.error(chalk.gray(` Docs: ${suggestion.docLink}`));
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Stack trace in verbose mode
|
|
768
|
+
if (verbose) {
|
|
769
|
+
if (error.details.cause instanceof Error && error.details.cause.stack) {
|
|
770
|
+
console.error(chalk.gray('\n Caused by:'));
|
|
771
|
+
console.error(chalk.gray(` ${error.details.cause.message}`));
|
|
772
|
+
const causeStack = error.details.cause.stack.split('\n').slice(1, 5);
|
|
773
|
+
for (const line of causeStack) {
|
|
774
|
+
console.error(chalk.gray(` ${line.trim()}`));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (error.stack) {
|
|
779
|
+
console.error(chalk.gray('\n Stack trace:'));
|
|
780
|
+
const stackLines = error.stack.split('\n').slice(1, 8);
|
|
781
|
+
for (const line of stackLines) {
|
|
782
|
+
console.error(chalk.gray(` ${line.trim()}`));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
console.error(chalk.gray('\n Run with --verbose for more details.'));
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
console.error('');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Format a plain Error for terminal display.
|
|
794
|
+
*/
|
|
795
|
+
private formatPlainError(
|
|
796
|
+
error: Error,
|
|
797
|
+
verbose: boolean,
|
|
798
|
+
commandName?: string
|
|
799
|
+
): void {
|
|
800
|
+
console.error('');
|
|
801
|
+
console.error(chalk.red.bold('Error'));
|
|
802
|
+
console.error(chalk.red(` ${error.message}`));
|
|
803
|
+
|
|
804
|
+
if (commandName) {
|
|
805
|
+
console.error(chalk.gray(` Command: ${commandName}`));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (verbose && error.stack) {
|
|
809
|
+
console.error(chalk.gray('\n Stack trace:'));
|
|
810
|
+
const stackLines = error.stack.split('\n').slice(1, 8);
|
|
811
|
+
for (const line of stackLines) {
|
|
812
|
+
console.error(chalk.gray(` ${line.trim()}`));
|
|
813
|
+
}
|
|
814
|
+
} else if (!verbose) {
|
|
815
|
+
console.error(chalk.gray('\n Run with --verbose for more details.'));
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
console.error('');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Convert an error to a JSON-serializable object.
|
|
823
|
+
*/
|
|
824
|
+
private toJson(
|
|
825
|
+
error: Error,
|
|
826
|
+
command?: CommandDefinition
|
|
827
|
+
): Record<string, unknown> {
|
|
828
|
+
const base: Record<string, unknown> = {
|
|
829
|
+
error: true,
|
|
830
|
+
message: error.message,
|
|
831
|
+
timestamp: new Date().toISOString(),
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
if (command) {
|
|
835
|
+
base['command'] = command.name;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (error instanceof CliError) {
|
|
839
|
+
base['code'] = error.code;
|
|
840
|
+
base['recoverable'] = error.recoverable;
|
|
841
|
+
base['suggestions'] = error.suggestions;
|
|
842
|
+
|
|
843
|
+
// Include non-Error details
|
|
844
|
+
const safeDetails: Record<string, unknown> = {};
|
|
845
|
+
for (const [key, value] of Object.entries(error.details)) {
|
|
846
|
+
if (value instanceof Error) {
|
|
847
|
+
safeDetails[key] = { message: value.message, name: value.name };
|
|
848
|
+
} else {
|
|
849
|
+
safeDetails[key] = value;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
base['details'] = safeDetails;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return base;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Format a detail value for display.
|
|
860
|
+
*/
|
|
861
|
+
private formatDetailValue(value: unknown): string {
|
|
862
|
+
if (value === null || value === undefined) return '(none)';
|
|
863
|
+
if (value instanceof Error) return value.message;
|
|
864
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
865
|
+
return String(value);
|
|
866
|
+
}
|
|
867
|
+
}
|