@vfarcic/dot-ai 0.82.0 → 0.83.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.
- package/README.md +1 -1
- package/dist/core/discovery.d.ts +24 -0
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +95 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/interfaces/mcp.d.ts +10 -0
- package/dist/interfaces/mcp.d.ts.map +1 -1
- package/dist/interfaces/mcp.js +103 -1
- package/dist/mcp/server.js +27 -10
- package/dist/tools/version.d.ts +1 -1
- package/dist/tools/version.d.ts.map +1 -1
- package/dist/tools/version.js +69 -92
- package/package.json +3 -3
- package/shared-prompts/prd-done.md +4 -3
package/README.md
CHANGED
|
@@ -342,4 +342,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
342
342
|
|
|
343
343
|
---
|
|
344
344
|
|
|
345
|
-
**DevOps AI Toolkit** - AI-powered development productivity platform for enhanced software development workflows.
|
|
345
|
+
**DevOps AI Toolkit** - AI-powered development productivity platform for enhanced software development workflows.
|
package/dist/core/discovery.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles cluster connection, resource discovery, and capability detection
|
|
5
5
|
*/
|
|
6
|
+
import * as k8s from '@kubernetes/client-node';
|
|
6
7
|
import { KubectlConfig } from './kubernetes-utils';
|
|
7
8
|
export interface ClusterInfo {
|
|
8
9
|
type: string;
|
|
@@ -103,6 +104,29 @@ export declare class KubernetesDiscovery {
|
|
|
103
104
|
* Set a new kubeconfig path (will require reconnection)
|
|
104
105
|
*/
|
|
105
106
|
setKubeconfigPath(newPath: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get connection status and configuration info for diagnostics
|
|
109
|
+
*/
|
|
110
|
+
getConnectionInfo(): {
|
|
111
|
+
connected: boolean;
|
|
112
|
+
kubeconfig: string;
|
|
113
|
+
mode: 'file' | 'in-cluster' | 'default';
|
|
114
|
+
server?: string;
|
|
115
|
+
context?: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Test connection to the cluster with detailed result
|
|
119
|
+
*/
|
|
120
|
+
testConnection(): Promise<{
|
|
121
|
+
connected: boolean;
|
|
122
|
+
version?: string;
|
|
123
|
+
error?: string;
|
|
124
|
+
errorType?: string;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Get the Kubernetes client for direct API access (used by other tools)
|
|
128
|
+
*/
|
|
129
|
+
getClient(): k8s.KubeConfig;
|
|
106
130
|
connect(): Promise<void>;
|
|
107
131
|
isConnected(): boolean;
|
|
108
132
|
getClusterInfo(): Promise<ClusterInfo>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/core/discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/core/discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAG/C,OAAO,EAEL,aAAa,EAEd,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAKD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,GAAG,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE;QACR,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,QAAQ,EAAE;QACR,WAAW,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,eAAe,EAAE,OAAO,CAAC;QACzB,oBAAoB,EAAE,MAAM,EAAE,CAAC;KAChC,CAAC;IACF,OAAO,EAAE;QACP,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,cAAc,CAAS;gBAEnB,MAAM,CAAC,EAAE,yBAAyB;IAK9C;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IAwB7B;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKxC;;OAEG;IACH,iBAAiB,IAAI;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;QACxC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB;IAuCD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAC9B,SAAS,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAgCF;;OAEG;IACH,SAAS,IAAI,GAAG,CAAC,UAAU;IAOrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC9B,WAAW,IAAI,OAAO;IAIhB,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAkB5C,OAAO,CAAC,iBAAiB;YAiCX,kBAAkB;IAuD1B,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC;IA+B/C;;OAEG;IACH;;;OAGG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAWvE,YAAY,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAkDlE,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAuD1E,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBhF,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC;YA2FzC,iBAAiB;YAwBjB,iBAAiB;YAwBjB,eAAe;YA8Bf,cAAc;IAwB5B,OAAO,CAAC,aAAa;IAWf,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAiBnE,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAalC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS1D;;OAEG;YACW,uBAAuB;IAsDrC;;OAEG;YACW,8BAA8B;IAkB5C;;OAEG;YACW,8BAA8B;IAiE5C;;OAEG;IACH,OAAO,CAAC,wBAAwB;CAgBjC"}
|
package/dist/core/discovery.js
CHANGED
|
@@ -71,7 +71,11 @@ class KubernetesDiscovery {
|
|
|
71
71
|
// Resolve relative paths against process.cwd()
|
|
72
72
|
return path.isAbsolute(kubeconfigPath) ? kubeconfigPath : path.resolve(kubeconfigPath);
|
|
73
73
|
}
|
|
74
|
-
// Priority 3: Default location
|
|
74
|
+
// Priority 3: Default location (only when not in cluster)
|
|
75
|
+
// When KUBERNETES_SERVICE_HOST is set, we should use in-cluster config instead
|
|
76
|
+
if (process.env.KUBERNETES_SERVICE_HOST) {
|
|
77
|
+
return ''; // Empty string indicates in-cluster should be used
|
|
78
|
+
}
|
|
75
79
|
return path.join(os.homedir(), '.kube', 'config');
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
@@ -87,17 +91,100 @@ class KubernetesDiscovery {
|
|
|
87
91
|
this.kubeconfigPath = newPath;
|
|
88
92
|
this.connected = false; // Force reconnection with new path
|
|
89
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Get connection status and configuration info for diagnostics
|
|
96
|
+
*/
|
|
97
|
+
getConnectionInfo() {
|
|
98
|
+
const isInCluster = process.env.KUBERNETES_SERVICE_HOST && (!this.kubeconfigPath || this.kubeconfigPath === '');
|
|
99
|
+
if (isInCluster) {
|
|
100
|
+
return {
|
|
101
|
+
connected: this.connected,
|
|
102
|
+
kubeconfig: 'in-cluster',
|
|
103
|
+
mode: 'in-cluster',
|
|
104
|
+
server: `https://${process.env.KUBERNETES_SERVICE_HOST}:${process.env.KUBERNETES_SERVICE_PORT}`,
|
|
105
|
+
context: 'in-cluster'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// For file-based or default configs
|
|
109
|
+
const mode = this.kubeconfigPath ? 'file' : 'default';
|
|
110
|
+
const kubeconfig = this.kubeconfigPath || '~/.kube/config';
|
|
111
|
+
let server;
|
|
112
|
+
let context;
|
|
113
|
+
if (this.connected && this.kc) {
|
|
114
|
+
try {
|
|
115
|
+
context = this.kc.getCurrentContext();
|
|
116
|
+
const cluster = this.kc.getCurrentCluster();
|
|
117
|
+
server = cluster?.server;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// Ignore errors getting context/cluster info
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
connected: this.connected,
|
|
125
|
+
kubeconfig,
|
|
126
|
+
mode,
|
|
127
|
+
server,
|
|
128
|
+
context
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Test connection to the cluster with detailed result
|
|
133
|
+
*/
|
|
134
|
+
async testConnection() {
|
|
135
|
+
if (!this.connected || !this.k8sApi) {
|
|
136
|
+
return { connected: false, error: 'No connection established' };
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
// Simple API call to test connectivity
|
|
140
|
+
await this.k8sApi.listNamespace();
|
|
141
|
+
// Try to get server version
|
|
142
|
+
let version;
|
|
143
|
+
try {
|
|
144
|
+
const versionClient = this.kc.makeApiClient(k8s.VersionApi);
|
|
145
|
+
const versionResponse = await versionClient.getCode();
|
|
146
|
+
version = versionResponse.gitVersion;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Version is optional
|
|
150
|
+
}
|
|
151
|
+
return { connected: true, version };
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
155
|
+
const classified = kubernetes_utils_1.ErrorClassifier.classifyError(error);
|
|
156
|
+
return {
|
|
157
|
+
connected: false,
|
|
158
|
+
error: errorMessage,
|
|
159
|
+
errorType: classified.type
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the Kubernetes client for direct API access (used by other tools)
|
|
165
|
+
*/
|
|
166
|
+
getClient() {
|
|
167
|
+
if (!this.kc) {
|
|
168
|
+
throw new Error('Kubernetes client not initialized. Call connect() first.');
|
|
169
|
+
}
|
|
170
|
+
return this.kc;
|
|
171
|
+
}
|
|
90
172
|
async connect() {
|
|
91
173
|
try {
|
|
92
174
|
this.kc = new k8s.KubeConfig();
|
|
93
175
|
if (this.kubeconfigPath) {
|
|
94
|
-
//
|
|
176
|
+
// Priority 1: Explicit kubeconfig file (constructor param or KUBECONFIG env)
|
|
95
177
|
if (!require('fs').existsSync(this.kubeconfigPath)) {
|
|
96
178
|
throw new Error(`Kubeconfig file not found: ${this.kubeconfigPath}`);
|
|
97
179
|
}
|
|
98
180
|
this.kc.loadFromFile(this.kubeconfigPath);
|
|
99
181
|
}
|
|
182
|
+
else if (process.env.KUBERNETES_SERVICE_HOST) {
|
|
183
|
+
// Priority 2: In-cluster configuration (when KUBERNETES_SERVICE_HOST is set by k8s)
|
|
184
|
+
this.kc.loadFromCluster();
|
|
185
|
+
}
|
|
100
186
|
else {
|
|
187
|
+
// Priority 3: Default kubeconfig location
|
|
101
188
|
this.kc.loadFromDefault();
|
|
102
189
|
}
|
|
103
190
|
// Create API clients
|
|
@@ -260,7 +347,12 @@ class KubernetesDiscovery {
|
|
|
260
347
|
* Delegates to shared utility function
|
|
261
348
|
*/
|
|
262
349
|
async executeKubectl(args, config) {
|
|
263
|
-
|
|
350
|
+
// Don't pass kubeconfig if it's empty (in-cluster configuration)
|
|
351
|
+
const kubectlConfig = { ...config };
|
|
352
|
+
if (this.kubeconfigPath && this.kubeconfigPath !== '') {
|
|
353
|
+
kubectlConfig.kubeconfig = this.kubeconfigPath;
|
|
354
|
+
}
|
|
355
|
+
return (0, kubernetes_utils_1.executeKubectl)(args, kubectlConfig);
|
|
264
356
|
}
|
|
265
357
|
async discoverCRDs(options) {
|
|
266
358
|
if (!this.connected) {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Universal Kubernetes application deployment agent with MCP interface
|
|
5
5
|
*/
|
|
6
6
|
export * from './core';
|
|
7
|
-
export declare const version
|
|
8
|
-
export declare const name
|
|
7
|
+
export declare const version: any;
|
|
8
|
+
export declare const name: any;
|
|
9
9
|
declare const _default: {
|
|
10
|
-
version:
|
|
11
|
-
name:
|
|
10
|
+
version: any;
|
|
11
|
+
name: any;
|
|
12
12
|
};
|
|
13
13
|
export default _default;
|
|
14
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,QAAQ,CAAC;AAOvB,eAAO,MAAM,OAAO,KAAsB,CAAC;AAC3C,eAAO,MAAM,IAAI,KAA4C,CAAC;;;;;AAG9D,wBAGE"}
|
package/dist/index.js
CHANGED
|
@@ -21,9 +21,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.name = exports.version = void 0;
|
|
23
23
|
__exportStar(require("./core"), exports);
|
|
24
|
-
// Version information
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// Version information - loaded dynamically from package.json
|
|
25
|
+
const fs_1 = require("fs");
|
|
26
|
+
const path_1 = require("path");
|
|
27
|
+
const packageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../package.json'), 'utf8'));
|
|
28
|
+
exports.version = packageJson.version;
|
|
29
|
+
exports.name = packageJson.name.replace('@vfarcic/', '');
|
|
27
30
|
// Default export for convenience
|
|
28
31
|
exports.default = {
|
|
29
32
|
version: exports.version,
|
package/dist/interfaces/mcp.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface MCPServerConfig {
|
|
|
10
10
|
version: string;
|
|
11
11
|
description: string;
|
|
12
12
|
author?: string;
|
|
13
|
+
transport?: 'stdio' | 'http';
|
|
14
|
+
port?: number;
|
|
15
|
+
host?: string;
|
|
16
|
+
sessionMode?: 'stateful' | 'stateless';
|
|
13
17
|
}
|
|
14
18
|
export declare class MCPServer {
|
|
15
19
|
private server;
|
|
@@ -17,6 +21,9 @@ export declare class MCPServer {
|
|
|
17
21
|
private initialized;
|
|
18
22
|
private logger;
|
|
19
23
|
private requestIdCounter;
|
|
24
|
+
private config;
|
|
25
|
+
private httpServer?;
|
|
26
|
+
private httpTransport?;
|
|
20
27
|
constructor(dotAI: DotAI, config: MCPServerConfig);
|
|
21
28
|
/**
|
|
22
29
|
* Register all tools with McpServer
|
|
@@ -28,6 +35,9 @@ export declare class MCPServer {
|
|
|
28
35
|
private registerPrompts;
|
|
29
36
|
private generateRequestId;
|
|
30
37
|
start(): Promise<void>;
|
|
38
|
+
private startStdioTransport;
|
|
39
|
+
private startHttpTransport;
|
|
40
|
+
private parseRequestBody;
|
|
31
41
|
stop(): Promise<void>;
|
|
32
42
|
isReady(): boolean;
|
|
33
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/interfaces/mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/interfaces/mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAwDtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;CACxC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,UAAU,CAAC,CAAkC;IACrD,OAAO,CAAC,aAAa,CAAC,CAAgC;gBAE1C,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe;IA8BjD;;OAEG;IACH,OAAO,CAAC,aAAa;IAmKrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqCvB,OAAO,CAAC,iBAAiB;IAInB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,mBAAmB;YAMnB,kBAAkB;YAiElB,gBAAgB;IAexB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,OAAO,IAAI,OAAO;CAGnB"}
|
package/dist/interfaces/mcp.js
CHANGED
|
@@ -9,6 +9,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.MCPServer = void 0;
|
|
10
10
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
11
11
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
12
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
13
|
+
const node_http_1 = require("node:http");
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
15
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
13
16
|
const error_handling_1 = require("../core/error-handling");
|
|
14
17
|
const recommend_1 = require("../tools/recommend");
|
|
@@ -26,8 +29,12 @@ class MCPServer {
|
|
|
26
29
|
initialized = false;
|
|
27
30
|
logger;
|
|
28
31
|
requestIdCounter = 0;
|
|
32
|
+
config;
|
|
33
|
+
httpServer;
|
|
34
|
+
httpTransport;
|
|
29
35
|
constructor(dotAI, config) {
|
|
30
36
|
this.dotAI = dotAI;
|
|
37
|
+
this.config = config;
|
|
31
38
|
this.logger = new error_handling_1.ConsoleLogger('MCPServer');
|
|
32
39
|
// Create McpServer instance
|
|
33
40
|
this.server = new mcp_js_1.McpServer({
|
|
@@ -147,12 +154,107 @@ class MCPServer {
|
|
|
147
154
|
return `mcp_${Date.now()}_${++this.requestIdCounter}`;
|
|
148
155
|
}
|
|
149
156
|
async start() {
|
|
157
|
+
// Get transport type from environment or config
|
|
158
|
+
const transportType = process.env.TRANSPORT_TYPE || this.config.transport || 'stdio';
|
|
159
|
+
this.logger.info('Starting MCP Server', {
|
|
160
|
+
transportType,
|
|
161
|
+
sessionMode: this.config.sessionMode || 'stateful'
|
|
162
|
+
});
|
|
163
|
+
if (transportType === 'http') {
|
|
164
|
+
await this.startHttpTransport();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
await this.startStdioTransport();
|
|
168
|
+
}
|
|
169
|
+
this.initialized = true;
|
|
170
|
+
}
|
|
171
|
+
async startStdioTransport() {
|
|
172
|
+
this.logger.info('Using STDIO transport');
|
|
150
173
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
151
174
|
await this.server.connect(transport);
|
|
152
|
-
|
|
175
|
+
}
|
|
176
|
+
async startHttpTransport() {
|
|
177
|
+
const port = parseInt(process.env.PORT || '') || this.config.port || 3456;
|
|
178
|
+
const host = process.env.HOST || this.config.host || '0.0.0.0';
|
|
179
|
+
const sessionMode = process.env.SESSION_MODE || this.config.sessionMode || 'stateful';
|
|
180
|
+
this.logger.info('Using HTTP/SSE transport', { port, host, sessionMode });
|
|
181
|
+
// Create HTTP transport with session management
|
|
182
|
+
this.httpTransport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
183
|
+
sessionIdGenerator: sessionMode === 'stateful' ? () => (0, node_crypto_1.randomUUID)() : undefined,
|
|
184
|
+
enableJsonResponse: false, // Use SSE for streaming
|
|
185
|
+
onsessioninitialized: (sessionId) => {
|
|
186
|
+
this.logger.info('Session initialized', { sessionId });
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// Connect MCP server to transport
|
|
190
|
+
await this.server.connect(this.httpTransport);
|
|
191
|
+
// Create HTTP server
|
|
192
|
+
this.httpServer = (0, node_http_1.createServer)(async (req, res) => {
|
|
193
|
+
this.logger.debug('HTTP request received', {
|
|
194
|
+
method: req.method,
|
|
195
|
+
url: req.url,
|
|
196
|
+
headers: req.headers
|
|
197
|
+
});
|
|
198
|
+
// Handle CORS for browser-based clients
|
|
199
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
200
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
201
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Session-Id');
|
|
202
|
+
if (req.method === 'OPTIONS') {
|
|
203
|
+
res.writeHead(204);
|
|
204
|
+
res.end();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// Parse request body for POST requests
|
|
208
|
+
let body = undefined;
|
|
209
|
+
if (req.method === 'POST') {
|
|
210
|
+
body = await this.parseRequestBody(req);
|
|
211
|
+
}
|
|
212
|
+
// Handle the request using the transport
|
|
213
|
+
try {
|
|
214
|
+
await this.httpTransport.handleRequest(req, res, body);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.logger.error('Error handling HTTP request', error);
|
|
218
|
+
if (!res.headersSent) {
|
|
219
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
220
|
+
res.end(JSON.stringify({ error: 'Internal server error' }));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
// Start listening
|
|
225
|
+
await new Promise((resolve, reject) => {
|
|
226
|
+
this.httpServer.listen(port, host, () => {
|
|
227
|
+
this.logger.info(`HTTP server listening on ${host}:${port}`);
|
|
228
|
+
resolve();
|
|
229
|
+
}).on('error', reject);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async parseRequestBody(req) {
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
let body = '';
|
|
235
|
+
req.on('data', chunk => body += chunk.toString());
|
|
236
|
+
req.on('end', () => {
|
|
237
|
+
try {
|
|
238
|
+
resolve(body ? JSON.parse(body) : undefined);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
reject(error);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
req.on('error', reject);
|
|
245
|
+
});
|
|
153
246
|
}
|
|
154
247
|
async stop() {
|
|
155
248
|
await this.server.close();
|
|
249
|
+
// Stop HTTP server if running
|
|
250
|
+
if (this.httpServer) {
|
|
251
|
+
await new Promise((resolve) => {
|
|
252
|
+
this.httpServer.close(() => {
|
|
253
|
+
this.logger.info('HTTP server stopped');
|
|
254
|
+
resolve();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
156
258
|
this.initialized = false;
|
|
157
259
|
}
|
|
158
260
|
isReady() {
|
package/dist/mcp/server.js
CHANGED
|
@@ -39,22 +39,24 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
return result;
|
|
40
40
|
};
|
|
41
41
|
})();
|
|
42
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
43
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
|
+
};
|
|
42
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
46
|
const mcp_js_1 = require("../interfaces/mcp.js");
|
|
44
47
|
const index_js_1 = require("../core/index.js");
|
|
48
|
+
const fs_1 = require("fs");
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
45
50
|
async function main() {
|
|
46
51
|
try {
|
|
47
52
|
// Validate required environment variables
|
|
48
53
|
process.stderr.write('Validating MCP server configuration...\n');
|
|
49
54
|
// Check session directory configuration
|
|
50
|
-
const sessionDir = process.env.DOT_AI_SESSION_DIR;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
process.stderr.write('
|
|
54
|
-
process.stderr.write('
|
|
55
|
-
process.stderr.write('- Example: "DOT_AI_SESSION_DIR": "/tmp/dot-ai-sessions"\n');
|
|
56
|
-
process.stderr.write('- Ensure the directory exists and is writable\n');
|
|
57
|
-
process.exit(1);
|
|
55
|
+
const sessionDir = process.env.DOT_AI_SESSION_DIR || '/app/sessions';
|
|
56
|
+
process.stderr.write(`Using session directory: ${sessionDir}\n`);
|
|
57
|
+
if (!process.env.DOT_AI_SESSION_DIR) {
|
|
58
|
+
process.stderr.write('INFO: DOT_AI_SESSION_DIR not set, using default: /app/sessions\n');
|
|
59
|
+
process.stderr.write('For custom session directory, set DOT_AI_SESSION_DIR environment variable\n');
|
|
58
60
|
}
|
|
59
61
|
// Validate session directory exists and is writable
|
|
60
62
|
try {
|
|
@@ -103,15 +105,18 @@ async function main() {
|
|
|
103
105
|
process.stderr.write(`FATAL: Failed to initialize DevOps AI Toolkit: ${initError}\n`);
|
|
104
106
|
process.exit(1);
|
|
105
107
|
}
|
|
108
|
+
// Load version dynamically from package.json
|
|
109
|
+
const packageJson = JSON.parse((0, fs_1.readFileSync)(path_1.default.join(__dirname, '../../package.json'), 'utf8'));
|
|
106
110
|
// Create and configure MCP server
|
|
107
111
|
const mcpServer = new mcp_js_1.MCPServer(dotAI, {
|
|
108
112
|
name: 'dot-ai',
|
|
109
|
-
version:
|
|
113
|
+
version: packageJson.version,
|
|
110
114
|
description: 'Universal Kubernetes application deployment agent with AI-powered orchestration',
|
|
111
115
|
author: 'Viktor Farcic'
|
|
112
116
|
});
|
|
113
117
|
// Start the MCP server
|
|
114
|
-
process.
|
|
118
|
+
const transportType = process.env.TRANSPORT_TYPE || 'stdio';
|
|
119
|
+
process.stderr.write(`Starting DevOps AI Toolkit MCP server with ${transportType} transport...\n`);
|
|
115
120
|
await mcpServer.start();
|
|
116
121
|
process.stderr.write('DevOps AI Toolkit MCP server started successfully\n');
|
|
117
122
|
// Handle graceful shutdown
|
|
@@ -125,6 +130,18 @@ async function main() {
|
|
|
125
130
|
await mcpServer.stop();
|
|
126
131
|
process.exit(0);
|
|
127
132
|
});
|
|
133
|
+
// Keep the process alive for HTTP transport
|
|
134
|
+
if (transportType === 'http') {
|
|
135
|
+
process.stderr.write('HTTP transport active - server will run until terminated\n');
|
|
136
|
+
// Keep the process running indefinitely for HTTP server
|
|
137
|
+
const keepAlive = () => {
|
|
138
|
+
setTimeout(keepAlive, 24 * 60 * 60 * 1000); // Check every 24 hours
|
|
139
|
+
};
|
|
140
|
+
keepAlive();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
process.stderr.write('STDIO transport active - waiting for client connection\n');
|
|
144
|
+
}
|
|
128
145
|
}
|
|
129
146
|
catch (error) {
|
|
130
147
|
process.stderr.write(`Failed to start DevOps AI Toolkit MCP server: ${error}\n`);
|
package/dist/tools/version.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export interface SystemStatus {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* Test Kyverno installation and readiness for policy generation
|
|
83
|
+
* Test Kyverno installation and readiness for policy generation using shared client
|
|
84
84
|
*/
|
|
85
85
|
export declare function getKyvernoStatus(): Promise<SystemStatus['kyverno']>;
|
|
86
86
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/tools/version.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/tools/version.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/version.js
CHANGED
|
@@ -45,7 +45,9 @@ exports.getVersionInfo = getVersionInfo;
|
|
|
45
45
|
exports.handleVersionTool = handleVersionTool;
|
|
46
46
|
const fs_1 = require("fs");
|
|
47
47
|
const path_1 = require("path");
|
|
48
|
+
const k8s = __importStar(require("@kubernetes/client-node"));
|
|
48
49
|
const index_1 = require("../core/index");
|
|
50
|
+
const discovery_1 = require("../core/discovery");
|
|
49
51
|
const kubernetes_utils_1 = require("../core/kubernetes-utils");
|
|
50
52
|
exports.VERSION_TOOL_NAME = 'version';
|
|
51
53
|
exports.VERSION_TOOL_DESCRIPTION = 'Get comprehensive system status including version information, Vector DB connection status, embedding service capabilities, Anthropic API connectivity, Kubernetes cluster connectivity, Kyverno policy engine status, and pattern management health check';
|
|
@@ -242,16 +244,24 @@ async function getCapabilityStatus() {
|
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
/**
|
|
245
|
-
* Test Kyverno installation and readiness for policy generation
|
|
247
|
+
* Test Kyverno installation and readiness for policy generation using shared client
|
|
246
248
|
*/
|
|
247
249
|
async function getKyvernoStatus() {
|
|
248
|
-
const kubeconfig = process.env.KUBECONFIG || '~/.kube/config';
|
|
249
250
|
try {
|
|
250
|
-
//
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
// Create discovery instance and establish connection
|
|
252
|
+
const discovery = new discovery_1.KubernetesDiscovery({});
|
|
253
|
+
await discovery.connect();
|
|
254
|
+
// First test if we can connect to Kubernetes at all
|
|
255
|
+
const testResult = await discovery.testConnection();
|
|
256
|
+
if (!testResult.connected) {
|
|
257
|
+
return {
|
|
258
|
+
installed: false,
|
|
259
|
+
policyGenerationReady: false,
|
|
260
|
+
error: 'Cannot detect Kyverno - Kubernetes cluster is not accessible'
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Check if Kyverno CRDs are installed using the original approach
|
|
264
|
+
const crdOutput = await discovery.executeKubectl(['get', 'crd', '--no-headers']);
|
|
255
265
|
const kyvernoCRDs = crdOutput.split('\n').filter(line => line.includes('kyverno.io') && (line.includes('clusterpolicies') ||
|
|
256
266
|
line.includes('policies') ||
|
|
257
267
|
line.includes('policyreports')));
|
|
@@ -262,23 +272,33 @@ async function getKyvernoStatus() {
|
|
|
262
272
|
reason: 'Kyverno CRDs not found in cluster - Kyverno is not installed'
|
|
263
273
|
};
|
|
264
274
|
}
|
|
265
|
-
// Check if Kyverno deployment is ready
|
|
275
|
+
// Check if Kyverno deployment is ready using the client
|
|
266
276
|
let deploymentReady = false;
|
|
267
277
|
let webhookReady = false;
|
|
268
278
|
let version;
|
|
269
279
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
280
|
+
// Get client and check deployment status
|
|
281
|
+
const client = discovery.getClient();
|
|
282
|
+
const appsV1Api = client.makeApiClient(k8s.AppsV1Api);
|
|
283
|
+
const deploymentResponse = await appsV1Api.listNamespacedDeployment({
|
|
284
|
+
namespace: 'kyverno'
|
|
285
|
+
});
|
|
286
|
+
const kyvernoDeployments = deploymentResponse.items.filter((deployment) => deployment.metadata?.name?.startsWith('kyverno-'));
|
|
287
|
+
if (kyvernoDeployments.length > 0) {
|
|
288
|
+
// Check if all Kyverno deployments are ready
|
|
289
|
+
deploymentReady = kyvernoDeployments.every((deployment) => {
|
|
290
|
+
const readyReplicas = deployment.status?.readyReplicas || 0;
|
|
291
|
+
const replicas = deployment.status?.replicas || 0;
|
|
292
|
+
return readyReplicas > 0 && readyReplicas === replicas;
|
|
293
|
+
});
|
|
294
|
+
// Try to get version from image tag of the first deployment (usually admission controller)
|
|
295
|
+
const firstDeployment = kyvernoDeployments[0];
|
|
296
|
+
const container = firstDeployment.spec?.template.spec?.containers?.[0];
|
|
297
|
+
if (container?.image) {
|
|
298
|
+
const imageMatch = container.image.match(/:v?([0-9]+\.[0-9]+\.[0-9]+)/);
|
|
299
|
+
if (imageMatch) {
|
|
300
|
+
version = imageMatch[1];
|
|
301
|
+
}
|
|
282
302
|
}
|
|
283
303
|
}
|
|
284
304
|
}
|
|
@@ -288,36 +308,14 @@ async function getKyvernoStatus() {
|
|
|
288
308
|
}
|
|
289
309
|
// Check admission controller webhook
|
|
290
310
|
try {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
webhookReady =
|
|
311
|
+
const client = discovery.getClient();
|
|
312
|
+
const admissionApi = client.makeApiClient(k8s.AdmissionregistrationV1Api);
|
|
313
|
+
const webhookResponse = await admissionApi.listValidatingWebhookConfiguration();
|
|
314
|
+
webhookReady = webhookResponse.items.some((webhook) => webhook.metadata?.name?.includes('kyverno'));
|
|
295
315
|
}
|
|
296
316
|
catch (error) {
|
|
297
317
|
webhookReady = false;
|
|
298
318
|
}
|
|
299
|
-
// Try to get version from deployment labels or image
|
|
300
|
-
try {
|
|
301
|
-
const deploymentDetails = await (0, kubernetes_utils_1.executeKubectl)([
|
|
302
|
-
'get', 'deployment', 'kyverno', '-n', 'kyverno', '-o', 'jsonpath={.metadata.labels.version}'
|
|
303
|
-
], { kubeconfig, timeout: 5000 });
|
|
304
|
-
if (deploymentDetails && deploymentDetails.trim()) {
|
|
305
|
-
version = deploymentDetails.trim();
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
// Fallback: try to get version from image tag
|
|
309
|
-
const imageOutput = await (0, kubernetes_utils_1.executeKubectl)([
|
|
310
|
-
'get', 'deployment', 'kyverno', '-n', 'kyverno', '-o', 'jsonpath={.spec.template.spec.containers[0].image}'
|
|
311
|
-
], { kubeconfig, timeout: 5000 });
|
|
312
|
-
const imageMatch = imageOutput.match(/:v?([0-9]+\.[0-9]+\.[0-9]+)/);
|
|
313
|
-
if (imageMatch) {
|
|
314
|
-
version = imageMatch[1];
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch (error) {
|
|
319
|
-
// Version detection is optional
|
|
320
|
-
}
|
|
321
319
|
// Determine if policy generation is ready
|
|
322
320
|
const policyGenerationReady = deploymentReady && webhookReady;
|
|
323
321
|
if (!policyGenerationReady) {
|
|
@@ -348,14 +346,6 @@ async function getKyvernoStatus() {
|
|
|
348
346
|
}
|
|
349
347
|
catch (error) {
|
|
350
348
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
351
|
-
// If Kubernetes is not available, we can't detect Kyverno
|
|
352
|
-
if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('connection refused')) {
|
|
353
|
-
return {
|
|
354
|
-
installed: false,
|
|
355
|
-
policyGenerationReady: false,
|
|
356
|
-
error: 'Cannot detect Kyverno - Kubernetes cluster is not accessible'
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
349
|
return {
|
|
360
350
|
installed: false,
|
|
361
351
|
policyGenerationReady: false,
|
|
@@ -364,55 +354,42 @@ async function getKyvernoStatus() {
|
|
|
364
354
|
}
|
|
365
355
|
}
|
|
366
356
|
/**
|
|
367
|
-
* Test Kubernetes cluster connectivity
|
|
357
|
+
* Test Kubernetes cluster connectivity using shared client
|
|
368
358
|
*/
|
|
369
359
|
async function getKubernetesStatus() {
|
|
370
|
-
const kubeconfig = process.env.KUBECONFIG || '~/.kube/config';
|
|
371
360
|
try {
|
|
372
|
-
//
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
// Get server version
|
|
390
|
-
let version;
|
|
391
|
-
try {
|
|
392
|
-
const versionInfo = await (0, kubernetes_utils_1.executeKubectl)(['version', '--short'], { kubeconfig, timeout: 5000 });
|
|
393
|
-
const serverMatch = versionInfo.match(/Server Version: (.+)/);
|
|
394
|
-
version = serverMatch ? serverMatch[1] : undefined;
|
|
361
|
+
// Create discovery instance and establish connection
|
|
362
|
+
const discovery = new discovery_1.KubernetesDiscovery({});
|
|
363
|
+
await discovery.connect();
|
|
364
|
+
// Get connection info using the shared approach
|
|
365
|
+
const connectionInfo = discovery.getConnectionInfo();
|
|
366
|
+
const testResult = await discovery.testConnection();
|
|
367
|
+
if (testResult.connected) {
|
|
368
|
+
return {
|
|
369
|
+
connected: true,
|
|
370
|
+
clusterInfo: {
|
|
371
|
+
endpoint: connectionInfo.server,
|
|
372
|
+
version: testResult.version,
|
|
373
|
+
context: connectionInfo.context
|
|
374
|
+
},
|
|
375
|
+
kubeconfig: connectionInfo.kubeconfig
|
|
376
|
+
};
|
|
395
377
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
378
|
+
else {
|
|
379
|
+
return {
|
|
380
|
+
connected: false,
|
|
381
|
+
kubeconfig: connectionInfo.kubeconfig,
|
|
382
|
+
error: testResult.error,
|
|
383
|
+
errorType: testResult.errorType
|
|
384
|
+
};
|
|
399
385
|
}
|
|
400
|
-
return {
|
|
401
|
-
connected: true,
|
|
402
|
-
clusterInfo: {
|
|
403
|
-
endpoint,
|
|
404
|
-
version,
|
|
405
|
-
context
|
|
406
|
-
},
|
|
407
|
-
kubeconfig
|
|
408
|
-
};
|
|
409
386
|
}
|
|
410
387
|
catch (error) {
|
|
411
388
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
412
389
|
const classified = kubernetes_utils_1.ErrorClassifier.classifyError(error);
|
|
413
390
|
return {
|
|
414
391
|
connected: false,
|
|
415
|
-
kubeconfig,
|
|
392
|
+
kubeconfig: process.env.KUBECONFIG || '~/.kube/config',
|
|
416
393
|
error: errorMessage,
|
|
417
394
|
errorType: classified.type
|
|
418
395
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vfarcic/dot-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.0",
|
|
4
4
|
"description": "AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
"typescript": "^5.0.0"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@anthropic-ai/sdk": "^0.
|
|
86
|
+
"@anthropic-ai/sdk": "^0.61.0",
|
|
87
87
|
"@kubernetes/client-node": "^1.3.0",
|
|
88
88
|
"@modelcontextprotocol/sdk": "^1.13.2",
|
|
89
89
|
"@qdrant/js-client-rest": "^1.15.0",
|
|
90
|
-
"@vfarcic/dot-ai": "^0.
|
|
90
|
+
"@vfarcic/dot-ai": "^0.82.0",
|
|
91
91
|
"glob": "^11.0.3",
|
|
92
92
|
"openai": "^5.11.0",
|
|
93
93
|
"yaml": "^2.8.0"
|
|
@@ -59,9 +59,10 @@ Complete the PRD implementation workflow including branch management, pull reque
|
|
|
59
59
|
- [ ] **Check ongoing processes**: Use `gh pr checks [pr-number]` to check for any ongoing CI/CD, security analysis, or automated reviews (CodeRabbit, CodeQL, etc.)
|
|
60
60
|
- [ ] **Check PR details**: Use `gh pr view [pr-number]` to check for human review comments and PR metadata
|
|
61
61
|
- [ ] **Review all automated feedback**: Check PR comments section for automated code review feedback (bots, linters, analyzers)
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
- **Use multiple methods to capture all feedback**:
|
|
63
|
+
- CLI commands: `gh pr view [pr-number]`, `gh pr checks [pr-number]`, `gh api repos/owner/repo/pulls/[pr-number]/comments`
|
|
64
|
+
- **Web interface inspection**: Fetch the PR URL directly to capture all comments, including inline code suggestions that CLI tools may miss
|
|
65
|
+
- Look for comments from automated tools (usernames ending in 'ai', 'bot', or known review tools)
|
|
65
66
|
- [ ] **Present code review findings**: ALWAYS summarize automated review feedback for the user (unless there are no findings)
|
|
66
67
|
- **Categorize findings**: Critical, Important, Optional based on impact
|
|
67
68
|
- **Provide specific examples**: Quote actual suggestions and their locations
|