vme-mcp-server 0.1.5 → 0.1.7

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.
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseVMIntent = void 0;
4
+ const vm_parsing_js_1 = require("./vm-parsing.js");
5
+ // Intent recognition system for natural language processing
6
+ function parseVMIntent(naturalLanguageInput) {
7
+ const input = naturalLanguageInput.toLowerCase().trim();
8
+ const result = {
9
+ action: 'unknown',
10
+ confidence: 0,
11
+ entities: {},
12
+ originalText: naturalLanguageInput
13
+ };
14
+ // Intent Classification with confidence scoring
15
+ const intentPatterns = {
16
+ create: [
17
+ /\b(create|provision|deploy|launch|start|build|make|setup)\b/,
18
+ /\bneed\s+(\d+\s+)?(vm|server|machine)/,
19
+ /\bcan\s+you\s+(create|provision|deploy)/
20
+ ],
21
+ modify: [
22
+ /\b(modify|change|update|edit|alter|resize|scale)\b/,
23
+ /\badd\s+(more\s+)?(cpu|memory|disk|storage)/,
24
+ /\bchange\s+the\s+(size|plan|configuration)/
25
+ ],
26
+ monitor: [
27
+ /\b(show|list|display|check|status|monitor|view)\b(?!.*\b(available|options|resources|templates|plans)\b)/,
28
+ /\bwhat\s+(vm|server|machine)/,
29
+ /\bhow\s+many\s+(vm|server|machine)/
30
+ ],
31
+ destroy: [
32
+ /\b(delete|remove|destroy|terminate|stop|kill)\b/,
33
+ /\btear\s+down/,
34
+ /\bshut\s+down/
35
+ ],
36
+ discover: [
37
+ /\b(available|options|resources|templates|plans)\b/,
38
+ /\bshow\s+me\s+(all|available)/,
39
+ /\bwhat\s+(are|is)\s+(available|possible)/,
40
+ /\bwhat\s+can\s+i\s+(create|do)/
41
+ ]
42
+ };
43
+ // Calculate confidence for each intent
44
+ let maxConfidence = 0;
45
+ let detectedAction = 'unknown';
46
+ for (const [action, patterns] of Object.entries(intentPatterns)) {
47
+ let matches = 0;
48
+ for (const pattern of patterns) {
49
+ if (pattern.test(input)) {
50
+ matches++;
51
+ }
52
+ }
53
+ const confidence = patterns.length > 0 ? matches / patterns.length : 0;
54
+ if (confidence > maxConfidence) {
55
+ maxConfidence = confidence;
56
+ detectedAction = action;
57
+ }
58
+ }
59
+ result.action = detectedAction;
60
+ result.confidence = maxConfidence;
61
+ // Entity Extraction for VM creation
62
+ if (result.action === 'create') {
63
+ // Extract VM names and count
64
+ const namePatterns = [
65
+ /\b(?:vm|server|machine)s?\s+(?:named\s+)?["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?/,
66
+ /\b(?:named\s+)?["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?\s+(?:vm|server|machine)/,
67
+ /\bcreate\s+(?:(?:vm|server|machine)s?\s+)?["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?/,
68
+ /\b(?:vm|server|machine)\s+named\s+["']?([a-zA-Z0-9\-_]+(?:\s*->\s*[a-zA-Z0-9\-_]+)?)["']?/
69
+ ];
70
+ for (const pattern of namePatterns) {
71
+ const match = input.match(pattern);
72
+ if (match) {
73
+ const nameInput = match[1] || match[2];
74
+ result.entities.vmNames = (0, vm_parsing_js_1.parseVMNames)(nameInput);
75
+ break;
76
+ }
77
+ }
78
+ // Extract count
79
+ const countMatch = input.match(/\b(\d+)\s+(?:vm|server|machine)/);
80
+ if (countMatch) {
81
+ result.entities.count = parseInt(countMatch[1]);
82
+ }
83
+ // Extract OS template
84
+ const osPatterns = [
85
+ /\b(ubuntu|rocky|centos|debian|rhel|red\s*hat|alma|almalinux)(?:\s+(\d+(?:\.\d+)?))?/,
86
+ /\busing\s+([a-zA-Z0-9\-_]+)\s+(?:template|os|operating)/,
87
+ /\b(?:template|os|operating)\s+([a-zA-Z0-9\-_]+)/
88
+ ];
89
+ for (const pattern of osPatterns) {
90
+ const match = input.match(pattern);
91
+ if (match) {
92
+ result.entities.osTemplate = match[0];
93
+ break;
94
+ }
95
+ }
96
+ // Extract size/memory
97
+ const sizePatterns = [
98
+ /\b(small|medium|large|tiny)\b/,
99
+ /\b(\d+)\s*gb\s+(?:memory|ram)\b/,
100
+ /\b(\d+)\s*cpu[s]?\s*,?\s*(\d+)\s*gb/,
101
+ /\bwith\s+(\d+(?:gb|cpu))\b/,
102
+ /\b(\d+gb)\b/
103
+ ];
104
+ for (const pattern of sizePatterns) {
105
+ const match = input.match(pattern);
106
+ if (match) {
107
+ result.entities.size = match[0];
108
+ break;
109
+ }
110
+ }
111
+ // Extract group/environment
112
+ const groupPatterns = [
113
+ /\bin\s+(?:the\s+)?([a-zA-Z0-9\-_]+)\s+(?:group|environment|site)/,
114
+ /\b(?:group|environment|site)\s+([a-zA-Z0-9\-_]+)/,
115
+ /\bfor\s+([a-zA-Z0-9\-_]+)\s+(?:environment|testing|production)/
116
+ ];
117
+ for (const pattern of groupPatterns) {
118
+ const match = input.match(pattern);
119
+ if (match) {
120
+ result.entities.group = match[1];
121
+ break;
122
+ }
123
+ }
124
+ // Extract zone/cloud
125
+ const zonePatterns = [
126
+ /\bon\s+([a-zA-Z0-9\-_]+)\s+(?:zone|cloud)/,
127
+ /\b(?:zone|cloud)\s+([a-zA-Z0-9\-_]+)/,
128
+ /\bin\s+([a-zA-Z0-9\-_]+)\s+(?:zone|cloud)/,
129
+ /\b(?:deploy|launch|create)\s+(?:in\s+)?([a-zA-Z0-9\-_]+)\s+zone/
130
+ ];
131
+ for (const pattern of zonePatterns) {
132
+ const match = input.match(pattern);
133
+ if (match) {
134
+ result.entities.zone = match[1];
135
+ break;
136
+ }
137
+ }
138
+ // Extract distribution strategy
139
+ if (input.includes('spread') || input.includes('distribute') || input.includes('across')) {
140
+ result.entities.distribution = 'spread';
141
+ }
142
+ else if (input.includes('each node') || input.includes('all nodes')) {
143
+ result.entities.distribution = 'spread';
144
+ }
145
+ else if (input.includes('node') && input.match(/node[s]?\s*[\d,\s]+/)) {
146
+ const nodeMatch = input.match(/node[s]?\s*([\d,\s]+)/);
147
+ if (nodeMatch) {
148
+ result.entities.distribution = nodeMatch[1].replace(/\s+/g, '');
149
+ }
150
+ }
151
+ // Boost confidence if we extracted entities
152
+ const entityCount = Object.keys(result.entities).length;
153
+ if (entityCount > 0) {
154
+ result.confidence = Math.min(0.95, result.confidence + (entityCount * 0.1));
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ exports.parseVMIntent = parseVMIntent;
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nameResolver = exports.NameResolver = void 0;
4
+ const capability_discovery_js_1 = require("./capability-discovery.js");
5
+ // Generic name->ID resolver using capability discovery data
6
+ class NameResolver {
7
+ constructor() {
8
+ this.capabilitiesCache = null;
9
+ this.cacheTimestamp = 0;
10
+ this.CACHE_TTL = 300000; // 5 minutes
11
+ }
12
+ async getCapabilities() {
13
+ const now = Date.now();
14
+ if (!this.capabilitiesCache || (now - this.cacheTimestamp) > this.CACHE_TTL) {
15
+ this.capabilitiesCache = await capability_discovery_js_1.capabilityDiscovery.discoverCapabilities(['all']);
16
+ this.cacheTimestamp = now;
17
+ }
18
+ return this.capabilitiesCache;
19
+ }
20
+ async resolveNameToId(resourceType, name) {
21
+ if (!name)
22
+ return null;
23
+ const capabilities = await this.getCapabilities();
24
+ const nameLower = name.toLowerCase();
25
+ switch (resourceType) {
26
+ case 'virtualImage':
27
+ case 'image':
28
+ return this.resolveImageName(capabilities, nameLower);
29
+ case 'group':
30
+ case 'site':
31
+ return this.resolveGroupName(capabilities, nameLower);
32
+ case 'servicePlan':
33
+ case 'plan':
34
+ return this.resolvePlanName(capabilities, nameLower);
35
+ case 'zone':
36
+ case 'cloud':
37
+ return this.resolveZoneName(capabilities, nameLower);
38
+ case 'instanceType':
39
+ return this.resolveInstanceTypeName(capabilities, nameLower);
40
+ default:
41
+ return null;
42
+ }
43
+ }
44
+ resolveImageName(capabilities, nameLower) {
45
+ const images = capabilities.compute?.available_images || [];
46
+ // Try exact name match first
47
+ let found = images.find((img) => img.name?.toLowerCase() === nameLower);
48
+ if (found)
49
+ return found.id;
50
+ // Try partial name match
51
+ found = images.find((img) => img.name?.toLowerCase().includes(nameLower));
52
+ if (found)
53
+ return found.id;
54
+ // Try category + version matching (e.g., "ubuntu 20.04" -> ubuntu-2004)
55
+ for (const img of images) {
56
+ const imgName = img.name?.toLowerCase() || '';
57
+ const category = img.category?.toLowerCase() || '';
58
+ const version = img.version?.toString() || '';
59
+ // Match patterns like "ubuntu 20.04" to "ubuntu-2004"
60
+ if (nameLower.includes(category) && nameLower.includes(version)) {
61
+ return img.id;
62
+ }
63
+ // Match patterns like "ubuntu" to first ubuntu image
64
+ if (nameLower === category) {
65
+ return img.id;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ resolveGroupName(capabilities, nameLower) {
71
+ const groups = capabilities.platform?.groups || [];
72
+ // Try exact match
73
+ let found = groups.find((g) => g.name?.toLowerCase() === nameLower);
74
+ if (found)
75
+ return found.id;
76
+ // Try partial match
77
+ found = groups.find((g) => g.name?.toLowerCase().includes(nameLower));
78
+ if (found)
79
+ return found.id;
80
+ return null;
81
+ }
82
+ resolvePlanName(capabilities, nameLower) {
83
+ const plans = capabilities.compute?.service_plans || [];
84
+ // Try exact match
85
+ let found = plans.find((p) => p.name?.toLowerCase() === nameLower);
86
+ if (found)
87
+ return found.id;
88
+ // Try size-based matching
89
+ if (nameLower.includes('small') || nameLower.includes('1 cpu') || nameLower.includes('4gb')) {
90
+ found = plans.find((p) => p.name?.toLowerCase().includes('1 cpu') ||
91
+ p.name?.toLowerCase().includes('4gb') ||
92
+ p.max_cpu === 1);
93
+ if (found)
94
+ return found.id;
95
+ }
96
+ if (nameLower.includes('medium') || nameLower.includes('2 cpu') || nameLower.includes('8gb')) {
97
+ found = plans.find((p) => p.name?.toLowerCase().includes('2 cpu') ||
98
+ p.name?.toLowerCase().includes('8gb') ||
99
+ p.max_cpu === 2);
100
+ if (found)
101
+ return found.id;
102
+ }
103
+ if (nameLower.includes('large') || nameLower.includes('4 cpu') || nameLower.includes('16gb')) {
104
+ found = plans.find((p) => p.name?.toLowerCase().includes('4 cpu') ||
105
+ p.name?.toLowerCase().includes('16gb') ||
106
+ p.max_cpu === 4);
107
+ if (found)
108
+ return found.id;
109
+ }
110
+ // Try partial match
111
+ found = plans.find((p) => p.name?.toLowerCase().includes(nameLower));
112
+ if (found)
113
+ return found.id;
114
+ return null;
115
+ }
116
+ resolveZoneName(capabilities, nameLower) {
117
+ const zones = capabilities.networking?.zones || [];
118
+ // Try exact match
119
+ let found = zones.find((z) => z.name?.toLowerCase() === nameLower);
120
+ if (found)
121
+ return found.id;
122
+ // Try partial match
123
+ found = zones.find((z) => z.name?.toLowerCase().includes(nameLower));
124
+ if (found)
125
+ return found.id;
126
+ return null;
127
+ }
128
+ resolveInstanceTypeName(capabilities, nameLower) {
129
+ const instanceTypes = capabilities.compute?.instance_types || [];
130
+ // Try exact match
131
+ let found = instanceTypes.find((t) => t.name?.toLowerCase() === nameLower);
132
+ if (found)
133
+ return found.id;
134
+ // Try code match
135
+ found = instanceTypes.find((t) => t.code?.toLowerCase() === nameLower);
136
+ if (found)
137
+ return found.id;
138
+ // Try partial match
139
+ found = instanceTypes.find((t) => t.name?.toLowerCase().includes(nameLower));
140
+ if (found)
141
+ return found.id;
142
+ return null;
143
+ }
144
+ // Helper method to get available names for a resource type
145
+ async getAvailableNames(resourceType) {
146
+ const capabilities = await this.getCapabilities();
147
+ switch (resourceType) {
148
+ case 'virtualImage':
149
+ return capabilities.compute?.available_images?.map((img) => img.name) || [];
150
+ case 'group':
151
+ return capabilities.platform?.groups?.map((g) => g.name) || [];
152
+ case 'servicePlan':
153
+ return capabilities.compute?.service_plans?.map((p) => p.name) || [];
154
+ case 'zone':
155
+ return capabilities.networking?.zones?.map((z) => z.name) || [];
156
+ case 'instanceType':
157
+ return capabilities.compute?.instance_types?.map((t) => t.name) || [];
158
+ default:
159
+ return [];
160
+ }
161
+ }
162
+ // Helper to resolve multiple resources at once
163
+ async resolveMultiple(resources) {
164
+ const results = {};
165
+ for (const resource of resources) {
166
+ const key = `${resource.type}_${resource.name}`;
167
+ results[key] = await this.resolveNameToId(resource.type, resource.name);
168
+ }
169
+ return results;
170
+ }
171
+ }
172
+ exports.NameResolver = NameResolver;
173
+ // Global resolver instance
174
+ exports.nameResolver = new NameResolver();
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logInteraction = exports.generateSessionId = void 0;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ // AI Training Data Collection Configuration
7
+ const AI_TRAINING_ENABLED = process.env.ENABLE_AI_TRAINING_DATA === 'true';
8
+ const AI_TRAINING_FIELDS = (process.env.AI_TRAINING_DATA_FIELDS || 'user_input,parsed_output,success_metrics').split(',');
9
+ const AI_TRAINING_RETENTION_DAYS = parseInt(process.env.AI_TRAINING_DATA_RETENTION_DAYS || '30');
10
+ // Generate session ID for interaction tracking
11
+ function generateSessionId() {
12
+ return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
13
+ }
14
+ exports.generateSessionId = generateSessionId;
15
+ // Privacy-aware interaction logging
16
+ function logInteraction(interaction) {
17
+ if (!AI_TRAINING_ENABLED) {
18
+ return; // Respect user privacy choice
19
+ }
20
+ try {
21
+ // Ensure logs directory exists
22
+ const logsDir = (0, path_1.join)(process.cwd(), 'ai-training-logs');
23
+ if (!(0, fs_1.existsSync)(logsDir)) {
24
+ (0, fs_1.mkdirSync)(logsDir, { recursive: true });
25
+ }
26
+ // Filter fields based on user preferences
27
+ const filteredInteraction = {
28
+ id: interaction.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
29
+ timestamp: new Date().toISOString(),
30
+ session_id: interaction.session_id || 'unknown',
31
+ tool_name: interaction.tool_name || 'unknown'
32
+ };
33
+ // Only include fields user has opted into
34
+ if (AI_TRAINING_FIELDS.includes('user_input') && interaction.user_input) {
35
+ filteredInteraction.user_input = interaction.user_input;
36
+ }
37
+ if (AI_TRAINING_FIELDS.includes('parsed_output') && interaction.parsed_output) {
38
+ filteredInteraction.parsed_output = interaction.parsed_output;
39
+ }
40
+ if (AI_TRAINING_FIELDS.includes('api_calls') && interaction.api_calls) {
41
+ filteredInteraction.api_calls = interaction.api_calls;
42
+ }
43
+ if (AI_TRAINING_FIELDS.includes('success_metrics') && interaction.success_metrics) {
44
+ filteredInteraction.success_metrics = interaction.success_metrics;
45
+ }
46
+ if (AI_TRAINING_FIELDS.includes('timing') && interaction.timing) {
47
+ filteredInteraction.timing = interaction.timing;
48
+ }
49
+ // Append to daily log file (JSONL format)
50
+ const logFile = (0, path_1.join)(logsDir, `interactions-${new Date().toISOString().split('T')[0]}.jsonl`);
51
+ const logEntry = JSON.stringify(filteredInteraction) + '\n';
52
+ (0, fs_1.writeFileSync)(logFile, logEntry, { flag: 'a' });
53
+ }
54
+ catch (error) {
55
+ // Fail silently to not disrupt user experience
56
+ console.warn('AI training data logging failed:', error);
57
+ }
58
+ }
59
+ exports.logInteraction = logInteraction;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateNodeAssignments = exports.parseVMNames = void 0;
4
+ // Helper function to parse VM name patterns
5
+ function parseVMNames(nameInput, count) {
6
+ // Handle range patterns like "web01->web03" or "web01 -> web03"
7
+ const rangeMatch = nameInput.match(/^(.+?)(\d+)\s*->\s*(.+?)(\d+)$/);
8
+ if (rangeMatch) {
9
+ const [, prefix1, start, prefix2, end] = rangeMatch;
10
+ if (prefix1.trim() === prefix2.trim()) {
11
+ const basePrefix = prefix1.trim();
12
+ const startNum = parseInt(start);
13
+ const endNum = parseInt(end);
14
+ const names = [];
15
+ for (let i = startNum; i <= endNum; i++) {
16
+ const paddedNum = i.toString().padStart(start.length, '0');
17
+ names.push(`${basePrefix}${paddedNum}`);
18
+ }
19
+ return names;
20
+ }
21
+ }
22
+ // Handle count-based generation
23
+ if (count && count > 1) {
24
+ const names = [];
25
+ // Extract base name and number pattern
26
+ const match = nameInput.match(/^(.+?)(\d+)$/);
27
+ if (match) {
28
+ const [, prefix, startStr] = match;
29
+ const startNum = parseInt(startStr);
30
+ for (let i = 0; i < count; i++) {
31
+ const num = startNum + i;
32
+ const paddedNum = num.toString().padStart(startStr.length, '0');
33
+ names.push(`${prefix}${paddedNum}`);
34
+ }
35
+ return names;
36
+ }
37
+ else {
38
+ // If no number pattern, append numbers
39
+ for (let i = 1; i <= count; i++) {
40
+ const paddedNum = i.toString().padStart(2, '0');
41
+ names.push(`${nameInput}${paddedNum}`);
42
+ }
43
+ return names;
44
+ }
45
+ }
46
+ // Single VM
47
+ return [nameInput];
48
+ }
49
+ exports.parseVMNames = parseVMNames;
50
+ // Determine node assignment strategy for VMs
51
+ function calculateNodeAssignments(vmNames, nodes, distribution) {
52
+ let nodeAssignments = [];
53
+ if (distribution === 'spread' || (vmNames.length > 1 && !distribution)) {
54
+ // Distribute VMs across all available nodes
55
+ vmNames.forEach((vmName, index) => {
56
+ const nodeId = nodes[index % nodes.length];
57
+ nodeAssignments.push({ name: vmName, kvmHostId: nodeId });
58
+ });
59
+ }
60
+ else if (distribution && distribution !== 'auto') {
61
+ // Parse specific node assignments like "1,2,3" or "node1,node2,node3"
62
+ const specifiedNodes = distribution.split(',').map((n) => {
63
+ const trimmed = n.trim();
64
+ return trimmed.startsWith('node') ? parseInt(trimmed.replace('node', '')) : parseInt(trimmed);
65
+ }).filter((n) => !isNaN(n));
66
+ if (specifiedNodes.length > 0) {
67
+ vmNames.forEach((vmName, index) => {
68
+ const nodeId = specifiedNodes[index % specifiedNodes.length];
69
+ nodeAssignments.push({ name: vmName, kvmHostId: nodeId });
70
+ });
71
+ }
72
+ else {
73
+ nodeAssignments = vmNames.map(vmName => ({ name: vmName }));
74
+ }
75
+ }
76
+ else {
77
+ // Auto-placement (no kvmHostId specified)
78
+ nodeAssignments = vmNames.map(vmName => ({ name: vmName }));
79
+ }
80
+ return nodeAssignments;
81
+ }
82
+ exports.calculateNodeAssignments = calculateNodeAssignments;