vcluster-yaml-mcp-server 0.1.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 +292 -0
- package/package.json +52 -0
- package/src/cli-handlers.js +290 -0
- package/src/cli.js +128 -0
- package/src/formatters.js +168 -0
- package/src/github.js +170 -0
- package/src/http-server.js +67 -0
- package/src/index.js +11 -0
- package/src/middleware/auth.js +25 -0
- package/src/schema-validator.js +351 -0
- package/src/server.js +1006 -0
- package/src/snippet-validator.js +370 -0
- package/src/snippet-validator.test.js +366 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# vCluster YAML MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that lets AI assistants query and validate [vCluster](https://github.com/loft-sh/vcluster) YAML configurations directly from GitHub.
|
|
4
|
+
|
|
5
|
+
## What Does It Do?
|
|
6
|
+
|
|
7
|
+
This MCP server provides AI assistants with tools to:
|
|
8
|
+
- Query vCluster configuration options and schemas
|
|
9
|
+
- Validate YAML configurations
|
|
10
|
+
- Search for specific settings using natural language
|
|
11
|
+
- Query any version with explicit version parameters (stateless)
|
|
12
|
+
- Extract validation rules from comments
|
|
13
|
+
|
|
14
|
+
**Key feature:** No local files needed. All data is fetched live from the vCluster GitHub repository.
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
The server uses the GitHub API to fetch vCluster YAML configurations, schemas, and documentation directly from the source:
|
|
19
|
+
|
|
20
|
+
1. **GitHub as Source of Truth**: Queries `github.com/loft-sh/vcluster` repository
|
|
21
|
+
2. **Stateless Version Queries**: Every tool accepts an optional `version` parameter (e.g., `v0.19.0`, `main`)
|
|
22
|
+
3. **Parallel Version Support**: Query multiple versions simultaneously without state conflicts
|
|
23
|
+
4. **Live Data**: Always fetches the latest configuration for the requested version
|
|
24
|
+
5. **Smart Caching**: 15-minute in-memory cache to avoid overloading GitHub API
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
graph LR
|
|
28
|
+
A[Claude/AI] --> B[MCP Server]
|
|
29
|
+
B --> C[GitHub API]
|
|
30
|
+
C --> D[vCluster Repository]
|
|
31
|
+
B --> E[Parse YAML/JSON]
|
|
32
|
+
E --> F[Return Structured Data]
|
|
33
|
+
F --> A
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
### Option 1: Local (stdio)
|
|
39
|
+
|
|
40
|
+
Run the server locally via npx:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"vcluster-yaml": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["-y", "vcluster-yaml-mcp-server"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Option 2: Remote (HTTP)
|
|
54
|
+
|
|
55
|
+
Use the public instance (always running latest version):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"vcluster-yaml": {
|
|
61
|
+
"type": "http",
|
|
62
|
+
"url": "https://vcluster-yaml.cloudrumble.net/mcp"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Command-Line Interface
|
|
69
|
+
|
|
70
|
+
The package also provides a standalone CLI for quick queries and validation without MCP setup:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Quick start with npx (no installation)
|
|
74
|
+
npx -y vcluster-yaml-mcp-server vcluster-yaml query sync --format table
|
|
75
|
+
|
|
76
|
+
# Or install globally
|
|
77
|
+
npm install -g vcluster-yaml-mcp-server
|
|
78
|
+
vcluster-yaml query sync --format table
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
📖 **[Full CLI Documentation →](docs/CLI.md)**
|
|
82
|
+
|
|
83
|
+
## Available Tools
|
|
84
|
+
|
|
85
|
+
### Version Discovery
|
|
86
|
+
|
|
87
|
+
**list-versions** - Browse all available vCluster versions
|
|
88
|
+
```javascript
|
|
89
|
+
// Returns tags (releases) and branches
|
|
90
|
+
// Example output: v0.19.0, v0.20.0, main, etc.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Configuration Queries
|
|
94
|
+
|
|
95
|
+
All query tools accept an optional `version` parameter (defaults to "main"):
|
|
96
|
+
|
|
97
|
+
**smart-query** - Universal search using dot notation or natural language
|
|
98
|
+
```javascript
|
|
99
|
+
smart-query --query="controlPlane.ingress.enabled" --version="v0.19.0"
|
|
100
|
+
smart-query --query="namespace syncing" --version="main"
|
|
101
|
+
smart-query --query="etcd" // Defaults to "main"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Output Format (kubectl-style):**
|
|
105
|
+
```
|
|
106
|
+
Found 4 matches for "replicas" in chart/values.yaml (v0.24.0)
|
|
107
|
+
|
|
108
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
109
|
+
|
|
110
|
+
MATCH: controlPlane.statefulSet.highAvailability.replicas
|
|
111
|
+
TYPE: integer
|
|
112
|
+
VALUE: 1
|
|
113
|
+
|
|
114
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
115
|
+
|
|
116
|
+
MATCH: controlPlane.coredns.deployment.replicas
|
|
117
|
+
TYPE: integer
|
|
118
|
+
VALUE: 1
|
|
119
|
+
|
|
120
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
121
|
+
|
|
122
|
+
MATCH: controlPlane.statefulSet.highAvailability
|
|
123
|
+
TYPE: object
|
|
124
|
+
|
|
125
|
+
FIELDS:
|
|
126
|
+
replicas <integer>
|
|
127
|
+
value: 1
|
|
128
|
+
|
|
129
|
+
leaseDuration <integer>
|
|
130
|
+
value: 60
|
|
131
|
+
|
|
132
|
+
renewDeadline <integer>
|
|
133
|
+
value: 40
|
|
134
|
+
|
|
135
|
+
retryPeriod <integer>
|
|
136
|
+
value: 15
|
|
137
|
+
|
|
138
|
+
RELATED CONFIGS:
|
|
139
|
+
• controlPlane.statefulSet.resources - Resource limits for HA mode
|
|
140
|
+
• controlPlane.backingStore.etcd.deploy.statefulSet.highAvailability
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Features:**
|
|
144
|
+
- ✅ **Structured output** - kubectl-style format
|
|
145
|
+
- ✅ **Type information** - Every value shows its type (integer, string, boolean, array, object)
|
|
146
|
+
- ✅ **Relevance ranking** - Exact matches appear first, results sorted by relevance
|
|
147
|
+
- ✅ **Related configs** - Suggests commonly configured fields together
|
|
148
|
+
- ✅ **Smart formatting** - Objects show structure, not full content dumps
|
|
149
|
+
- ✅ **LLM-friendly** - Easy to parse and understand for AI assistants
|
|
150
|
+
|
|
151
|
+
### Config Creation & Validation
|
|
152
|
+
|
|
153
|
+
All validation tools accept an optional `version` parameter (defaults to "main"):
|
|
154
|
+
|
|
155
|
+
**create-vcluster-config** - Create and validate configs in one step (PRIMARY TOOL)
|
|
156
|
+
```javascript
|
|
157
|
+
// Claude uses this when generating configs for you
|
|
158
|
+
// Ensures every generated config is validated before you see it
|
|
159
|
+
create-vcluster-config --yaml_content="<generated-yaml>" --description="Node sync config" --version="v0.24.0"
|
|
160
|
+
|
|
161
|
+
// Returns:
|
|
162
|
+
// ✅ Configuration validated successfully!
|
|
163
|
+
// Version: v0.24.0
|
|
164
|
+
// Section: sync
|
|
165
|
+
// Validation time: 45ms
|
|
166
|
+
//
|
|
167
|
+
// ### Configuration:
|
|
168
|
+
// [your YAML here]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**validate-config** - Validate existing YAML configs
|
|
172
|
+
```javascript
|
|
173
|
+
// Validate user-provided configs against specific version
|
|
174
|
+
validate-config --content="<your-yaml>" --version="v0.24.0"
|
|
175
|
+
|
|
176
|
+
// Validate files from GitHub
|
|
177
|
+
validate-config --file="chart/values.yaml" --version="main"
|
|
178
|
+
|
|
179
|
+
// Works with full configs or partial snippets (auto-detects section)
|
|
180
|
+
// Returns: { valid: true/false, errors: [...], section: "...", version: "...", elapsed_ms: <100 }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**extract-validation-rules** - Get validation rules from YAML comments
|
|
184
|
+
```javascript
|
|
185
|
+
extract-validation-rules --section="controlPlane" --version="v0.24.0"
|
|
186
|
+
// Returns: { rules, enums, dependencies, defaults }
|
|
187
|
+
// Extracts constraints like "Valid values: a, b, c"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Usage Examples
|
|
191
|
+
|
|
192
|
+
### Interactive Config Creation (Primary Workflow)
|
|
193
|
+
|
|
194
|
+
Ask Claude:
|
|
195
|
+
> "Create a vCluster config with node sync enabled and etcd embedded"
|
|
196
|
+
|
|
197
|
+
Claude will:
|
|
198
|
+
1. Use `smart-query` or `extract-validation-rules` to research options
|
|
199
|
+
2. Generate the YAML configuration
|
|
200
|
+
3. **Automatically** call `create-vcluster-config` to validate
|
|
201
|
+
4. Return validated, ready-to-use configuration
|
|
202
|
+
|
|
203
|
+
**Why this works:** The `create-vcluster-config` tool forces Claude to validate every config it generates. You'll always get validated configs.
|
|
204
|
+
|
|
205
|
+
### Validate User-Provided Configuration
|
|
206
|
+
|
|
207
|
+
Ask Claude:
|
|
208
|
+
> "Is this ingress configuration valid for vCluster v0.24?"
|
|
209
|
+
> ```yaml
|
|
210
|
+
> ingress:
|
|
211
|
+
> enabled: true
|
|
212
|
+
> host: "my-vcluster.example.com"
|
|
213
|
+
> ```
|
|
214
|
+
|
|
215
|
+
Claude will:
|
|
216
|
+
1. Use `validate-config` with `--version="v0.24.0"` parameter
|
|
217
|
+
2. Report any validation errors with specific paths
|
|
218
|
+
3. Suggest fixes if needed
|
|
219
|
+
|
|
220
|
+
### Explore vCluster Options
|
|
221
|
+
|
|
222
|
+
Ask Claude:
|
|
223
|
+
> "What high availability options are available in vCluster v0.19.0?"
|
|
224
|
+
|
|
225
|
+
Claude will use:
|
|
226
|
+
- `smart-query` with `--version="v0.19.0"` to find HA-related settings
|
|
227
|
+
- No need to "switch" versions - query directly with version parameter
|
|
228
|
+
|
|
229
|
+
### Compare Versions
|
|
230
|
+
|
|
231
|
+
Ask Claude:
|
|
232
|
+
> "How did the sync.fromHost configuration change between v0.19.0 and v0.20.0?"
|
|
233
|
+
|
|
234
|
+
Claude will use:
|
|
235
|
+
- `smart-query` with `--version="v0.19.0"` for first version
|
|
236
|
+
- `smart-query` with `--version="v0.20.0"` for second version
|
|
237
|
+
- Can query both versions in parallel (stateless design)
|
|
238
|
+
|
|
239
|
+
## Token Optimization
|
|
240
|
+
|
|
241
|
+
This server is designed for efficient token usage with the new kubectl-style format:
|
|
242
|
+
|
|
243
|
+
| Tool | Tokens | Strategy | Performance |
|
|
244
|
+
|------|--------|----------|-------------|
|
|
245
|
+
| create-vcluster-config | ~300-600 | Validation + formatted response with emoji indicators | <100ms |
|
|
246
|
+
| validate-config | ~200-500 | Fast validation, precise errors only | <100ms |
|
|
247
|
+
| smart-query | ~800-1.5K | Structured output (was ~2K with JSON dumps), limits to 50 matches | <100ms |
|
|
248
|
+
| extract-validation-rules | ~2-5K | Section-specific filtering, cache for knowledge base | <100ms |
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Install dependencies
|
|
254
|
+
npm install
|
|
255
|
+
|
|
256
|
+
# Run locally (stdio)
|
|
257
|
+
node src/index.js
|
|
258
|
+
|
|
259
|
+
# Test with MCP Inspector
|
|
260
|
+
npx @modelcontextprotocol/inspector node src/index.js
|
|
261
|
+
# Open http://localhost:5173
|
|
262
|
+
|
|
263
|
+
# Run tests
|
|
264
|
+
npm test
|
|
265
|
+
|
|
266
|
+
# Run HTTP server locally
|
|
267
|
+
npm run start:http
|
|
268
|
+
# Server runs on http://localhost:3000
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Technical Details
|
|
272
|
+
|
|
273
|
+
- **SDK**: `@modelcontextprotocol/sdk` v1.20.1 (Streamable HTTP transport)
|
|
274
|
+
- **Node**: >=18
|
|
275
|
+
- **Transport**: Both stdio (local) and HTTP/SSE (remote)
|
|
276
|
+
- **Dependencies**: `js-yaml` for parsing, `node-jq` for querying, `node-fetch` for GitHub API
|
|
277
|
+
|
|
278
|
+
## Release Process
|
|
279
|
+
|
|
280
|
+
This project uses automated CI/CD workflows for releases to npm, Docker Hub, and GitHub Releases.
|
|
281
|
+
|
|
282
|
+
📖 **[Release Documentation →](docs/RELEASING.md)**
|
|
283
|
+
|
|
284
|
+
## Links
|
|
285
|
+
|
|
286
|
+
- [vCluster GitHub](https://github.com/loft-sh/vcluster)
|
|
287
|
+
- [Model Context Protocol](https://modelcontextprotocol.io)
|
|
288
|
+
- [MCP Specification](https://spec.modelcontextprotocol.io)
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vcluster-yaml-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for querying vcluster YAML configurations using jq",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vcluster-yaml-mcp": "./src/index.js",
|
|
9
|
+
"vcluster-yaml": "./src/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/**/*.js",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node src/index.js",
|
|
17
|
+
"start:http": "node src/http-server.js",
|
|
18
|
+
"docker:build": "docker build -t piotrzan/vcluster-yaml-mcp-server:latest .",
|
|
19
|
+
"docker:push": "docker push piotrzan/vcluster-yaml-mcp-server:latest",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"test:perf": "vitest run tests/performance.test.js",
|
|
24
|
+
"test:ci": "vitest run tests/ci/",
|
|
25
|
+
"lint:workflows": "actionlint .github/workflows/*.yml"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
29
|
+
"ajv": "^8.17.1",
|
|
30
|
+
"ajv-formats": "^3.0.1",
|
|
31
|
+
"chalk": "^5.4.1",
|
|
32
|
+
"cli-table3": "^0.6.5",
|
|
33
|
+
"commander": "^13.0.0",
|
|
34
|
+
"express": "^4.18.2",
|
|
35
|
+
"js-yaml": "^4.1.0",
|
|
36
|
+
"node-fetch": "^3.3.2",
|
|
37
|
+
"node-jq": "^6.0.1"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
44
|
+
"execa": "^9.5.2",
|
|
45
|
+
"json-schema-library": "^10.3.0",
|
|
46
|
+
"jsonschema": "^1.5.0",
|
|
47
|
+
"strip-ansi": "^7.1.0",
|
|
48
|
+
"supertest": "^7.1.4",
|
|
49
|
+
"vitest": "^3.2.4",
|
|
50
|
+
"z-schema": "^6.0.2"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI handlers that bridge CLI commands to underlying logic
|
|
3
|
+
* Reuses githubClient and validation logic from server.js
|
|
4
|
+
* Returns structured data for CLI formatters
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
import { githubClient } from './github.js';
|
|
9
|
+
import { validateSnippet } from './snippet-validator.js';
|
|
10
|
+
|
|
11
|
+
// Helper function to get the type of a value
|
|
12
|
+
function getType(value) {
|
|
13
|
+
if (value === null) return 'null';
|
|
14
|
+
if (Array.isArray(value)) return 'array';
|
|
15
|
+
if (typeof value === 'object') return 'object';
|
|
16
|
+
if (typeof value === 'number') {
|
|
17
|
+
return Number.isInteger(value) ? 'integer' : 'number';
|
|
18
|
+
}
|
|
19
|
+
return typeof value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Helper function to rank search results
|
|
23
|
+
function rankResult(item, searchTerm) {
|
|
24
|
+
const pathLower = item.path.toLowerCase();
|
|
25
|
+
const keyLower = item.key.toLowerCase();
|
|
26
|
+
let score = 0;
|
|
27
|
+
|
|
28
|
+
// Exact match
|
|
29
|
+
if (pathLower === searchTerm || keyLower === searchTerm) {
|
|
30
|
+
score += 100;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Starts with search term
|
|
34
|
+
if (pathLower.startsWith(searchTerm) || keyLower.startsWith(searchTerm)) {
|
|
35
|
+
score += 50;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Ends with search term
|
|
39
|
+
if (pathLower.endsWith(searchTerm) || keyLower.endsWith(searchTerm)) {
|
|
40
|
+
score += 40;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Contains search term
|
|
44
|
+
if (pathLower.includes(searchTerm) || keyLower.includes(searchTerm)) {
|
|
45
|
+
score += 30;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Prefer leaf nodes (actual values)
|
|
49
|
+
if (item.isLeaf) {
|
|
50
|
+
score += 10;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Prefer shorter paths (more specific)
|
|
54
|
+
const pathDepth = item.path.split('.').length;
|
|
55
|
+
score -= pathDepth;
|
|
56
|
+
|
|
57
|
+
return score;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handle query command
|
|
62
|
+
* Searches for configuration fields in vCluster YAML
|
|
63
|
+
*/
|
|
64
|
+
export async function handleQuery(query, options) {
|
|
65
|
+
const version = options.version || 'main';
|
|
66
|
+
const fileName = options.file || 'chart/values.yaml';
|
|
67
|
+
|
|
68
|
+
let yamlData;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
yamlData = await githubClient.getYamlContent(fileName, version);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error: `Could not load ${fileName} from GitHub (version: ${version}). Error: ${error.message}`,
|
|
76
|
+
metadata: {
|
|
77
|
+
query,
|
|
78
|
+
file: fileName,
|
|
79
|
+
version
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const searchTerm = query.toLowerCase();
|
|
85
|
+
const results = [];
|
|
86
|
+
|
|
87
|
+
// Helper function to extract all paths and values
|
|
88
|
+
function extractInfo(obj, path = '') {
|
|
89
|
+
const info = [];
|
|
90
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
91
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
92
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
93
|
+
|
|
94
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
95
|
+
info.push({ path: currentPath, key, value, isLeaf: false });
|
|
96
|
+
info.push(...extractInfo(value, currentPath));
|
|
97
|
+
} else {
|
|
98
|
+
info.push({ path: currentPath, key, value, isLeaf: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return info;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const allInfo = extractInfo(yamlData);
|
|
106
|
+
|
|
107
|
+
// Support dot notation queries
|
|
108
|
+
const isDotNotation = searchTerm.includes('.');
|
|
109
|
+
|
|
110
|
+
if (isDotNotation) {
|
|
111
|
+
// Exact and partial dot notation matching
|
|
112
|
+
for (const item of allInfo) {
|
|
113
|
+
const pathLower = item.path.toLowerCase();
|
|
114
|
+
|
|
115
|
+
if (pathLower === searchTerm ||
|
|
116
|
+
pathLower.endsWith(searchTerm) ||
|
|
117
|
+
pathLower.includes(searchTerm)) {
|
|
118
|
+
results.push(item);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Keyword-based search
|
|
123
|
+
const keywords = searchTerm.split(/\s+/);
|
|
124
|
+
|
|
125
|
+
for (const item of allInfo) {
|
|
126
|
+
const pathLower = item.path.toLowerCase();
|
|
127
|
+
const keyLower = item.key.toLowerCase();
|
|
128
|
+
const valueStr = JSON.stringify(item.value).toLowerCase();
|
|
129
|
+
|
|
130
|
+
const allKeywordsMatch = keywords.every(kw =>
|
|
131
|
+
pathLower.includes(kw) || keyLower.includes(kw) || valueStr.includes(kw)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (allKeywordsMatch) {
|
|
135
|
+
results.push(item);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Sort results by relevance
|
|
141
|
+
results.sort((a, b) => {
|
|
142
|
+
const scoreA = rankResult(a, searchTerm);
|
|
143
|
+
const scoreB = rankResult(b, searchTerm);
|
|
144
|
+
return scoreB - scoreA;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Format results for CLI output
|
|
148
|
+
const formattedResults = results.slice(0, 50).map(item => ({
|
|
149
|
+
field: item.path,
|
|
150
|
+
value: item.value,
|
|
151
|
+
type: getType(item.value),
|
|
152
|
+
path: item.path,
|
|
153
|
+
description: '' // Could be enhanced with comments parsing
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
results: formattedResults,
|
|
159
|
+
metadata: {
|
|
160
|
+
query,
|
|
161
|
+
file: fileName,
|
|
162
|
+
version,
|
|
163
|
+
resultCount: formattedResults.length,
|
|
164
|
+
totalMatches: results.length
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Handle list-versions command
|
|
171
|
+
* Lists available vCluster versions from GitHub
|
|
172
|
+
*/
|
|
173
|
+
export async function handleListVersions() {
|
|
174
|
+
try {
|
|
175
|
+
const tags = await githubClient.getTags();
|
|
176
|
+
|
|
177
|
+
// Only show versions starting with 'v'
|
|
178
|
+
const versionTags = tags.filter(tag => tag.startsWith('v'));
|
|
179
|
+
|
|
180
|
+
// Always include main branch
|
|
181
|
+
const versions = ['main', ...versionTags];
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
success: true,
|
|
185
|
+
versions,
|
|
186
|
+
metadata: {
|
|
187
|
+
totalCount: versions.length,
|
|
188
|
+
source: 'github'
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: `Failed to fetch versions: ${error.message}`,
|
|
195
|
+
versions: [],
|
|
196
|
+
metadata: {
|
|
197
|
+
totalCount: 0,
|
|
198
|
+
source: 'github'
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Handle validate command
|
|
206
|
+
* Validates vCluster YAML configuration
|
|
207
|
+
*/
|
|
208
|
+
export async function handleValidate(content, options) {
|
|
209
|
+
const version = options.version || 'main';
|
|
210
|
+
|
|
211
|
+
// Validate YAML syntax first
|
|
212
|
+
let yamlData;
|
|
213
|
+
try {
|
|
214
|
+
yamlData = yaml.load(content);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
valid: false,
|
|
219
|
+
errors: [
|
|
220
|
+
{
|
|
221
|
+
path: 'root',
|
|
222
|
+
message: error.message,
|
|
223
|
+
type: 'syntax'
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
metadata: {
|
|
227
|
+
version,
|
|
228
|
+
contentLength: content.length
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Fetch schema for validation
|
|
234
|
+
try {
|
|
235
|
+
const schemaContent = await githubClient.getFileContent('chart/values.schema.json', version);
|
|
236
|
+
const fullSchema = JSON.parse(schemaContent);
|
|
237
|
+
|
|
238
|
+
// Use snippet validator
|
|
239
|
+
const result = validateSnippet(
|
|
240
|
+
content,
|
|
241
|
+
fullSchema,
|
|
242
|
+
version
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (result.valid) {
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
valid: true,
|
|
249
|
+
errors: [],
|
|
250
|
+
metadata: {
|
|
251
|
+
version,
|
|
252
|
+
contentLength: content.length
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
} else {
|
|
256
|
+
// Format errors for CLI
|
|
257
|
+
const errors = result.errors.map(err => ({
|
|
258
|
+
path: err.instancePath || err.dataPath || 'root',
|
|
259
|
+
message: err.message || 'Validation error',
|
|
260
|
+
type: err.keyword || 'validation'
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
success: true,
|
|
265
|
+
valid: false,
|
|
266
|
+
errors,
|
|
267
|
+
metadata: {
|
|
268
|
+
version,
|
|
269
|
+
contentLength: content.length
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
valid: false,
|
|
277
|
+
errors: [
|
|
278
|
+
{
|
|
279
|
+
path: 'root',
|
|
280
|
+
message: `Failed to load schema: ${error.message}`,
|
|
281
|
+
type: 'schema-error'
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
metadata: {
|
|
285
|
+
version,
|
|
286
|
+
contentLength: content.length
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|