@wundr.io/cli 1.0.0

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 (213) hide show
  1. package/README.md +551 -0
  2. package/bin/wundr.js +39 -0
  3. package/dist/ai/ai-service.d.ts +152 -0
  4. package/dist/ai/ai-service.d.ts.map +1 -0
  5. package/dist/ai/ai-service.js +430 -0
  6. package/dist/ai/ai-service.js.map +1 -0
  7. package/dist/ai/claude-client.d.ts +130 -0
  8. package/dist/ai/claude-client.d.ts.map +1 -0
  9. package/dist/ai/claude-client.js +339 -0
  10. package/dist/ai/claude-client.js.map +1 -0
  11. package/dist/ai/conversation-manager.d.ts +164 -0
  12. package/dist/ai/conversation-manager.d.ts.map +1 -0
  13. package/dist/ai/conversation-manager.js +612 -0
  14. package/dist/ai/conversation-manager.js.map +1 -0
  15. package/dist/ai/index.d.ts +5 -0
  16. package/dist/ai/index.d.ts.map +1 -0
  17. package/dist/ai/index.js +8 -0
  18. package/dist/ai/index.js.map +1 -0
  19. package/dist/cli.d.ts +36 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +173 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands/ai.d.ts +89 -0
  24. package/dist/commands/ai.d.ts.map +1 -0
  25. package/dist/commands/ai.js +735 -0
  26. package/dist/commands/ai.js.map +1 -0
  27. package/dist/commands/analyze-optimized.d.ts +14 -0
  28. package/dist/commands/analyze-optimized.d.ts.map +1 -0
  29. package/dist/commands/analyze-optimized.js +437 -0
  30. package/dist/commands/analyze-optimized.js.map +1 -0
  31. package/dist/commands/analyze.d.ts +65 -0
  32. package/dist/commands/analyze.d.ts.map +1 -0
  33. package/dist/commands/analyze.js +435 -0
  34. package/dist/commands/analyze.js.map +1 -0
  35. package/dist/commands/batch.d.ts +71 -0
  36. package/dist/commands/batch.d.ts.map +1 -0
  37. package/dist/commands/batch.js +738 -0
  38. package/dist/commands/batch.js.map +1 -0
  39. package/dist/commands/chat.d.ts +71 -0
  40. package/dist/commands/chat.d.ts.map +1 -0
  41. package/dist/commands/chat.js +674 -0
  42. package/dist/commands/chat.js.map +1 -0
  43. package/dist/commands/claude-init.d.ts +28 -0
  44. package/dist/commands/claude-init.d.ts.map +1 -0
  45. package/dist/commands/claude-init.js +587 -0
  46. package/dist/commands/claude-init.js.map +1 -0
  47. package/dist/commands/claude-setup.d.ts +32 -0
  48. package/dist/commands/claude-setup.d.ts.map +1 -0
  49. package/dist/commands/claude-setup.js +570 -0
  50. package/dist/commands/claude-setup.js.map +1 -0
  51. package/dist/commands/computer-setup-commands.d.ts +39 -0
  52. package/dist/commands/computer-setup-commands.d.ts.map +1 -0
  53. package/dist/commands/computer-setup-commands.js +563 -0
  54. package/dist/commands/computer-setup-commands.js.map +1 -0
  55. package/dist/commands/computer-setup.d.ts +7 -0
  56. package/dist/commands/computer-setup.d.ts.map +1 -0
  57. package/dist/commands/computer-setup.js +481 -0
  58. package/dist/commands/computer-setup.js.map +1 -0
  59. package/dist/commands/create-command.d.ts +7 -0
  60. package/dist/commands/create-command.d.ts.map +1 -0
  61. package/dist/commands/create-command.js +158 -0
  62. package/dist/commands/create-command.js.map +1 -0
  63. package/dist/commands/create.d.ts +74 -0
  64. package/dist/commands/create.d.ts.map +1 -0
  65. package/dist/commands/create.js +556 -0
  66. package/dist/commands/create.js.map +1 -0
  67. package/dist/commands/dashboard.d.ts +91 -0
  68. package/dist/commands/dashboard.d.ts.map +1 -0
  69. package/dist/commands/dashboard.js +537 -0
  70. package/dist/commands/dashboard.js.map +1 -0
  71. package/dist/commands/govern.d.ts +70 -0
  72. package/dist/commands/govern.d.ts.map +1 -0
  73. package/dist/commands/govern.js +480 -0
  74. package/dist/commands/govern.js.map +1 -0
  75. package/dist/commands/init.d.ts +55 -0
  76. package/dist/commands/init.d.ts.map +1 -0
  77. package/dist/commands/init.js +584 -0
  78. package/dist/commands/init.js.map +1 -0
  79. package/dist/commands/performance-optimizer.d.ts +30 -0
  80. package/dist/commands/performance-optimizer.d.ts.map +1 -0
  81. package/dist/commands/performance-optimizer.js +649 -0
  82. package/dist/commands/performance-optimizer.js.map +1 -0
  83. package/dist/commands/plugins.d.ts +87 -0
  84. package/dist/commands/plugins.d.ts.map +1 -0
  85. package/dist/commands/plugins.js +685 -0
  86. package/dist/commands/plugins.js.map +1 -0
  87. package/dist/commands/setup.d.ts +29 -0
  88. package/dist/commands/setup.d.ts.map +1 -0
  89. package/dist/commands/setup.js +399 -0
  90. package/dist/commands/setup.js.map +1 -0
  91. package/dist/commands/test-init.d.ts +9 -0
  92. package/dist/commands/test-init.d.ts.map +1 -0
  93. package/dist/commands/test-init.js +222 -0
  94. package/dist/commands/test-init.js.map +1 -0
  95. package/dist/commands/test.d.ts +25 -0
  96. package/dist/commands/test.d.ts.map +1 -0
  97. package/dist/commands/test.js +217 -0
  98. package/dist/commands/test.js.map +1 -0
  99. package/dist/commands/watch.d.ts +76 -0
  100. package/dist/commands/watch.d.ts.map +1 -0
  101. package/dist/commands/watch.js +610 -0
  102. package/dist/commands/watch.js.map +1 -0
  103. package/dist/context/context-manager.d.ts +155 -0
  104. package/dist/context/context-manager.d.ts.map +1 -0
  105. package/dist/context/context-manager.js +383 -0
  106. package/dist/context/context-manager.js.map +1 -0
  107. package/dist/context/index.d.ts +3 -0
  108. package/dist/context/index.d.ts.map +1 -0
  109. package/dist/context/index.js +6 -0
  110. package/dist/context/index.js.map +1 -0
  111. package/dist/context/session-manager.d.ts +207 -0
  112. package/dist/context/session-manager.d.ts.map +1 -0
  113. package/dist/context/session-manager.js +682 -0
  114. package/dist/context/session-manager.js.map +1 -0
  115. package/dist/index.d.ts +8 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +51 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/interactive/interactive-mode.d.ts +76 -0
  120. package/dist/interactive/interactive-mode.d.ts.map +1 -0
  121. package/dist/interactive/interactive-mode.js +730 -0
  122. package/dist/interactive/interactive-mode.js.map +1 -0
  123. package/dist/nlp/command-mapper.d.ts +174 -0
  124. package/dist/nlp/command-mapper.d.ts.map +1 -0
  125. package/dist/nlp/command-mapper.js +623 -0
  126. package/dist/nlp/command-mapper.js.map +1 -0
  127. package/dist/nlp/command-parser.d.ts +106 -0
  128. package/dist/nlp/command-parser.d.ts.map +1 -0
  129. package/dist/nlp/command-parser.js +416 -0
  130. package/dist/nlp/command-parser.js.map +1 -0
  131. package/dist/nlp/index.d.ts +5 -0
  132. package/dist/nlp/index.d.ts.map +1 -0
  133. package/dist/nlp/index.js +8 -0
  134. package/dist/nlp/index.js.map +1 -0
  135. package/dist/nlp/intent-classifier.d.ts +59 -0
  136. package/dist/nlp/intent-classifier.d.ts.map +1 -0
  137. package/dist/nlp/intent-classifier.js +384 -0
  138. package/dist/nlp/intent-classifier.js.map +1 -0
  139. package/dist/nlp/intent-parser.d.ts +152 -0
  140. package/dist/nlp/intent-parser.d.ts.map +1 -0
  141. package/dist/nlp/intent-parser.js +739 -0
  142. package/dist/nlp/intent-parser.js.map +1 -0
  143. package/dist/plugins/plugin-manager.d.ts +120 -0
  144. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  145. package/dist/plugins/plugin-manager.js +595 -0
  146. package/dist/plugins/plugin-manager.js.map +1 -0
  147. package/dist/types/index.d.ts +224 -0
  148. package/dist/types/index.d.ts.map +1 -0
  149. package/dist/types/index.js +3 -0
  150. package/dist/types/index.js.map +1 -0
  151. package/dist/utils/config-manager.d.ts +73 -0
  152. package/dist/utils/config-manager.d.ts.map +1 -0
  153. package/dist/utils/config-manager.js +339 -0
  154. package/dist/utils/config-manager.js.map +1 -0
  155. package/dist/utils/error-handler.d.ts +46 -0
  156. package/dist/utils/error-handler.d.ts.map +1 -0
  157. package/dist/utils/error-handler.js +169 -0
  158. package/dist/utils/error-handler.js.map +1 -0
  159. package/dist/utils/logger.d.ts +25 -0
  160. package/dist/utils/logger.d.ts.map +1 -0
  161. package/dist/utils/logger.js +94 -0
  162. package/dist/utils/logger.js.map +1 -0
  163. package/package.json +119 -0
  164. package/src/ai/ai-service.ts +595 -0
  165. package/src/ai/claude-client.ts +490 -0
  166. package/src/ai/conversation-manager.ts +907 -0
  167. package/src/ai/index.ts +8 -0
  168. package/src/cli.ts +202 -0
  169. package/src/commands/ai.ts +995 -0
  170. package/src/commands/analyze-optimized.ts +641 -0
  171. package/src/commands/analyze.ts +576 -0
  172. package/src/commands/batch.ts +935 -0
  173. package/src/commands/chat.ts +876 -0
  174. package/src/commands/claude-init.ts +715 -0
  175. package/src/commands/claude-setup.ts +697 -0
  176. package/src/commands/computer-setup-commands.ts +709 -0
  177. package/src/commands/computer-setup.ts +565 -0
  178. package/src/commands/create-command.ts +175 -0
  179. package/src/commands/create.ts +727 -0
  180. package/src/commands/dashboard.ts +691 -0
  181. package/src/commands/govern.ts +635 -0
  182. package/src/commands/init.ts +677 -0
  183. package/src/commands/performance-optimizer.ts +864 -0
  184. package/src/commands/plugins.ts +848 -0
  185. package/src/commands/setup.ts +508 -0
  186. package/src/commands/test-init.ts +242 -0
  187. package/src/commands/test.ts +264 -0
  188. package/src/commands/watch.ts +755 -0
  189. package/src/context/context-manager.ts +546 -0
  190. package/src/context/index.ts +9 -0
  191. package/src/context/session-manager.ts +1019 -0
  192. package/src/index.ts +64 -0
  193. package/src/interactive/interactive-mode.ts +830 -0
  194. package/src/nlp/command-mapper.ts +885 -0
  195. package/src/nlp/command-parser.ts +564 -0
  196. package/src/nlp/index.ts +4 -0
  197. package/src/nlp/intent-classifier.ts +458 -0
  198. package/src/nlp/intent-parser.ts +1101 -0
  199. package/src/plugins/plugin-manager.ts +744 -0
  200. package/src/types/index.ts +252 -0
  201. package/src/types/modules.d.ts +56 -0
  202. package/src/utils/config-manager.ts +391 -0
  203. package/src/utils/error-handler.ts +192 -0
  204. package/src/utils/logger.ts +104 -0
  205. package/templates/batch/ci-cd.yaml +62 -0
  206. package/templates/component/{{fileName}}.test.tsx +17 -0
  207. package/templates/component/{{fileName}}.tsx +21 -0
  208. package/templates/service/{{fileName}}.ts +98 -0
  209. package/templates/wundr-test.config.js +0 -0
  210. package/test-suites/api/health.spec.ts +134 -0
  211. package/test-suites/helpers/test-config.ts +84 -0
  212. package/test-suites/ui/accessibility.spec.ts +102 -0
  213. package/test-suites/ui/smoke.spec.ts +92 -0
@@ -0,0 +1,252 @@
1
+ import { Command } from 'commander';
2
+
3
+ /**
4
+ * Core types for the Wundr CLI
5
+ */
6
+
7
+ export interface WundrConfig {
8
+ version: string;
9
+ defaultMode: 'cli' | 'interactive' | 'chat' | 'tui';
10
+ plugins: string[];
11
+ integrations: {
12
+ github?: GitHubIntegration;
13
+ slack?: SlackIntegration;
14
+ jira?: JiraIntegration;
15
+ };
16
+ ai: {
17
+ provider: string;
18
+ model: string;
19
+ apiKey?: string;
20
+ };
21
+ analysis: {
22
+ patterns: string[];
23
+ excludes: string[];
24
+ maxDepth: number;
25
+ };
26
+ governance: {
27
+ rules: string[];
28
+ severity: 'error' | 'warning' | 'info';
29
+ };
30
+ }
31
+
32
+ export interface GitHubIntegration {
33
+ token: string;
34
+ owner: string;
35
+ repo: string;
36
+ webhooks?: boolean;
37
+ }
38
+
39
+ export interface SlackIntegration {
40
+ token: string;
41
+ channel: string;
42
+ webhooks?: boolean;
43
+ }
44
+
45
+ export interface JiraIntegration {
46
+ url: string;
47
+ email: string;
48
+ token: string;
49
+ }
50
+
51
+ export interface CommandContext {
52
+ config: WundrConfig;
53
+ logger: Logger;
54
+ spinner: Spinner;
55
+ interactive: boolean;
56
+ dryRun: boolean;
57
+ }
58
+
59
+ export interface Logger {
60
+ debug(message: string, ...args: any[]): void;
61
+ info(message: string, ...args: any[]): void;
62
+ warn(message: string, ...args: any[]): void;
63
+ error(message: string, ...args: any[]): void;
64
+ success(message: string, ...args: any[]): void;
65
+ setLevel(level: 'debug' | 'info' | 'warn' | 'error'): void;
66
+ }
67
+
68
+ export interface Spinner {
69
+ start(text?: string): void;
70
+ succeed(text?: string): void;
71
+ fail(text?: string): void;
72
+ warn(text?: string): void;
73
+ info(text?: string): void;
74
+ stop(): void;
75
+ text: string;
76
+ }
77
+
78
+ export interface Plugin {
79
+ name: string;
80
+ version: string;
81
+ description: string;
82
+ commands?: PluginCommand[];
83
+ hooks?: PluginHook[];
84
+ activate(context: PluginContext): Promise<void>;
85
+ deactivate(): Promise<void>;
86
+ }
87
+
88
+ export interface PluginCommand {
89
+ name: string;
90
+ description: string;
91
+ options?: CommandOption[];
92
+ action: (args: any[], options: any, context: CommandContext) => Promise<void>;
93
+ }
94
+
95
+ export interface PluginHook {
96
+ event: string;
97
+ handler: (data: any, context: CommandContext) => Promise<void>;
98
+ }
99
+
100
+ export interface CommandOption {
101
+ flags: string;
102
+ description: string;
103
+ defaultValue?: any;
104
+ }
105
+
106
+ export interface PluginContext {
107
+ config: WundrConfig;
108
+ logger: Logger;
109
+ registerCommand(command: PluginCommand): void;
110
+ registerHook(hook: PluginHook): void;
111
+ }
112
+
113
+ export interface BatchJob {
114
+ name: string;
115
+ description?: string;
116
+ commands: BatchCommand[];
117
+ parallel?: boolean;
118
+ continueOnError?: boolean;
119
+ timeout?: number;
120
+ }
121
+
122
+ export interface BatchCommand {
123
+ command: string;
124
+ args?: string[];
125
+ options?: Record<string, any>;
126
+ condition?: string;
127
+ retry?: number;
128
+ timeout?: number;
129
+ }
130
+
131
+ export interface WatchConfig {
132
+ patterns: string[];
133
+ ignore?: string[];
134
+ commands: WatchCommand[];
135
+ debounce?: number;
136
+ recursive?: boolean;
137
+ }
138
+
139
+ export interface WatchCommand {
140
+ trigger: 'change' | 'add' | 'delete' | 'rename';
141
+ command: string;
142
+ args?: string[];
143
+ condition?: string;
144
+ }
145
+
146
+ export interface TUILayout {
147
+ name: string;
148
+ widgets: TUIWidget[];
149
+ keybindings?: Keybinding[];
150
+ }
151
+
152
+ export interface TUIWidget {
153
+ type: 'log' | 'chart' | 'table' | 'progress' | 'text';
154
+ position: {
155
+ top: string | number;
156
+ left: string | number;
157
+ width: string | number;
158
+ height: string | number;
159
+ };
160
+ options: Record<string, any>;
161
+ dataSource?: string;
162
+ }
163
+
164
+ export interface Keybinding {
165
+ key: string;
166
+ action: string;
167
+ description: string;
168
+ }
169
+
170
+ export interface ChatSession {
171
+ id: string;
172
+ model: string;
173
+ context?: string;
174
+ history: ChatMessage[];
175
+ created: Date;
176
+ updated: Date;
177
+ }
178
+
179
+ export interface ChatMessage {
180
+ role: 'user' | 'assistant' | 'system';
181
+ content: string;
182
+ timestamp: Date;
183
+ metadata?: Record<string, any>;
184
+ }
185
+
186
+ export interface AnalysisResult {
187
+ type: 'dependency' | 'quality' | 'security' | 'performance';
188
+ findings: Finding[];
189
+ metrics: Record<string, number>;
190
+ recommendations: Recommendation[];
191
+ timestamp: Date;
192
+ }
193
+
194
+ export interface Finding {
195
+ id: string;
196
+ severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
197
+ title: string;
198
+ description: string;
199
+ file: string;
200
+ line?: number;
201
+ column?: number;
202
+ rule: string;
203
+ fixable: boolean;
204
+ fix?: string;
205
+ }
206
+
207
+ export interface Recommendation {
208
+ id: string;
209
+ title: string;
210
+ description: string;
211
+ impact: 'high' | 'medium' | 'low';
212
+ effort: 'low' | 'medium' | 'high';
213
+ category: string;
214
+ actions: RecommendationAction[];
215
+ }
216
+
217
+ export interface RecommendationAction {
218
+ type: 'command' | 'file_change' | 'config_change';
219
+ description: string;
220
+ command?: string;
221
+ file?: string;
222
+ changes?: any;
223
+ }
224
+
225
+ /**
226
+ * Base command interface for all command categories
227
+ */
228
+ export interface BaseCommand {
229
+ program: Command;
230
+ config: WundrConfig;
231
+ logger: Logger;
232
+ registerCommands(): void;
233
+ }
234
+
235
+ /**
236
+ * Interactive mode types
237
+ */
238
+ export interface InteractiveSession {
239
+ mode: 'wizard' | 'chat' | 'tui' | 'watch';
240
+ config: any;
241
+ state: Record<string, any>;
242
+ active: boolean;
243
+ }
244
+
245
+ /**
246
+ * Error handling types
247
+ */
248
+ export interface WundrError extends Error {
249
+ code?: string;
250
+ context?: Record<string, any>;
251
+ recoverable?: boolean;
252
+ }
@@ -0,0 +1,56 @@
1
+ // Type declarations for external modules
2
+ declare module '@wundr/computer-setup' {
3
+ export class ComputerSetupManager {
4
+ static getInstance(): ComputerSetupManager;
5
+ initialize(): Promise<void>;
6
+ validateSetup(profile?: any): Promise<boolean>;
7
+ cleanup(): Promise<void>;
8
+ getProfile(name: string): Promise<any>;
9
+ getDefaultProfile(): Promise<any>;
10
+ getAvailableProfiles(): Promise<any[]>;
11
+ setup(profile: any): Promise<{
12
+ success: boolean;
13
+ completedSteps?: any[];
14
+ skippedSteps?: any[];
15
+ failedSteps?: any[];
16
+ warnings?: string[];
17
+ errors?: string[];
18
+ report?: any;
19
+ }>;
20
+ on(event: string, callback: Function): void;
21
+ }
22
+ }
23
+
24
+ declare module '@wundr/core' {
25
+ export interface CoreConfig {
26
+ [key: string]: any;
27
+ }
28
+ export const defaultConfig: CoreConfig;
29
+ export function getLogger(name: string): any;
30
+ }
31
+
32
+ declare module '@wundr/project-templates' {
33
+ export interface TemplateConfig {
34
+ name: string;
35
+ description: string;
36
+ files: Array<{ path: string; content: string }>;
37
+ [key: string]: any;
38
+ }
39
+ export function getTemplate(name: string): TemplateConfig | null;
40
+ export function listTemplates(): string[];
41
+ export const projectTemplates: Record<string, any>;
42
+ }
43
+
44
+ declare module 'open' {
45
+ interface Options {
46
+ app?: string | string[];
47
+ wait?: boolean;
48
+ background?: boolean;
49
+ url?: boolean;
50
+ }
51
+ function open(
52
+ target: string,
53
+ options?: Options
54
+ ): Promise<import('child_process').ChildProcess>;
55
+ export = open;
56
+ }
@@ -0,0 +1,391 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { z } from 'zod';
5
+ import { WundrConfig } from '../types';
6
+ import { logger } from './logger';
7
+ import { errorHandler } from './error-handler';
8
+
9
+ // Zod schema for configuration validation
10
+ const WundrConfigSchema = z.object({
11
+ version: z.string(),
12
+ defaultMode: z.enum(['cli', 'interactive', 'chat', 'tui']).default('cli'),
13
+ plugins: z.array(z.string()).default([]),
14
+ integrations: z
15
+ .object({
16
+ github: z
17
+ .object({
18
+ token: z.string(),
19
+ owner: z.string(),
20
+ repo: z.string(),
21
+ webhooks: z.boolean().optional(),
22
+ })
23
+ .optional(),
24
+ slack: z
25
+ .object({
26
+ token: z.string(),
27
+ channel: z.string(),
28
+ webhooks: z.boolean().optional(),
29
+ })
30
+ .optional(),
31
+ jira: z
32
+ .object({
33
+ url: z.string(),
34
+ email: z.string(),
35
+ token: z.string(),
36
+ })
37
+ .optional(),
38
+ })
39
+ .default({}),
40
+ ai: z.object({
41
+ provider: z.string().default('claude'),
42
+ model: z.string().default('claude-3'),
43
+ apiKey: z.string().optional(),
44
+ }),
45
+ analysis: z.object({
46
+ patterns: z
47
+ .array(z.string())
48
+ .default(['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx']),
49
+ excludes: z
50
+ .array(z.string())
51
+ .default(['**/node_modules/**', '**/dist/**', '**/build/**']),
52
+ maxDepth: z.number().default(10),
53
+ }),
54
+ governance: z.object({
55
+ rules: z.array(z.string()).default([]),
56
+ severity: z.enum(['error', 'warning', 'info']).default('warning'),
57
+ }),
58
+ });
59
+
60
+ /**
61
+ * Configuration management system
62
+ */
63
+ export class ConfigManager {
64
+ private config: WundrConfig | null = null;
65
+ private configPath: string;
66
+ private userConfigDir: string;
67
+
68
+ constructor() {
69
+ this.userConfigDir = path.join(os.homedir(), '.wundr');
70
+ this.configPath = path.join(this.userConfigDir, 'config.json');
71
+ }
72
+
73
+ /**
74
+ * Load configuration from file or create default
75
+ */
76
+ async loadConfig(customPath?: string): Promise<WundrConfig> {
77
+ const configFile = customPath || this.configPath;
78
+
79
+ try {
80
+ // Ensure config directory exists
81
+ await fs.ensureDir(path.dirname(configFile));
82
+
83
+ // Load existing config or create default
84
+ if (await fs.pathExists(configFile)) {
85
+ const rawConfig = await fs.readJson(configFile);
86
+ this.config = WundrConfigSchema.parse(rawConfig);
87
+ logger.debug(`Loaded config from: ${configFile}`);
88
+ } else {
89
+ this.config = this.createDefaultConfig();
90
+ await this.saveConfig();
91
+ logger.info('Created default configuration');
92
+ }
93
+
94
+ // Merge with environment variables
95
+ this.config = this.mergeEnvironmentVariables(this.config);
96
+
97
+ return this.config;
98
+ } catch (error) {
99
+ logger.error('Failed to load configuration:', error);
100
+ throw errorHandler.createError(
101
+ 'WUNDR_CONFIG_INVALID',
102
+ 'Failed to load or parse configuration file',
103
+ { configPath: configFile },
104
+ true
105
+ );
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Save current configuration to file
111
+ */
112
+ async saveConfig(customPath?: string): Promise<void> {
113
+ if (!this.config) {
114
+ throw new Error('No configuration to save');
115
+ }
116
+
117
+ const configFile = customPath || this.configPath;
118
+
119
+ try {
120
+ await fs.ensureDir(path.dirname(configFile));
121
+ await fs.writeJson(configFile, this.config, { spaces: 2 });
122
+ logger.debug(`Saved config to: ${configFile}`);
123
+ } catch (error) {
124
+ logger.error('Failed to save configuration:', error);
125
+ throw errorHandler.createError(
126
+ 'WUNDR_CONFIG_INVALID',
127
+ 'Failed to save configuration file',
128
+ { configPath: configFile },
129
+ false
130
+ );
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get current configuration
136
+ */
137
+ getConfig(): WundrConfig {
138
+ if (!this.config) {
139
+ throw new Error('Configuration not loaded. Call loadConfig() first.');
140
+ }
141
+ return this.config;
142
+ }
143
+
144
+ /**
145
+ * Update configuration values
146
+ */
147
+ updateConfig(updates: Partial<WundrConfig>): void {
148
+ if (!this.config) {
149
+ throw new Error('Configuration not loaded. Call loadConfig() first.');
150
+ }
151
+
152
+ this.config = { ...this.config, ...updates };
153
+ }
154
+
155
+ /**
156
+ * Validate configuration against schema
157
+ */
158
+ validateConfig(config?: any): { valid: boolean; errors: string[] } {
159
+ const configToValidate = config || this.config;
160
+
161
+ try {
162
+ WundrConfigSchema.parse(configToValidate);
163
+ return { valid: true, errors: [] };
164
+ } catch (error) {
165
+ if (error instanceof z.ZodError) {
166
+ return {
167
+ valid: false,
168
+ errors: error.issues.map(
169
+ issue => `${issue.path.join('.')}: ${issue.message}`
170
+ ),
171
+ };
172
+ }
173
+ return { valid: false, errors: [error.message] };
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Get configuration value by path
179
+ */
180
+ get<T>(path: string, defaultValue?: T): T | undefined {
181
+ if (!this.config) {
182
+ return defaultValue;
183
+ }
184
+
185
+ const keys = path.split('.');
186
+ let value: any = this.config;
187
+
188
+ for (const key of keys) {
189
+ if (value && typeof value === 'object' && key in value) {
190
+ value = value[key];
191
+ } else {
192
+ return defaultValue;
193
+ }
194
+ }
195
+
196
+ return value;
197
+ }
198
+
199
+ /**
200
+ * Set configuration value by path
201
+ */
202
+ set(path: string, value: any): void {
203
+ if (!this.config) {
204
+ throw new Error('Configuration not loaded. Call loadConfig() first.');
205
+ }
206
+
207
+ const keys = path.split('.');
208
+ let current: any = this.config;
209
+
210
+ for (let i = 0; i < keys.length - 1; i++) {
211
+ const key = keys[i];
212
+ if (!key) {
213
+ throw new Error(`Invalid path: empty key at position ${i}`);
214
+ }
215
+ if (!current || typeof current !== 'object') {
216
+ throw new Error(`Invalid path: cannot set property on non-object`);
217
+ }
218
+ if (!current[key] || typeof current[key] !== 'object') {
219
+ current[key] = {};
220
+ }
221
+ current = current[key];
222
+ }
223
+
224
+ const finalKey = keys[keys.length - 1];
225
+ if (!finalKey) {
226
+ throw new Error('Invalid path: empty final key');
227
+ }
228
+ if (!finalKey || !current || typeof current !== 'object') {
229
+ throw new Error(`Invalid path: cannot set property`);
230
+ }
231
+ current[finalKey] = value;
232
+ }
233
+
234
+ /**
235
+ * Create default configuration
236
+ */
237
+ private createDefaultConfig(): WundrConfig {
238
+ return {
239
+ version: '1.0.0',
240
+ defaultMode: 'cli',
241
+ plugins: [],
242
+ integrations: {},
243
+ ai: {
244
+ provider: 'claude',
245
+ model: 'claude-3-opus-20240229',
246
+ },
247
+ analysis: {
248
+ patterns: ['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx'],
249
+ excludes: ['**/node_modules/**', '**/dist/**', '**/build/**'],
250
+ maxDepth: 10,
251
+ },
252
+ governance: {
253
+ rules: [],
254
+ severity: 'warning',
255
+ },
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Merge environment variables into configuration
261
+ */
262
+ private mergeEnvironmentVariables(config: WundrConfig): WundrConfig {
263
+ const envConfig = { ...config };
264
+
265
+ // AI API Key from environment
266
+ if (process.env['CLAUDE_API_KEY'] && !envConfig.ai.apiKey) {
267
+ envConfig.ai.apiKey = process.env['CLAUDE_API_KEY'];
268
+ }
269
+
270
+ if (
271
+ process.env['OPENAI_API_KEY'] &&
272
+ envConfig.ai.provider === 'openai' &&
273
+ !envConfig.ai.apiKey
274
+ ) {
275
+ envConfig.ai.apiKey = process.env['OPENAI_API_KEY'];
276
+ }
277
+
278
+ // AI Provider and Model from environment
279
+ if (process.env['WUNDR_AI_PROVIDER']) {
280
+ envConfig.ai.provider = process.env['WUNDR_AI_PROVIDER'];
281
+ }
282
+
283
+ if (process.env['WUNDR_AI_MODEL']) {
284
+ envConfig.ai.model = process.env['WUNDR_AI_MODEL'];
285
+ }
286
+
287
+ // GitHub integration
288
+ if (process.env['GITHUB_TOKEN']) {
289
+ envConfig.integrations.github = envConfig.integrations.github || {
290
+ token: process.env['GITHUB_TOKEN'],
291
+ owner: process.env['GITHUB_OWNER'] || '',
292
+ repo: process.env['GITHUB_REPO'] || '',
293
+ };
294
+ if (!envConfig.integrations.github.token) {
295
+ envConfig.integrations.github.token = process.env['GITHUB_TOKEN'];
296
+ }
297
+ }
298
+
299
+ return envConfig;
300
+ }
301
+
302
+ /**
303
+ * Get API key for AI provider with fallback mechanisms
304
+ */
305
+ getAIApiKey(provider?: string): string | undefined {
306
+ const currentProvider = provider || this.config?.ai?.provider || 'claude';
307
+
308
+ // First check config
309
+ if (this.config?.ai?.apiKey) {
310
+ return this.config.ai.apiKey;
311
+ }
312
+
313
+ // Then check environment variables based on provider
314
+ switch (currentProvider.toLowerCase()) {
315
+ case 'claude':
316
+ return process.env['CLAUDE_API_KEY'];
317
+ case 'openai':
318
+ return process.env['OPENAI_API_KEY'];
319
+ default:
320
+ return process.env['CLAUDE_API_KEY']; // Default fallback
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Set API key securely in config
326
+ */
327
+ async setAIApiKey(apiKey: string, provider?: string): Promise<void> {
328
+ if (!this.config) {
329
+ await this.loadConfig();
330
+ }
331
+
332
+ if (provider && provider !== this.config!.ai.provider) {
333
+ this.config!.ai.provider = provider;
334
+ }
335
+
336
+ this.config!.ai.apiKey = apiKey;
337
+ await this.saveConfig();
338
+ }
339
+
340
+ /**
341
+ * Check if AI is properly configured
342
+ */
343
+ isAIConfigured(): boolean {
344
+ const apiKey = this.getAIApiKey();
345
+ const provider = this.config?.ai?.provider;
346
+
347
+ return !!(apiKey && provider);
348
+ }
349
+
350
+ /**
351
+ * Get config file paths
352
+ */
353
+ getConfigPaths(): { user: string; project: string } {
354
+ return {
355
+ user: this.configPath,
356
+ project: path.join(process.cwd(), 'wundr.config.json'),
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Merge project config with user config
362
+ */
363
+ async loadProjectConfig(): Promise<WundrConfig> {
364
+ const projectConfigPath = path.join(process.cwd(), 'wundr.config.json');
365
+
366
+ // Load user config first
367
+ await this.loadConfig();
368
+
369
+ // Merge with project config if it exists
370
+ if (await fs.pathExists(projectConfigPath)) {
371
+ try {
372
+ const projectConfig = await fs.readJson(projectConfigPath);
373
+ const validatedProjectConfig =
374
+ WundrConfigSchema.partial().parse(projectConfig);
375
+
376
+ this.config = {
377
+ ...this.config!,
378
+ ...validatedProjectConfig,
379
+ };
380
+
381
+ logger.debug(`Merged project config from: ${projectConfigPath}`);
382
+ } catch (error) {
383
+ const message =
384
+ error instanceof Error ? error.message : 'Unknown error';
385
+ logger.warn(`Failed to load project config: ${message}`);
386
+ }
387
+ }
388
+
389
+ return this.config!;
390
+ }
391
+ }