@wundr.io/cli 1.0.10 → 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.
Files changed (269) hide show
  1. package/bin/wundr.js +8 -4
  2. package/package.json +23 -23
  3. package/src/ai/ai-service.ts +16 -17
  4. package/src/ai/claude-client.ts +16 -16
  5. package/src/ai/conversation-manager.ts +29 -29
  6. package/src/cli.ts +4 -4
  7. package/src/commands/ai.ts +246 -78
  8. package/src/commands/alignment.ts +74 -74
  9. package/src/commands/analyze-optimized.ts +111 -78
  10. package/src/commands/analyze.ts +14 -14
  11. package/src/commands/batch.ts +179 -42
  12. package/src/commands/chat.ts +37 -30
  13. package/src/commands/claude-init.ts +41 -45
  14. package/src/commands/claude-setup.ts +204 -119
  15. package/src/commands/computer-setup.ts +85 -43
  16. package/src/commands/create-command.ts +4 -4
  17. package/src/commands/create.ts +27 -27
  18. package/src/commands/dashboard.ts +24 -24
  19. package/src/commands/govern.ts +25 -25
  20. package/src/commands/governance.ts +34 -34
  21. package/src/commands/guardian.ts +56 -56
  22. package/src/commands/init.ts +25 -22
  23. package/src/commands/orchestrator.ts +68 -41
  24. package/src/commands/performance-optimizer.ts +34 -35
  25. package/src/commands/plugins.ts +27 -27
  26. package/src/commands/project-update.ts +175 -72
  27. package/src/commands/rag.ts +185 -78
  28. package/src/commands/session.ts +35 -35
  29. package/src/commands/setup.ts +40 -344
  30. package/src/commands/test-init.ts +3 -3
  31. package/src/commands/test.ts +4 -4
  32. package/src/commands/watch.ts +28 -29
  33. package/src/commands/worktree.ts +49 -49
  34. package/src/context/context-manager.ts +10 -10
  35. package/src/context/session-manager.ts +41 -41
  36. package/src/framework/command-interface.ts +520 -0
  37. package/src/framework/command-registry.ts +942 -0
  38. package/src/framework/completion-exporter.ts +383 -0
  39. package/src/framework/debug-logger.ts +519 -0
  40. package/src/framework/error-handler.ts +867 -0
  41. package/src/framework/help-generator.ts +540 -0
  42. package/src/framework/index.ts +169 -0
  43. package/src/framework/interactive-repl.ts +703 -0
  44. package/src/framework/output-formatter.ts +834 -0
  45. package/src/framework/progress-manager.ts +539 -0
  46. package/src/index.ts +4 -4
  47. package/src/interactive/interactive-mode.ts +16 -16
  48. package/src/lib/conflict-resolution.ts +799 -9
  49. package/src/lib/merge-strategy.ts +529 -7
  50. package/src/lib/safety-mechanisms.ts +422 -18
  51. package/src/lib/state-detection.ts +1015 -13
  52. package/src/nlp/command-mapper.ts +29 -29
  53. package/src/nlp/command-parser.ts +17 -17
  54. package/src/nlp/intent-classifier.ts +7 -7
  55. package/src/nlp/intent-parser.ts +54 -52
  56. package/src/plugins/plugin-manager.ts +61 -39
  57. package/src/tests/computer-setup-integration.test.ts +46 -15
  58. package/src/types/modules.d.ts +424 -1
  59. package/src/utils/backup-rollback-manager.ts +11 -8
  60. package/src/utils/config-manager.ts +3 -3
  61. package/src/utils/error-handler.ts +2 -2
  62. package/src/utils/logger.ts +22 -22
  63. package/templates/batch/ci-cd.yaml +7 -7
  64. package/test-suites/api/health.spec.ts +20 -23
  65. package/test-suites/helpers/test-config.ts +14 -13
  66. package/test-suites/ui/accessibility.spec.ts +27 -22
  67. package/test-suites/ui/smoke.spec.ts +26 -21
  68. package/LICENSE +0 -21
  69. package/dist/ai/ai-service.d.ts +0 -152
  70. package/dist/ai/ai-service.d.ts.map +0 -1
  71. package/dist/ai/ai-service.js +0 -430
  72. package/dist/ai/ai-service.js.map +0 -1
  73. package/dist/ai/claude-client.d.ts +0 -130
  74. package/dist/ai/claude-client.d.ts.map +0 -1
  75. package/dist/ai/claude-client.js +0 -340
  76. package/dist/ai/claude-client.js.map +0 -1
  77. package/dist/ai/conversation-manager.d.ts +0 -164
  78. package/dist/ai/conversation-manager.d.ts.map +0 -1
  79. package/dist/ai/conversation-manager.js +0 -614
  80. package/dist/ai/conversation-manager.js.map +0 -1
  81. package/dist/ai/index.d.ts +0 -5
  82. package/dist/ai/index.d.ts.map +0 -1
  83. package/dist/ai/index.js +0 -8
  84. package/dist/ai/index.js.map +0 -1
  85. package/dist/cli.d.ts +0 -36
  86. package/dist/cli.d.ts.map +0 -1
  87. package/dist/cli.js +0 -192
  88. package/dist/cli.js.map +0 -1
  89. package/dist/commands/ai.d.ts +0 -89
  90. package/dist/commands/ai.d.ts.map +0 -1
  91. package/dist/commands/ai.js +0 -799
  92. package/dist/commands/ai.js.map +0 -1
  93. package/dist/commands/alignment.d.ts +0 -78
  94. package/dist/commands/alignment.d.ts.map +0 -1
  95. package/dist/commands/alignment.js +0 -817
  96. package/dist/commands/alignment.js.map +0 -1
  97. package/dist/commands/analyze-optimized.d.ts +0 -14
  98. package/dist/commands/analyze-optimized.d.ts.map +0 -1
  99. package/dist/commands/analyze-optimized.js +0 -600
  100. package/dist/commands/analyze-optimized.js.map +0 -1
  101. package/dist/commands/analyze.d.ts +0 -65
  102. package/dist/commands/analyze.d.ts.map +0 -1
  103. package/dist/commands/analyze.js +0 -435
  104. package/dist/commands/analyze.js.map +0 -1
  105. package/dist/commands/batch.d.ts +0 -71
  106. package/dist/commands/batch.d.ts.map +0 -1
  107. package/dist/commands/batch.js +0 -738
  108. package/dist/commands/batch.js.map +0 -1
  109. package/dist/commands/chat.d.ts +0 -71
  110. package/dist/commands/chat.d.ts.map +0 -1
  111. package/dist/commands/chat.js +0 -674
  112. package/dist/commands/chat.js.map +0 -1
  113. package/dist/commands/claude-init.d.ts +0 -28
  114. package/dist/commands/claude-init.d.ts.map +0 -1
  115. package/dist/commands/claude-init.js +0 -591
  116. package/dist/commands/claude-init.js.map +0 -1
  117. package/dist/commands/claude-setup.d.ts +0 -119
  118. package/dist/commands/claude-setup.d.ts.map +0 -1
  119. package/dist/commands/claude-setup.js +0 -1073
  120. package/dist/commands/claude-setup.js.map +0 -1
  121. package/dist/commands/computer-setup-commands.d.ts +0 -53
  122. package/dist/commands/computer-setup-commands.d.ts.map +0 -1
  123. package/dist/commands/computer-setup-commands.js +0 -705
  124. package/dist/commands/computer-setup-commands.js.map +0 -1
  125. package/dist/commands/computer-setup.d.ts +0 -7
  126. package/dist/commands/computer-setup.d.ts.map +0 -1
  127. package/dist/commands/computer-setup.js +0 -849
  128. package/dist/commands/computer-setup.js.map +0 -1
  129. package/dist/commands/create-command.d.ts +0 -7
  130. package/dist/commands/create-command.d.ts.map +0 -1
  131. package/dist/commands/create-command.js +0 -158
  132. package/dist/commands/create-command.js.map +0 -1
  133. package/dist/commands/create.d.ts +0 -74
  134. package/dist/commands/create.d.ts.map +0 -1
  135. package/dist/commands/create.js +0 -556
  136. package/dist/commands/create.js.map +0 -1
  137. package/dist/commands/dashboard.d.ts +0 -91
  138. package/dist/commands/dashboard.d.ts.map +0 -1
  139. package/dist/commands/dashboard.js +0 -538
  140. package/dist/commands/dashboard.js.map +0 -1
  141. package/dist/commands/govern.d.ts +0 -70
  142. package/dist/commands/govern.d.ts.map +0 -1
  143. package/dist/commands/govern.js +0 -481
  144. package/dist/commands/govern.js.map +0 -1
  145. package/dist/commands/governance.d.ts +0 -17
  146. package/dist/commands/governance.d.ts.map +0 -1
  147. package/dist/commands/governance.js +0 -703
  148. package/dist/commands/governance.js.map +0 -1
  149. package/dist/commands/guardian.d.ts +0 -20
  150. package/dist/commands/guardian.d.ts.map +0 -1
  151. package/dist/commands/guardian.js +0 -597
  152. package/dist/commands/guardian.js.map +0 -1
  153. package/dist/commands/init.d.ts +0 -59
  154. package/dist/commands/init.d.ts.map +0 -1
  155. package/dist/commands/init.js +0 -650
  156. package/dist/commands/init.js.map +0 -1
  157. package/dist/commands/orchestrator.d.ts +0 -7
  158. package/dist/commands/orchestrator.d.ts.map +0 -1
  159. package/dist/commands/orchestrator.js +0 -571
  160. package/dist/commands/orchestrator.js.map +0 -1
  161. package/dist/commands/performance-optimizer.d.ts +0 -30
  162. package/dist/commands/performance-optimizer.d.ts.map +0 -1
  163. package/dist/commands/performance-optimizer.js +0 -650
  164. package/dist/commands/performance-optimizer.js.map +0 -1
  165. package/dist/commands/plugins.d.ts +0 -87
  166. package/dist/commands/plugins.d.ts.map +0 -1
  167. package/dist/commands/plugins.js +0 -685
  168. package/dist/commands/plugins.js.map +0 -1
  169. package/dist/commands/rag.d.ts +0 -7
  170. package/dist/commands/rag.d.ts.map +0 -1
  171. package/dist/commands/rag.js +0 -748
  172. package/dist/commands/rag.js.map +0 -1
  173. package/dist/commands/session.d.ts +0 -41
  174. package/dist/commands/session.d.ts.map +0 -1
  175. package/dist/commands/session.js +0 -441
  176. package/dist/commands/session.js.map +0 -1
  177. package/dist/commands/setup.d.ts +0 -29
  178. package/dist/commands/setup.d.ts.map +0 -1
  179. package/dist/commands/setup.js +0 -397
  180. package/dist/commands/setup.js.map +0 -1
  181. package/dist/commands/test-init.d.ts +0 -9
  182. package/dist/commands/test-init.d.ts.map +0 -1
  183. package/dist/commands/test-init.js +0 -222
  184. package/dist/commands/test-init.js.map +0 -1
  185. package/dist/commands/test.d.ts +0 -25
  186. package/dist/commands/test.d.ts.map +0 -1
  187. package/dist/commands/test.js +0 -217
  188. package/dist/commands/test.js.map +0 -1
  189. package/dist/commands/vp.d.ts +0 -7
  190. package/dist/commands/vp.d.ts.map +0 -1
  191. package/dist/commands/vp.js +0 -571
  192. package/dist/commands/vp.js.map +0 -1
  193. package/dist/commands/watch.d.ts +0 -76
  194. package/dist/commands/watch.d.ts.map +0 -1
  195. package/dist/commands/watch.js +0 -613
  196. package/dist/commands/watch.js.map +0 -1
  197. package/dist/commands/worktree.d.ts +0 -63
  198. package/dist/commands/worktree.d.ts.map +0 -1
  199. package/dist/commands/worktree.js +0 -774
  200. package/dist/commands/worktree.js.map +0 -1
  201. package/dist/context/context-manager.d.ts +0 -155
  202. package/dist/context/context-manager.d.ts.map +0 -1
  203. package/dist/context/context-manager.js +0 -383
  204. package/dist/context/context-manager.js.map +0 -1
  205. package/dist/context/index.d.ts +0 -3
  206. package/dist/context/index.d.ts.map +0 -1
  207. package/dist/context/index.js +0 -6
  208. package/dist/context/index.js.map +0 -1
  209. package/dist/context/session-manager.d.ts +0 -207
  210. package/dist/context/session-manager.d.ts.map +0 -1
  211. package/dist/context/session-manager.js +0 -686
  212. package/dist/context/session-manager.js.map +0 -1
  213. package/dist/index.d.ts +0 -8
  214. package/dist/index.d.ts.map +0 -1
  215. package/dist/index.js +0 -51
  216. package/dist/index.js.map +0 -1
  217. package/dist/interactive/interactive-mode.d.ts +0 -76
  218. package/dist/interactive/interactive-mode.d.ts.map +0 -1
  219. package/dist/interactive/interactive-mode.js +0 -732
  220. package/dist/interactive/interactive-mode.js.map +0 -1
  221. package/dist/nlp/command-mapper.d.ts +0 -174
  222. package/dist/nlp/command-mapper.d.ts.map +0 -1
  223. package/dist/nlp/command-mapper.js +0 -624
  224. package/dist/nlp/command-mapper.js.map +0 -1
  225. package/dist/nlp/command-parser.d.ts +0 -106
  226. package/dist/nlp/command-parser.d.ts.map +0 -1
  227. package/dist/nlp/command-parser.js +0 -417
  228. package/dist/nlp/command-parser.js.map +0 -1
  229. package/dist/nlp/index.d.ts +0 -5
  230. package/dist/nlp/index.d.ts.map +0 -1
  231. package/dist/nlp/index.js +0 -8
  232. package/dist/nlp/index.js.map +0 -1
  233. package/dist/nlp/intent-classifier.d.ts +0 -59
  234. package/dist/nlp/intent-classifier.d.ts.map +0 -1
  235. package/dist/nlp/intent-classifier.js +0 -384
  236. package/dist/nlp/intent-classifier.js.map +0 -1
  237. package/dist/nlp/intent-parser.d.ts +0 -152
  238. package/dist/nlp/intent-parser.d.ts.map +0 -1
  239. package/dist/nlp/intent-parser.js +0 -744
  240. package/dist/nlp/intent-parser.js.map +0 -1
  241. package/dist/plugins/plugin-manager.d.ts +0 -120
  242. package/dist/plugins/plugin-manager.d.ts.map +0 -1
  243. package/dist/plugins/plugin-manager.js +0 -595
  244. package/dist/plugins/plugin-manager.js.map +0 -1
  245. package/dist/types/index.d.ts +0 -224
  246. package/dist/types/index.d.ts.map +0 -1
  247. package/dist/types/index.js +0 -3
  248. package/dist/types/index.js.map +0 -1
  249. package/dist/utils/backup-rollback-manager.d.ts +0 -72
  250. package/dist/utils/backup-rollback-manager.d.ts.map +0 -1
  251. package/dist/utils/backup-rollback-manager.js +0 -289
  252. package/dist/utils/backup-rollback-manager.js.map +0 -1
  253. package/dist/utils/claude-config-installer.d.ts +0 -98
  254. package/dist/utils/claude-config-installer.d.ts.map +0 -1
  255. package/dist/utils/claude-config-installer.js +0 -678
  256. package/dist/utils/claude-config-installer.js.map +0 -1
  257. package/dist/utils/config-manager.d.ts +0 -73
  258. package/dist/utils/config-manager.d.ts.map +0 -1
  259. package/dist/utils/config-manager.js +0 -339
  260. package/dist/utils/config-manager.js.map +0 -1
  261. package/dist/utils/error-handler.d.ts +0 -46
  262. package/dist/utils/error-handler.d.ts.map +0 -1
  263. package/dist/utils/error-handler.js +0 -169
  264. package/dist/utils/error-handler.js.map +0 -1
  265. package/dist/utils/logger.d.ts +0 -25
  266. package/dist/utils/logger.d.ts.map +0 -1
  267. package/dist/utils/logger.js +0 -105
  268. package/dist/utils/logger.js.map +0 -1
  269. 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
+ }