@vfarcic/dot-ai 0.5.1 → 0.7.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 (146) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/{src/cli.ts → dist/cli.js} +19 -26
  4. package/dist/core/claude.d.ts +42 -0
  5. package/dist/core/claude.d.ts.map +1 -0
  6. package/dist/core/claude.js +229 -0
  7. package/dist/core/deploy-operation.d.ts +38 -0
  8. package/dist/core/deploy-operation.d.ts.map +1 -0
  9. package/dist/core/deploy-operation.js +101 -0
  10. package/dist/core/discovery.d.ts +162 -0
  11. package/dist/core/discovery.d.ts.map +1 -0
  12. package/dist/core/discovery.js +758 -0
  13. package/dist/core/error-handling.d.ts +167 -0
  14. package/dist/core/error-handling.d.ts.map +1 -0
  15. package/dist/core/error-handling.js +399 -0
  16. package/dist/core/index.d.ts +42 -0
  17. package/dist/core/index.d.ts.map +1 -0
  18. package/dist/core/index.js +123 -0
  19. package/dist/core/kubernetes-utils.d.ts +38 -0
  20. package/dist/core/kubernetes-utils.d.ts.map +1 -0
  21. package/dist/core/kubernetes-utils.js +177 -0
  22. package/dist/core/memory.d.ts +45 -0
  23. package/dist/core/memory.d.ts.map +1 -0
  24. package/dist/core/memory.js +113 -0
  25. package/dist/core/schema.d.ts +187 -0
  26. package/dist/core/schema.d.ts.map +1 -0
  27. package/dist/core/schema.js +655 -0
  28. package/dist/core/session-utils.d.ts +29 -0
  29. package/dist/core/session-utils.d.ts.map +1 -0
  30. package/dist/core/session-utils.js +121 -0
  31. package/dist/core/workflow.d.ts +70 -0
  32. package/dist/core/workflow.d.ts.map +1 -0
  33. package/dist/core/workflow.js +161 -0
  34. package/dist/index.d.ts +15 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +32 -0
  37. package/dist/interfaces/cli.d.ts +74 -0
  38. package/dist/interfaces/cli.d.ts.map +1 -0
  39. package/dist/interfaces/cli.js +769 -0
  40. package/dist/interfaces/mcp.d.ts +30 -0
  41. package/dist/interfaces/mcp.d.ts.map +1 -0
  42. package/dist/interfaces/mcp.js +105 -0
  43. package/dist/mcp/server.d.ts +9 -0
  44. package/dist/mcp/server.d.ts.map +1 -0
  45. package/dist/mcp/server.js +151 -0
  46. package/dist/tools/answer-question.d.ts +27 -0
  47. package/dist/tools/answer-question.d.ts.map +1 -0
  48. package/dist/tools/answer-question.js +696 -0
  49. package/dist/tools/choose-solution.d.ts +23 -0
  50. package/dist/tools/choose-solution.d.ts.map +1 -0
  51. package/dist/tools/choose-solution.js +171 -0
  52. package/dist/tools/deploy-manifests.d.ts +25 -0
  53. package/dist/tools/deploy-manifests.d.ts.map +1 -0
  54. package/dist/tools/deploy-manifests.js +74 -0
  55. package/dist/tools/generate-manifests.d.ts +23 -0
  56. package/dist/tools/generate-manifests.d.ts.map +1 -0
  57. package/dist/tools/generate-manifests.js +424 -0
  58. package/dist/tools/index.d.ts +11 -0
  59. package/dist/tools/index.d.ts.map +1 -0
  60. package/dist/tools/index.js +34 -0
  61. package/dist/tools/recommend.d.ts +23 -0
  62. package/dist/tools/recommend.d.ts.map +1 -0
  63. package/dist/tools/recommend.js +332 -0
  64. package/package.json +124 -2
  65. package/.claude/commands/context-load.md +0 -11
  66. package/.claude/commands/context-save.md +0 -16
  67. package/.claude/commands/prd-done.md +0 -115
  68. package/.claude/commands/prd-get.md +0 -25
  69. package/.claude/commands/prd-start.md +0 -87
  70. package/.claude/commands/task-done.md +0 -77
  71. package/.claude/commands/tests-reminder.md +0 -32
  72. package/.claude/settings.local.json +0 -20
  73. package/.eslintrc.json +0 -25
  74. package/.github/workflows/ci.yml +0 -170
  75. package/.prettierrc.json +0 -10
  76. package/.teller.yml +0 -8
  77. package/CLAUDE.md +0 -162
  78. package/assets/images/logo.png +0 -0
  79. package/bin/dot-ai.ts +0 -47
  80. package/bin.js +0 -19
  81. package/destroy.sh +0 -45
  82. package/devbox.json +0 -13
  83. package/devbox.lock +0 -225
  84. package/docs/API.md +0 -449
  85. package/docs/CONTEXT.md +0 -49
  86. package/docs/DEVELOPMENT.md +0 -203
  87. package/docs/NEXT_STEPS.md +0 -97
  88. package/docs/STAGE_BASED_API.md +0 -97
  89. package/docs/cli-guide.md +0 -798
  90. package/docs/design.md +0 -750
  91. package/docs/discovery-engine.md +0 -515
  92. package/docs/error-handling.md +0 -429
  93. package/docs/function-registration.md +0 -157
  94. package/docs/mcp-guide.md +0 -416
  95. package/renovate.json +0 -51
  96. package/setup.sh +0 -111
  97. package/src/core/claude.ts +0 -280
  98. package/src/core/deploy-operation.ts +0 -127
  99. package/src/core/discovery.ts +0 -900
  100. package/src/core/error-handling.ts +0 -562
  101. package/src/core/index.ts +0 -143
  102. package/src/core/kubernetes-utils.ts +0 -218
  103. package/src/core/memory.ts +0 -148
  104. package/src/core/schema.ts +0 -830
  105. package/src/core/session-utils.ts +0 -97
  106. package/src/core/workflow.ts +0 -234
  107. package/src/index.ts +0 -18
  108. package/src/interfaces/cli.ts +0 -872
  109. package/src/interfaces/mcp.ts +0 -183
  110. package/src/mcp/server.ts +0 -131
  111. package/src/tools/answer-question.ts +0 -807
  112. package/src/tools/choose-solution.ts +0 -169
  113. package/src/tools/deploy-manifests.ts +0 -94
  114. package/src/tools/generate-manifests.ts +0 -502
  115. package/src/tools/index.ts +0 -41
  116. package/src/tools/recommend.ts +0 -370
  117. package/tests/__mocks__/@kubernetes/client-node.ts +0 -106
  118. package/tests/build-system.test.ts +0 -345
  119. package/tests/configuration.test.ts +0 -226
  120. package/tests/core/deploy-operation.test.ts +0 -38
  121. package/tests/core/discovery.test.ts +0 -1648
  122. package/tests/core/error-handling.test.ts +0 -632
  123. package/tests/core/schema.test.ts +0 -1658
  124. package/tests/core/session-utils.test.ts +0 -245
  125. package/tests/core.test.ts +0 -439
  126. package/tests/fixtures/configmap-no-labels.yaml +0 -8
  127. package/tests/fixtures/crossplane-app-configuration.yaml +0 -6
  128. package/tests/fixtures/crossplane-providers.yaml +0 -45
  129. package/tests/fixtures/crossplane-rbac.yaml +0 -48
  130. package/tests/fixtures/invalid-configmap.yaml +0 -8
  131. package/tests/fixtures/invalid-deployment.yaml +0 -17
  132. package/tests/fixtures/test-deployment.yaml +0 -28
  133. package/tests/fixtures/valid-configmap.yaml +0 -15
  134. package/tests/infrastructure.test.ts +0 -426
  135. package/tests/interfaces/cli.test.ts +0 -1036
  136. package/tests/interfaces/mcp.test.ts +0 -139
  137. package/tests/kubernetes-utils.test.ts +0 -200
  138. package/tests/mcp/server.test.ts +0 -126
  139. package/tests/setup.ts +0 -31
  140. package/tests/tools/answer-question.test.ts +0 -367
  141. package/tests/tools/choose-solution.test.ts +0 -481
  142. package/tests/tools/deploy-manifests.test.ts +0 -185
  143. package/tests/tools/generate-manifests.test.ts +0 -441
  144. package/tests/tools/index.test.ts +0 -111
  145. package/tests/tools/recommend.test.ts +0 -180
  146. package/tsconfig.json +0 -34
@@ -1,872 +0,0 @@
1
- /**
2
- * CLI Interface Module
3
- *
4
- * Command-line interface for dot-ai
5
- */
6
-
7
- import { Command } from 'commander';
8
- import { DotAI } from '../core';
9
- import * as yaml from 'js-yaml';
10
- import Table from 'cli-table3';
11
- import { ResourceRecommender } from '../core/schema';
12
- import { handleRecommendTool } from '../tools/recommend';
13
- import { handleChooseSolutionTool } from '../tools/choose-solution';
14
- import { handleAnswerQuestionTool } from '../tools/answer-question';
15
- import { handleGenerateManifestsTool } from '../tools/generate-manifests';
16
- import { handleDeployManifestsTool } from '../tools/deploy-manifests';
17
- import { Logger, ConsoleLogger } from '../core/error-handling';
18
- import { readFileSync } from 'fs';
19
- import { join } from 'path';
20
-
21
- export interface CliResult {
22
- success: boolean;
23
- data?: any;
24
- error?: string;
25
- warnings?: string[];
26
- _rawFormat?: boolean;
27
- }
28
-
29
- export interface ParsedArguments {
30
- command: string;
31
- options: Record<string, any>;
32
- }
33
-
34
- export interface CliConfig {
35
- defaultOutput?: string;
36
- verboseMode?: boolean;
37
- outputFile?: string;
38
- quietMode?: boolean;
39
- }
40
-
41
- export class CliInterface {
42
- private dotAI?: DotAI;
43
- private program: Command;
44
- private config: CliConfig;
45
- private logger: Logger;
46
-
47
- constructor(dotAI?: DotAI, config: CliConfig = {}) {
48
- this.dotAI = dotAI;
49
- this.config = config;
50
- this.program = new Command();
51
- this.logger = new ConsoleLogger('CLI');
52
- this.program.name('dot-ai').description('AI-powered Kubernetes deployment agent');
53
-
54
- // Add global options that apply to all commands
55
- this.program
56
- .option('--kubeconfig <path>', 'Path to kubeconfig file (overrides KUBECONFIG env var and default location)')
57
- .option('--verbose', 'Enable verbose output globally')
58
- .option('--output-file <path>', 'Write clean formatted output to file (respects --output format)')
59
- .option('--quiet', 'Suppress tool registration and info logs');
60
-
61
- this.setupCommands();
62
- }
63
-
64
- setDotAI(dotAI: DotAI): void {
65
- this.dotAI = dotAI;
66
- }
67
-
68
- private ensureDotAI(): DotAI {
69
- if (!this.dotAI) {
70
- throw new Error('Cluster connection required. Please ensure your kubeconfig is valid and cluster is accessible.');
71
- }
72
- return this.dotAI;
73
- }
74
-
75
- private getPackageInfo(): { name: string; version: string } {
76
- try {
77
- const packagePath = join(__dirname, '../../package.json');
78
- const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
79
- return { name: packageJson.name, version: packageJson.version };
80
- } catch (error) {
81
- return { name: '@vfarcic/dot-ai', version: '0.1.0' }; // fallback
82
- }
83
- }
84
-
85
- private setupCommands(): void {
86
- const { name, version } = this.getPackageInfo();
87
-
88
- this.program
89
- .name('dot-ai')
90
- .description('Kubernetes application deployment agent with AI-powered orchestration')
91
- .version(version, '-v, --version', 'output the version number');
92
-
93
- // Add custom help with installation and usage examples
94
- this.program.addHelpText('after', `
95
-
96
- Installation:
97
- npm install -g ${name} Install globally
98
- npx ${name} <command> Run without installing
99
-
100
- Examples:
101
- npx ${name} recommend --intent "deploy my Node.js app"
102
- npx ${name} status --deployment my-app
103
- npx ${name} learn --pattern microservice
104
-
105
- For more help on specific commands, use:
106
- npx ${name} help <command>
107
- `);
108
-
109
-
110
-
111
- // Status command
112
- this.program
113
- .command('status')
114
- .description('Check deployment status')
115
- .option('--deployment <id>', 'Deployment/workflow ID to check')
116
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
117
- .action(async (options, command) => {
118
- // Get global options from parent command
119
- const globalOptions = command.parent?.opts() || {};
120
- this.processGlobalOptions(globalOptions);
121
-
122
- // Validate output format
123
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
124
- console.error('Error: Invalid output format. Supported: json, yaml, table');
125
- process.exit(1);
126
- }
127
- const result = await this.executeCommand('status', options);
128
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
129
- });
130
-
131
- // Learn command
132
- this.program
133
- .command('learn')
134
- .description('Show learned deployment patterns and recommendations')
135
- .option('--pattern <type>', 'Filter by pattern type')
136
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
137
- .action(async (options, command) => {
138
- // Get global options from parent command
139
- const globalOptions = command.parent?.opts() || {};
140
- this.processGlobalOptions(globalOptions);
141
-
142
- // Validate output format
143
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
144
- console.error('Error: Invalid output format. Supported: json, yaml, table');
145
- process.exit(1);
146
- }
147
- const result = await this.executeCommand('learn', options);
148
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
149
- });
150
-
151
- // Recommend command
152
- this.program
153
- .command('recommend')
154
- .description('Get AI-powered Kubernetes resource recommendations based on your intent')
155
- .requiredOption('--intent <description>', 'Describe what you want to deploy or accomplish')
156
- .option('--session-dir <path>', 'Directory to store solution files (defaults to DOT_AI_SESSION_DIR env var)')
157
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
158
- .action(async (options, command) => {
159
- // Get global options from parent command
160
- const globalOptions = command.parent?.opts() || {};
161
- this.processGlobalOptions(globalOptions);
162
-
163
- // Validate output format
164
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
165
- console.error('Error: Invalid output format. Supported: json, yaml, table');
166
- process.exit(1);
167
- }
168
- const result = await this.executeCommand('recommend', options);
169
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
170
- });
171
-
172
- // Choose Solution command
173
- this.program
174
- .command('choose-solution')
175
- .description('Select a solution by ID and return its questions for configuration')
176
- .requiredOption('--solution-id <id>', 'Solution ID to choose (e.g., sol_2025-07-01T154349_1e1e242592ff)')
177
- .requiredOption('--session-dir <path>', 'Directory containing solution files')
178
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
179
- .action(async (options, command) => {
180
- // Get global options from parent command
181
- const globalOptions = command.parent?.opts() || {};
182
- this.processGlobalOptions(globalOptions);
183
-
184
- // Validate output format
185
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
186
- console.error('Error: Invalid output format. Supported: json, yaml, table');
187
- process.exit(1);
188
- }
189
- const result = await this.executeCommand('chooseSolution', options);
190
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
191
- });
192
-
193
- // Answer Question command
194
- this.program
195
- .command('answer-question')
196
- .description('Process user answers and return remaining questions or completion status')
197
- .requiredOption('--solution-id <id>', 'Solution ID to update (e.g., sol_2025-07-01T154349_1e1e242592ff)')
198
- .requiredOption('--session-dir <path>', 'Directory containing solution files')
199
- .requiredOption('--answers <json>', 'User answers as JSON object')
200
- .requiredOption('--stage <stage>', 'Configuration stage (required, basic, advanced, open)')
201
- .option('--done', 'Set when providing final open question answer', false)
202
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
203
- .action(async (options, command) => {
204
- // Get global options from parent command
205
- const globalOptions = command.parent?.opts() || {};
206
- this.processGlobalOptions(globalOptions);
207
-
208
- // Parse answers JSON
209
- try {
210
- options.answers = JSON.parse(options.answers);
211
- } catch (error) {
212
- console.error('Error: Invalid JSON in --answers parameter');
213
- process.exit(1);
214
- }
215
-
216
- // Validate output format
217
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
218
- console.error('Error: Invalid output format. Supported: json, yaml, table');
219
- process.exit(1);
220
- }
221
- const result = await this.executeCommand('answerQuestion', options);
222
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
223
- });
224
-
225
- // Generate Manifests command
226
- this.program
227
- .command('generate-manifests')
228
- .description('Generate Kubernetes manifests from solution configuration using AI')
229
- .requiredOption('--solution-id <id>', 'Solution ID to generate manifests for (e.g., sol_2025-07-01T154349_1e1e242592ff)')
230
- .requiredOption('--session-dir <path>', 'Directory containing solution files')
231
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
232
- .action(async (options, command) => {
233
- // Get global options from parent command
234
- const globalOptions = command.parent?.opts() || {};
235
- this.processGlobalOptions(globalOptions);
236
-
237
- // Validate output format
238
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
239
- console.error('Error: Invalid output format. Supported: json, yaml, table');
240
- process.exit(1);
241
- }
242
- const result = await this.executeCommand('generateManifests', options);
243
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
244
- });
245
-
246
- // Deploy Manifests command
247
- this.program
248
- .command('deploy-manifests')
249
- .description('Deploy Kubernetes manifests from generated solution')
250
- .requiredOption('--solution-id <id>', 'Solution ID to deploy')
251
- .requiredOption('--session-dir <path>', 'Session directory path')
252
- .option('--timeout <seconds>', 'Deployment timeout in seconds', '30')
253
- .option('--output <format>', 'Output format (json|yaml|table)', 'json')
254
- .action(async (options, command) => {
255
- // Get global options from parent command
256
- const globalOptions = command.parent?.opts() || {};
257
- this.processGlobalOptions(globalOptions);
258
-
259
- // Validate output format
260
- if (options.output && !['json', 'yaml', 'table'].includes(options.output)) {
261
- console.error('Error: Invalid output format. Supported: json, yaml, table');
262
- process.exit(1);
263
- }
264
- const result = await this.executeCommand('deployManifests', options);
265
- this.outputResult(result, options.output || this.config.defaultOutput || 'json', this.config.outputFile);
266
- });
267
-
268
- // REMOVED: enhance command - moved to legacy reference
269
- // See src/legacy/tools/enhance-solution.ts for reference implementation
270
- }
271
-
272
- getCommands(): string[] {
273
- return ['dot-ai'];
274
- }
275
-
276
- getSubcommands(): string[] {
277
- return this.program.commands.map(cmd => cmd.name());
278
- }
279
-
280
- async getHelp(): Promise<string> {
281
- const { name } = this.getPackageInfo();
282
- const basicHelp = this.program.helpInformation();
283
- const customHelp = `
284
-
285
- Installation:
286
- npm install -g ${name} Install globally
287
- npx ${name} <command> Run without installing
288
-
289
- Examples:
290
- npx ${name} recommend --intent "deploy my Node.js app"
291
- npx ${name} status --deployment my-app
292
- npx ${name} learn --pattern microservice
293
-
294
- For more help on specific commands, use:
295
- npx ${name} help <command>
296
- `;
297
- return basicHelp + customHelp;
298
- }
299
-
300
- async getCommandHelp(commandName: string): Promise<string> {
301
- const command = this.program.commands.find(cmd => cmd.name() === commandName);
302
- if (!command) {
303
- throw new Error(`Unknown command: ${commandName}`);
304
- }
305
- return command.helpInformation();
306
- }
307
-
308
- async parseArguments(args: string[]): Promise<ParsedArguments> {
309
- if (args.length === 0) {
310
- throw new Error('No command provided');
311
- }
312
-
313
- const commandName = args[0];
314
- const validCommands = this.getSubcommands();
315
-
316
- if (!validCommands.includes(commandName)) {
317
- throw new Error(`Unknown command: ${commandName}`);
318
- }
319
-
320
- // Validate options based on command
321
- const options: Record<string, any> = {};
322
-
323
- for (let i = 1; i < args.length; i += 2) {
324
- const option = args[i];
325
- const value = args[i + 1];
326
-
327
- if (!option.startsWith('--')) {
328
- throw new Error(`Invalid option format: ${option}`);
329
- }
330
-
331
- const optionName = option.substring(2);
332
-
333
- // Validate known options for each command
334
- const validOptions = this.getValidOptionsForCommand(commandName);
335
- if (!validOptions.includes(optionName)) {
336
- throw new Error(`Unknown option: ${option}`);
337
- }
338
-
339
- // Validate specific option values
340
- if (optionName === 'output' && value && !['json', 'yaml', 'table'].includes(value)) {
341
- throw new Error('Invalid output format. Supported: json, yaml, table');
342
- }
343
-
344
- options[optionName] = value || true;
345
- }
346
-
347
- // Check required options
348
- // (no required option checks needed for current commands)
349
-
350
- return {
351
- command: commandName,
352
- options
353
- };
354
- }
355
-
356
- private getValidOptionsForCommand(command: string): string[] {
357
- const commonOptions = ['output', 'verbose'];
358
-
359
- switch (command) {
360
- case 'discover':
361
- return [...commonOptions, 'cluster', 'remember'];
362
- case 'status':
363
- return [...commonOptions, 'deployment'];
364
- case 'learn':
365
- return [...commonOptions, 'pattern'];
366
- case 'recommend':
367
- return [...commonOptions, 'intent', 'session-dir'];
368
- case 'chooseSolution':
369
- return [...commonOptions, 'solution-id', 'session-dir'];
370
- case 'answerQuestion':
371
- return [...commonOptions, 'solution-id', 'session-dir', 'stage', 'answers', 'done'];
372
- case 'generateManifests':
373
- return [...commonOptions, 'solution-id', 'session-dir'];
374
- case 'deployManifests':
375
- return [...commonOptions, 'solution-id', 'session-dir', 'timeout'];
376
- // REMOVED: enhance command, deploy command
377
- default:
378
- return commonOptions;
379
- }
380
- }
381
-
382
- async executeCommand(command: string, options: Record<string, any> = {}): Promise<CliResult> {
383
- try {
384
- // Only initialize DotAI for commands that need cluster access
385
- if (command !== 'chooseSolution' && command !== 'answerQuestion' && command !== 'generateManifests') {
386
- await this.ensureDotAI().initialize();
387
- }
388
-
389
- switch (command) {
390
- case 'status':
391
- return await this.handleStatusCommand(options);
392
- case 'learn':
393
- return await this.handleLearnCommand(options);
394
- case 'recommend':
395
- return await this.handleRecommendCommand(options);
396
- case 'chooseSolution':
397
- return await this.handleChooseSolutionCommand(options);
398
- case 'answerQuestion':
399
- return await this.handleAnswerQuestionCommand(options);
400
- case 'generateManifests':
401
- return await this.handleGenerateManifestsCommand(options);
402
- case 'deployManifests':
403
- return await this.handleDeployManifestsCommand(options);
404
- // REMOVED: enhance command, deploy command
405
- default:
406
- return {
407
- success: false,
408
- error: `Unknown command: ${command}`
409
- };
410
- }
411
- } catch (error) {
412
- return this.handleError(error, command);
413
- }
414
- }
415
-
416
-
417
-
418
- private async handleStatusCommand(options: Record<string, any>): Promise<CliResult> {
419
- try {
420
- const phase = this.ensureDotAI().workflow.getCurrentPhase();
421
-
422
- return {
423
- success: true,
424
- data: {
425
- workflowId: options.deployment,
426
- phase,
427
- status: 'active'
428
- }
429
- };
430
- } catch (error) {
431
- return {
432
- success: false,
433
- error: `Status check failed: ${(error as Error).message}`
434
- };
435
- }
436
- }
437
-
438
- private async handleLearnCommand(options: Record<string, any>): Promise<CliResult> {
439
- try {
440
- const recommendations = await this.ensureDotAI().memory.getRecommendations(
441
- options.pattern || 'deployment',
442
- {}
443
- );
444
-
445
- return {
446
- success: true,
447
- data: {
448
- recommendations,
449
- patternType: options.pattern || 'deployment'
450
- }
451
- };
452
- } catch (error) {
453
- return {
454
- success: false,
455
- error: `Learning retrieval failed: ${(error as Error).message}`
456
- };
457
- }
458
- }
459
-
460
- private async handleRecommendCommand(options: Record<string, any>): Promise<CliResult> {
461
- try {
462
- // Show progress for long-running AI operations
463
- this.showProgress('🔍 Analyzing your intent and discovering cluster resources...');
464
-
465
-
466
- // Prepare arguments for the recommend tool including session directory
467
- const toolArgs = {
468
- intent: options.intent,
469
- sessionDir: options.sessionDir // This will be passed to our tool's getSessionDirectory function
470
- };
471
-
472
- this.showProgress('🤖 AI is analyzing resources and generating solutions...');
473
-
474
- // Execute the recommend tool directly
475
- const requestId = `cli_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
476
- const result = await handleRecommendTool(toolArgs, this.ensureDotAI(), this.logger, requestId);
477
-
478
- this.clearProgress();
479
-
480
- // Parse the tool result
481
- const responseData = JSON.parse(result.content[0].text);
482
-
483
- return {
484
- success: true,
485
- data: responseData
486
- };
487
- } catch (error) {
488
- // Give a moment for user to see progress before clearing
489
- await new Promise(resolve => setTimeout(resolve, 1000));
490
- this.clearProgress(); // Clear progress indicators on error
491
- return {
492
- success: false,
493
- error: `AI-powered recommendations failed: ${(error as Error).message}`
494
- };
495
- }
496
- }
497
-
498
- private async handleChooseSolutionCommand(options: Record<string, any>): Promise<CliResult> {
499
- try {
500
- // Show progress for file operations
501
- this.showProgress('📋 Loading solution and extracting questions...');
502
-
503
-
504
- // Prepare arguments for the chooseSolution tool
505
- const toolArgs = {
506
- solutionId: options.solutionId,
507
- sessionDir: options.sessionDir
508
- };
509
-
510
- // Execute the chooseSolution tool directly
511
- const requestId = `cli_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
512
- const result = await handleChooseSolutionTool(toolArgs, null as any, this.logger, requestId);
513
-
514
- this.clearProgress();
515
-
516
- // Parse the tool result
517
- const responseData = JSON.parse(result.content[0].text);
518
-
519
- return {
520
- success: true,
521
- data: responseData
522
- };
523
- } catch (error) {
524
- // Give a moment for user to see progress before clearing
525
- await new Promise(resolve => setTimeout(resolve, 1000));
526
- this.clearProgress(); // Clear progress indicators on error
527
- return {
528
- success: false,
529
- error: `Choose solution failed: ${(error as Error).message}`
530
- };
531
- }
532
- }
533
-
534
- private async handleAnswerQuestionCommand(options: Record<string, any>): Promise<CliResult> {
535
- try {
536
- // Show progress for file operations
537
- this.showProgress('📝 Processing answers and updating solution...');
538
-
539
-
540
- // Prepare arguments for the answerQuestion tool
541
- const toolArgs = {
542
- solutionId: options.solutionId,
543
- sessionDir: options.sessionDir,
544
- stage: options.stage,
545
- answers: options.answers,
546
- done: options.done || false
547
- };
548
-
549
- // Execute the answerQuestion tool directly
550
- const requestId = `cli_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
551
- const result = await handleAnswerQuestionTool(toolArgs, null as any, this.logger, requestId);
552
-
553
- this.clearProgress();
554
-
555
- // Parse the tool result
556
- const responseData = JSON.parse(result.content[0].text);
557
-
558
- return {
559
- success: true,
560
- data: responseData
561
- };
562
- } catch (error) {
563
- // Give a moment for user to see progress before clearing
564
- await new Promise(resolve => setTimeout(resolve, 1000));
565
- this.clearProgress(); // Clear progress indicators on error
566
- return {
567
- success: false,
568
- error: `Answer question failed: ${(error as Error).message}`
569
- };
570
- }
571
- }
572
-
573
- private async handleGenerateManifestsCommand(options: Record<string, any>): Promise<CliResult> {
574
- try {
575
- // Show progress for manifest generation
576
- this.showProgress('🤖 Generating Kubernetes manifests with AI...');
577
-
578
-
579
- // Prepare arguments for the generateManifests tool
580
- const toolArgs = {
581
- solutionId: options.solutionId,
582
- sessionDir: options.sessionDir
583
- };
584
-
585
- // Execute the generateManifests tool directly
586
- const requestId = `cli_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
587
- const result = await handleGenerateManifestsTool(toolArgs, this.dotAI!, this.logger, requestId);
588
-
589
- this.clearProgress();
590
-
591
- // Parse the tool result
592
- const responseData = JSON.parse(result.content[0].text);
593
-
594
- return {
595
- success: true,
596
- data: responseData
597
- };
598
- } catch (error) {
599
- // Give a moment for user to see progress before clearing
600
- await new Promise(resolve => setTimeout(resolve, 1000));
601
- this.clearProgress(); // Clear progress indicators on error
602
- return {
603
- success: false,
604
- error: `Manifest generation failed: ${(error as Error).message}`
605
- };
606
- }
607
- }
608
-
609
- private async handleDeployManifestsCommand(options: Record<string, any>): Promise<CliResult> {
610
- try {
611
- // Show progress for deployment
612
- this.showProgress('🚀 Deploying Kubernetes manifests...');
613
-
614
- // Prepare arguments for the deployManifests tool
615
- const toolArgs = {
616
- solutionId: options.solutionId,
617
- timeout: parseInt(options.timeout) || 30
618
- };
619
-
620
- // Execute the deployManifests tool directly
621
- const requestId = `cli_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
622
- const result = await handleDeployManifestsTool(toolArgs, this.ensureDotAI(), this.logger, requestId);
623
-
624
- this.clearProgress();
625
-
626
- // Parse the tool result
627
- const responseData = JSON.parse(result.content[0].text);
628
-
629
- return {
630
- success: true,
631
- data: responseData
632
- };
633
- } catch (error) {
634
- this.clearProgress(); // Clear progress indicators on error
635
- return {
636
- success: false,
637
- error: `Manifest deployment failed: ${(error as Error).message}`
638
- };
639
- }
640
- }
641
-
642
- // REMOVED: handleEnhanceCommand method - moved to legacy reference
643
- // See src/legacy/tools/enhance-solution.ts for reference implementation
644
-
645
- async continueWorkflow(workflowId: string, input: { responses: Record<string, any> }): Promise<CliResult> {
646
- try {
647
- await this.ensureDotAI().workflow.transitionTo('Validation');
648
-
649
- const claudeResponse = await this.ensureDotAI().claude.processUserInput(
650
- `Continue workflow ${workflowId} with responses: ${JSON.stringify(input.responses)}`
651
- );
652
-
653
- return {
654
- success: true,
655
- data: claudeResponse
656
- };
657
- } catch (error) {
658
- return {
659
- success: false,
660
- error: `Workflow continuation failed: ${(error as Error).message}`
661
- };
662
- }
663
- }
664
-
665
- formatOutput(result: CliResult, format: string): string {
666
- // Handle raw format for commands that need it
667
- const isRawFormat = (result as any)._rawFormat;
668
- if (isRawFormat) {
669
- switch (format) {
670
- case 'json':
671
- return JSON.stringify(result.data, null, 2);
672
- case 'yaml':
673
- return yaml.dump(result.data);
674
- case 'table':
675
- return this.formatAsTable({ success: true, data: result.data });
676
- default:
677
- return JSON.stringify(result.data, null, 2);
678
- }
679
- }
680
-
681
- // Standard CLI format for other commands
682
- switch (format) {
683
- case 'json':
684
- return JSON.stringify(result, null, 2);
685
-
686
- case 'yaml':
687
- return yaml.dump(result);
688
-
689
- case 'table':
690
- return this.formatAsTable(result);
691
-
692
- default:
693
- return JSON.stringify(result, null, 2);
694
- }
695
- }
696
-
697
- private formatAsTable(result: CliResult): string {
698
- if (!result.success) {
699
- const table = new Table({
700
- head: ['Status', 'Error'],
701
- colWidths: [20, 60]
702
- });
703
- table.push(['Failed', result.error || 'Unknown error']);
704
- return table.toString();
705
- }
706
-
707
- // Format data as table based on content
708
- if (result.data && (result.data.resources || result.data.crds)) {
709
- const table = new Table({
710
- head: ['Resource Type', 'Category'],
711
- colWidths: [40, 20]
712
- });
713
-
714
- // Add discovered resources
715
- if (result.data.resources && result.data.resources.resources) {
716
- result.data.resources.resources.forEach((resource: any) => {
717
- const category = resource.group === '' ? 'Core' : resource.group;
718
- table.push([resource.kind, category]);
719
- });
720
- }
721
-
722
- // Add custom resources (CRDs) - handle both string and object formats
723
- if (result.data.crds && Array.isArray(result.data.crds)) {
724
- result.data.crds.forEach((crd: any) => {
725
- const crdName = typeof crd === 'string' ? crd : crd.name;
726
- table.push([crdName, 'Custom']);
727
- });
728
- }
729
-
730
- return table.toString();
731
- }
732
-
733
- // Generic table format
734
- const table = new Table({
735
- head: ['Property', 'Value'],
736
- colWidths: [30, 50]
737
- });
738
-
739
- if (result.data) {
740
- Object.entries(result.data).forEach(([key, value]) => {
741
- table.push([key, JSON.stringify(value)]);
742
- });
743
- }
744
-
745
- return table.toString();
746
- }
747
-
748
- private outputResult(result: CliResult, format: string, outputFile?: string): void {
749
- const output = this.formatOutput(result, format);
750
-
751
- if (outputFile) {
752
- // Write clean output to file
753
- const fs = require('fs');
754
- const path = require('path');
755
-
756
- // Ensure directory exists
757
- const dir = path.dirname(outputFile);
758
- if (!fs.existsSync(dir)) {
759
- fs.mkdirSync(dir, { recursive: true });
760
- }
761
-
762
- fs.writeFileSync(outputFile, output);
763
- } else {
764
- // Write to stdout
765
- process.stdout.write(`${output}\n`);
766
- }
767
-
768
- if (!result.success) {
769
- process.exit(1);
770
- }
771
- }
772
-
773
- /**
774
- * Process global options and update config
775
- */
776
- private processGlobalOptions(options: Record<string, any>): void {
777
- if (options.verbose !== undefined) {
778
- this.config.verboseMode = options.verbose;
779
- }
780
- if (options.outputFile !== undefined) {
781
- this.config.outputFile = options.outputFile;
782
- }
783
- if (options.quiet !== undefined) {
784
- this.config.quietMode = options.quiet;
785
- }
786
- }
787
-
788
-
789
- private handleError(error: any, _command: string): CliResult {
790
- this.clearProgress(); // Clear any progress indicators on error
791
-
792
- let errorMessage = (error as Error).message;
793
-
794
- // Provide helpful error messages for common issues
795
- if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('connection')) {
796
- errorMessage = 'Cannot connect to Kubernetes cluster. Check your kubeconfig and cluster status.';
797
- } else if (errorMessage.includes('Connection failed')) {
798
- errorMessage = `Failed to initialize DevOps AI Toolkit: ${errorMessage}`;
799
- }
800
-
801
- return {
802
- success: false,
803
- error: errorMessage
804
- };
805
- }
806
-
807
- /**
808
- * Show progress message to user during long-running operations
809
- */
810
- private showProgress(message: string): void {
811
- // Only show progress if output is going to console (not when piped or in JSON mode)
812
- if (process.stdout.isTTY) {
813
- process.stderr.write(`\r\x1b[K${message}`);
814
- }
815
- }
816
-
817
- /**
818
- * Clear progress indicators
819
- */
820
- private clearProgress(): void {
821
- if (process.stdout.isTTY) {
822
- process.stderr.write('\r\x1b[K');
823
- }
824
- }
825
-
826
- /**
827
- * Find best solutions with detailed progress feedback
828
- */
829
- private async findBestSolutionsWithProgress(
830
- recommender: ResourceRecommender,
831
- intent: string,
832
- discoverResourcesFn: () => Promise<any>,
833
- explainResourceFn: (resource: string) => Promise<any>
834
- ): Promise<any[]> {
835
- this.showProgress('🤖 AI is analyzing your intent...');
836
-
837
- // Start a timer to show elapsed time
838
- const startTime = Date.now();
839
- const progressInterval = setInterval(() => {
840
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
841
- this.showProgress(`🤖 AI analysis in progress... (${elapsed}s)`);
842
- }, 3000);
843
-
844
- try {
845
- // The ResourceRecommender handles the three phases internally:
846
- // 1. Resource discovery and selection
847
- // 2. Schema fetching and ranking
848
- // 3. Question generation
849
- const solutions = await recommender.findBestSolutions(intent, discoverResourcesFn, explainResourceFn);
850
-
851
- clearInterval(progressInterval);
852
- return solutions;
853
- } catch (error) {
854
- clearInterval(progressInterval);
855
- throw error;
856
- }
857
- }
858
-
859
- // CLI entry point
860
- async run(args: string[] = process.argv): Promise<void> {
861
- try {
862
- await this.program.parseAsync(args);
863
- } catch (error) {
864
- const result = this.handleError(error, 'general');
865
- this.outputResult(result, 'json');
866
- process.exit(1);
867
- }
868
- }
869
- }
870
-
871
- // Export for CLI entry point
872
- export default CliInterface;