langmart-gateway-type3 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.env.example +29 -0
  2. package/README.md +480 -0
  3. package/dist/bash-tools.d.ts +56 -0
  4. package/dist/bash-tools.d.ts.map +1 -0
  5. package/dist/bash-tools.js +188 -0
  6. package/dist/bash-tools.js.map +1 -0
  7. package/dist/core-tools.d.ts +94 -0
  8. package/dist/core-tools.d.ts.map +1 -0
  9. package/dist/core-tools.js +694 -0
  10. package/dist/core-tools.js.map +1 -0
  11. package/dist/debug-utils.d.ts +22 -0
  12. package/dist/debug-utils.d.ts.map +1 -0
  13. package/dist/debug-utils.js +37 -0
  14. package/dist/debug-utils.js.map +1 -0
  15. package/dist/devops-tools.d.ts +147 -0
  16. package/dist/devops-tools.d.ts.map +1 -0
  17. package/dist/devops-tools.js +718 -0
  18. package/dist/devops-tools.js.map +1 -0
  19. package/dist/gateway-config.d.ts +56 -0
  20. package/dist/gateway-config.d.ts.map +1 -0
  21. package/dist/gateway-config.js +198 -0
  22. package/dist/gateway-config.js.map +1 -0
  23. package/dist/gateway-mode.d.ts +58 -0
  24. package/dist/gateway-mode.d.ts.map +1 -0
  25. package/dist/gateway-mode.js +240 -0
  26. package/dist/gateway-mode.js.map +1 -0
  27. package/dist/gateway-server.d.ts +208 -0
  28. package/dist/gateway-server.d.ts.map +1 -0
  29. package/dist/gateway-server.js +1811 -0
  30. package/dist/gateway-server.js.map +1 -0
  31. package/dist/headless-session.d.ts +192 -0
  32. package/dist/headless-session.d.ts.map +1 -0
  33. package/dist/headless-session.js +584 -0
  34. package/dist/headless-session.js.map +1 -0
  35. package/dist/index-server.d.ts +4 -0
  36. package/dist/index-server.d.ts.map +1 -0
  37. package/dist/index-server.js +129 -0
  38. package/dist/index-server.js.map +1 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +101 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/key-vault.d.ts +102 -0
  44. package/dist/key-vault.d.ts.map +1 -0
  45. package/dist/key-vault.js +365 -0
  46. package/dist/key-vault.js.map +1 -0
  47. package/dist/local-vault.d.ts +195 -0
  48. package/dist/local-vault.d.ts.map +1 -0
  49. package/dist/local-vault.js +571 -0
  50. package/dist/local-vault.js.map +1 -0
  51. package/dist/marketplace-tools.d.ts +104 -0
  52. package/dist/marketplace-tools.d.ts.map +1 -0
  53. package/dist/marketplace-tools.js +2846 -0
  54. package/dist/marketplace-tools.js.map +1 -0
  55. package/dist/mcp-manager.d.ts +114 -0
  56. package/dist/mcp-manager.d.ts.map +1 -0
  57. package/dist/mcp-manager.js +338 -0
  58. package/dist/mcp-manager.js.map +1 -0
  59. package/dist/web-tools.d.ts +86 -0
  60. package/dist/web-tools.d.ts.map +1 -0
  61. package/dist/web-tools.js +431 -0
  62. package/dist/web-tools.js.map +1 -0
  63. package/dist/websocket-handler.d.ts +131 -0
  64. package/dist/websocket-handler.d.ts.map +1 -0
  65. package/dist/websocket-handler.js +596 -0
  66. package/dist/websocket-handler.js.map +1 -0
  67. package/dist/welcome-pages.d.ts +6 -0
  68. package/dist/welcome-pages.d.ts.map +1 -0
  69. package/dist/welcome-pages.js +200 -0
  70. package/dist/welcome-pages.js.map +1 -0
  71. package/package.json +168 -0
  72. package/scripts/install-remote.sh +282 -0
  73. package/scripts/start.sh +85 -0
  74. package/scripts/status.sh +79 -0
  75. package/scripts/stop.sh +67 -0
@@ -0,0 +1,718 @@
1
+ "use strict";
2
+ /**
3
+ * DevOps Tools for Gateway Type 3
4
+ *
5
+ * Provides MCP tools for remote server management and script execution.
6
+ * These tools interact with the Gateway Type 1 API to:
7
+ * - List available remote servers
8
+ * - Test server connectivity
9
+ * - List available scripts
10
+ * - Execute scripts on remote servers
11
+ * - View script execution logs
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.DevOpsTools = void 0;
18
+ const axios_1 = __importDefault(require("axios"));
19
+ const child_process_1 = require("child_process");
20
+ const util_1 = require("util");
21
+ const promises_1 = require("fs/promises");
22
+ const path_1 = require("path");
23
+ const os_1 = require("os");
24
+ const crypto_1 = require("crypto");
25
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
26
+ class DevOpsTools {
27
+ constructor(marketplaceUrl, apiKey) {
28
+ this.sessionId = null;
29
+ this.serverMap = new Map(); // sequence number → UUID
30
+ this.scriptMap = new Map(); // sequence number → slug
31
+ this.marketplaceUrl = marketplaceUrl;
32
+ this.apiKey = apiKey;
33
+ this.client = axios_1.default.create({
34
+ baseURL: marketplaceUrl,
35
+ timeout: 60000,
36
+ headers: {
37
+ 'Authorization': `Bearer ${apiKey}`,
38
+ 'Content-Type': 'application/json'
39
+ }
40
+ });
41
+ }
42
+ static getInstance(marketplaceUrl, apiKey) {
43
+ if (!DevOpsTools.instance && marketplaceUrl && apiKey) {
44
+ DevOpsTools.instance = new DevOpsTools(marketplaceUrl, apiKey);
45
+ }
46
+ if (!DevOpsTools.instance) {
47
+ throw new Error('DevOpsTools not initialized. Provide marketplaceUrl and apiKey.');
48
+ }
49
+ return DevOpsTools.instance;
50
+ }
51
+ setSessionId(sessionId) {
52
+ this.sessionId = sessionId;
53
+ }
54
+ /**
55
+ * Get tool definitions for AI
56
+ */
57
+ getTools() {
58
+ return [
59
+ // Server management tools
60
+ {
61
+ type: 'function',
62
+ function: {
63
+ name: 'devops.servers.list',
64
+ description: 'List all remote servers configured for deployments. Shows server names, hostnames, status, and connection info WITHOUT exposing credentials. Servers are numbered [1], [2], [3], etc. Use these numbers in subsequent tool calls.',
65
+ parameters: {
66
+ type: 'object',
67
+ properties: {
68
+ status: {
69
+ type: 'string',
70
+ enum: ['active', 'inactive', 'failed', 'all'],
71
+ description: 'Filter by status (default: all)'
72
+ },
73
+ tags: {
74
+ type: 'string',
75
+ description: 'Comma-separated tags to filter by'
76
+ }
77
+ },
78
+ required: []
79
+ }
80
+ }
81
+ },
82
+ {
83
+ type: 'function',
84
+ function: {
85
+ name: 'devops.servers.test_connection',
86
+ description: 'Test SSH connectivity to a remote server. Returns success/failure with diagnostics and latency.',
87
+ parameters: {
88
+ type: 'object',
89
+ properties: {
90
+ server_id: {
91
+ type: 'string',
92
+ description: 'Server ID or sequence number (e.g., "1" for the first server from devops.servers.list)'
93
+ }
94
+ },
95
+ required: ['server_id']
96
+ }
97
+ }
98
+ },
99
+ {
100
+ type: 'function',
101
+ function: {
102
+ name: 'devops.servers.get_info',
103
+ description: 'Get detailed information about a specific server (without credentials).',
104
+ parameters: {
105
+ type: 'object',
106
+ properties: {
107
+ server_id: {
108
+ type: 'string',
109
+ description: 'Server ID or sequence number'
110
+ }
111
+ },
112
+ required: ['server_id']
113
+ }
114
+ }
115
+ },
116
+ // Script management tools
117
+ {
118
+ type: 'function',
119
+ function: {
120
+ name: 'devops.scripts.list',
121
+ description: 'List all available automation scripts. Scripts are numbered [1], [2], etc. Use script slugs or numbers in execute commands. Shows name, description, category, and required parameters.',
122
+ parameters: {
123
+ type: 'object',
124
+ properties: {
125
+ category: {
126
+ type: 'string',
127
+ enum: ['deployment', 'monitoring', 'maintenance', 'custom', 'all'],
128
+ description: 'Filter by category (default: all)'
129
+ }
130
+ },
131
+ required: []
132
+ }
133
+ }
134
+ },
135
+ {
136
+ type: 'function',
137
+ function: {
138
+ name: 'devops.scripts.get_info',
139
+ description: 'Get detailed information about a script including its full content and parameter definitions.',
140
+ parameters: {
141
+ type: 'object',
142
+ properties: {
143
+ script: {
144
+ type: 'string',
145
+ description: 'Script slug (e.g., "deploy-type3-gateway") or sequence number from devops.scripts.list'
146
+ }
147
+ },
148
+ required: ['script']
149
+ }
150
+ }
151
+ },
152
+ {
153
+ type: 'function',
154
+ function: {
155
+ name: 'devops.scripts.execute',
156
+ description: 'Execute a script on a remote server. The script is retrieved from the database and executed via SSH. Parameters are passed as environment variables. Returns execution output and status.',
157
+ parameters: {
158
+ type: 'object',
159
+ properties: {
160
+ script: {
161
+ type: 'string',
162
+ description: 'Script slug (e.g., "deploy-type3-gateway") or sequence number'
163
+ },
164
+ server_id: {
165
+ type: 'string',
166
+ description: 'Target server ID or sequence number from devops.servers.list'
167
+ },
168
+ parameters: {
169
+ type: 'object',
170
+ description: 'Script parameters as key-value pairs (e.g., {"GATEWAY_NAME": "my-gateway", "GATEWAY_PORT": 8083})'
171
+ }
172
+ },
173
+ required: ['script', 'server_id']
174
+ }
175
+ }
176
+ },
177
+ {
178
+ type: 'function',
179
+ function: {
180
+ name: 'devops.scripts.get_execution_logs',
181
+ description: 'Get execution history and logs for a script or server. Shows recent executions with status, output, and timing.',
182
+ parameters: {
183
+ type: 'object',
184
+ properties: {
185
+ script: {
186
+ type: 'string',
187
+ description: 'Script slug or ID to filter by (optional)'
188
+ },
189
+ server_id: {
190
+ type: 'string',
191
+ description: 'Server ID to filter by (optional)'
192
+ },
193
+ limit: {
194
+ type: 'number',
195
+ description: 'Number of recent executions to show (default: 10)'
196
+ }
197
+ },
198
+ required: []
199
+ }
200
+ }
201
+ }
202
+ ];
203
+ }
204
+ /**
205
+ * Resolve server ID (handle sequence numbers like "1", "2", etc.)
206
+ */
207
+ resolveServerId(serverIdOrNumber) {
208
+ const num = parseInt(serverIdOrNumber);
209
+ if (!isNaN(num) && this.serverMap.has(num)) {
210
+ return this.serverMap.get(num);
211
+ }
212
+ return serverIdOrNumber;
213
+ }
214
+ /**
215
+ * Resolve script slug (handle sequence numbers like "1", "2", etc.)
216
+ */
217
+ resolveScriptSlug(scriptOrNumber) {
218
+ const num = parseInt(scriptOrNumber);
219
+ if (!isNaN(num) && this.scriptMap.has(num)) {
220
+ return this.scriptMap.get(num);
221
+ }
222
+ return scriptOrNumber;
223
+ }
224
+ /**
225
+ * Execute a DevOps tool
226
+ */
227
+ async executeTool(toolName, args) {
228
+ try {
229
+ switch (toolName) {
230
+ case 'devops.servers.list':
231
+ return await this.listServers(args);
232
+ case 'devops.servers.test_connection':
233
+ return await this.testServerConnection(args);
234
+ case 'devops.servers.get_info':
235
+ return await this.getServerInfo(args);
236
+ case 'devops.scripts.list':
237
+ return await this.listScripts(args);
238
+ case 'devops.scripts.get_info':
239
+ return await this.getScriptInfo(args);
240
+ case 'devops.scripts.execute':
241
+ return await this.executeScript(args);
242
+ case 'devops.scripts.get_execution_logs':
243
+ return await this.getExecutionLogs(args);
244
+ default:
245
+ return { success: false, error: `Unknown tool: ${toolName}` };
246
+ }
247
+ }
248
+ catch (error) {
249
+ console.error(`[DevOpsTools] Error executing ${toolName}:`, error.message);
250
+ return { success: false, error: error.message };
251
+ }
252
+ }
253
+ // ============= SERVER TOOLS =============
254
+ /**
255
+ * List remote servers
256
+ */
257
+ async listServers(args) {
258
+ try {
259
+ const params = new URLSearchParams();
260
+ if (args.status && args.status !== 'all')
261
+ params.append('status', args.status);
262
+ if (args.tags)
263
+ params.append('tags', args.tags);
264
+ const query = params.toString() ? `?${params.toString()}` : '';
265
+ const response = await this.client.get(`/api/v1/servers${query}`);
266
+ if (!response.data.success) {
267
+ return { success: false, error: response.data.error || 'Failed to list servers' };
268
+ }
269
+ const servers = response.data.servers;
270
+ // Update server map
271
+ this.serverMap.clear();
272
+ servers.forEach((server, index) => {
273
+ this.serverMap.set(index + 1, server.id);
274
+ });
275
+ if (servers.length === 0) {
276
+ return {
277
+ success: true,
278
+ output: 'No remote servers found. Use the Remote Servers page in the web UI to add deployment targets.'
279
+ };
280
+ }
281
+ // Format output
282
+ let output = `Found ${servers.length} remote server(s):\n\n`;
283
+ servers.forEach((server, index) => {
284
+ const statusIcon = server.status === 'active' ? '✅' :
285
+ server.status === 'testing' ? '🔄' :
286
+ server.status === 'failed' ? '❌' : '⚪';
287
+ output += `[${index + 1}] ${statusIcon} ${server.name}\n`;
288
+ output += ` Host: ${server.username}@${server.hostname}:${server.port}\n`;
289
+ output += ` Auth: ${server.auth_type}\n`;
290
+ output += ` Status: ${server.status}`;
291
+ if (server.last_connection_status) {
292
+ output += ` (last test: ${server.last_connection_status})`;
293
+ }
294
+ output += '\n';
295
+ if (server.description) {
296
+ output += ` Description: ${server.description}\n`;
297
+ }
298
+ if (server.tags && server.tags.length > 0) {
299
+ output += ` Tags: ${server.tags.join(', ')}\n`;
300
+ }
301
+ output += '\n';
302
+ });
303
+ output += 'Use server numbers (e.g., 1, 2) in subsequent commands.';
304
+ return { success: true, output };
305
+ }
306
+ catch (error) {
307
+ return { success: false, error: error.response?.data?.error || error.message };
308
+ }
309
+ }
310
+ /**
311
+ * Test server connection
312
+ */
313
+ async testServerConnection(args) {
314
+ try {
315
+ const serverId = this.resolveServerId(args.server_id);
316
+ const response = await this.client.post(`/api/v1/servers/${serverId}/test`);
317
+ if (response.data.success) {
318
+ return {
319
+ success: true,
320
+ output: `✅ Connection successful!\n\nMessage: ${response.data.message}\nLatency: ${response.data.latency_ms}ms`
321
+ };
322
+ }
323
+ else {
324
+ return {
325
+ success: false,
326
+ error: `Connection failed: ${response.data.message}`
327
+ };
328
+ }
329
+ }
330
+ catch (error) {
331
+ return { success: false, error: error.response?.data?.error || error.message };
332
+ }
333
+ }
334
+ /**
335
+ * Get server info
336
+ */
337
+ async getServerInfo(args) {
338
+ try {
339
+ const serverId = this.resolveServerId(args.server_id);
340
+ const response = await this.client.get(`/api/v1/servers/${serverId}`);
341
+ if (!response.data.success) {
342
+ return { success: false, error: response.data.error || 'Server not found' };
343
+ }
344
+ const server = response.data.server;
345
+ let output = `Server: ${server.name}\n`;
346
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
347
+ output += `ID: ${server.id}\n`;
348
+ output += `Hostname: ${server.hostname}\n`;
349
+ output += `Port: ${server.port}\n`;
350
+ output += `Username: ${server.username}\n`;
351
+ output += `Auth Type: ${server.auth_type}\n`;
352
+ output += `Status: ${server.status}\n`;
353
+ if (server.description) {
354
+ output += `Description: ${server.description}\n`;
355
+ }
356
+ if (server.tags && server.tags.length > 0) {
357
+ output += `Tags: ${server.tags.join(', ')}\n`;
358
+ }
359
+ if (server.last_connection_test) {
360
+ output += `Last Test: ${new Date(server.last_connection_test).toLocaleString()}\n`;
361
+ output += `Test Result: ${server.last_connection_status}\n`;
362
+ }
363
+ return { success: true, output };
364
+ }
365
+ catch (error) {
366
+ return { success: false, error: error.response?.data?.error || error.message };
367
+ }
368
+ }
369
+ // ============= SCRIPT TOOLS =============
370
+ /**
371
+ * List available scripts
372
+ */
373
+ async listScripts(args) {
374
+ try {
375
+ const params = new URLSearchParams();
376
+ if (args.category && args.category !== 'all')
377
+ params.append('category', args.category);
378
+ const query = params.toString() ? `?${params.toString()}` : '';
379
+ const response = await this.client.get(`/api/v1/scripts${query}`);
380
+ if (!response.data.success) {
381
+ return { success: false, error: response.data.error || 'Failed to list scripts' };
382
+ }
383
+ const scripts = response.data.scripts;
384
+ // Update script map
385
+ this.scriptMap.clear();
386
+ scripts.forEach((script, index) => {
387
+ this.scriptMap.set(index + 1, script.slug);
388
+ });
389
+ if (scripts.length === 0) {
390
+ return {
391
+ success: true,
392
+ output: 'No scripts found. Use the Scripts page in the web UI to create automation scripts.'
393
+ };
394
+ }
395
+ // Format output
396
+ let output = `Found ${scripts.length} script(s):\n\n`;
397
+ // Group by category
398
+ const categories = new Map();
399
+ scripts.forEach(script => {
400
+ if (!categories.has(script.category)) {
401
+ categories.set(script.category, []);
402
+ }
403
+ categories.get(script.category).push(script);
404
+ });
405
+ let globalIndex = 0;
406
+ for (const [category, categoryScripts] of categories) {
407
+ output += `📁 ${category.toUpperCase()}\n`;
408
+ for (const script of categoryScripts) {
409
+ globalIndex++;
410
+ const systemBadge = script.is_system ? ' [system]' : '';
411
+ output += ` [${globalIndex}] ${script.name}${systemBadge}\n`;
412
+ output += ` Slug: ${script.slug}\n`;
413
+ output += ` ${script.description}\n`;
414
+ if (script.parameters && script.parameters.length > 0) {
415
+ const required = script.parameters.filter(p => p.required).map(p => p.name);
416
+ const optional = script.parameters.filter(p => !p.required).map(p => p.name);
417
+ if (required.length > 0) {
418
+ output += ` Required params: ${required.join(', ')}\n`;
419
+ }
420
+ if (optional.length > 0) {
421
+ output += ` Optional params: ${optional.join(', ')}\n`;
422
+ }
423
+ }
424
+ output += '\n';
425
+ }
426
+ }
427
+ output += 'Use devops.scripts.get_info to see full script details.\n';
428
+ output += 'Use devops.scripts.execute to run a script on a server.';
429
+ return { success: true, output };
430
+ }
431
+ catch (error) {
432
+ return { success: false, error: error.response?.data?.error || error.message };
433
+ }
434
+ }
435
+ /**
436
+ * Get script info
437
+ */
438
+ async getScriptInfo(args) {
439
+ try {
440
+ const scriptSlug = this.resolveScriptSlug(args.script);
441
+ const response = await this.client.get(`/api/v1/scripts/${scriptSlug}`);
442
+ if (!response.data.success) {
443
+ return { success: false, error: response.data.error || 'Script not found' };
444
+ }
445
+ const script = response.data.script;
446
+ let output = `Script: ${script.name}\n`;
447
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
448
+ output += `Slug: ${script.slug}\n`;
449
+ output += `Category: ${script.category}\n`;
450
+ output += `Description: ${script.description}\n`;
451
+ output += `Timeout: ${script.timeout_seconds} seconds\n`;
452
+ if (script.tags && script.tags.length > 0) {
453
+ output += `Tags: ${script.tags.join(', ')}\n`;
454
+ }
455
+ output += `System Script: ${script.is_system ? 'Yes' : 'No'}\n`;
456
+ output += '\n';
457
+ // Parameters
458
+ if (script.parameters && script.parameters.length > 0) {
459
+ output += `Parameters:\n`;
460
+ for (const param of script.parameters) {
461
+ const required = param.required ? '*' : '';
462
+ const defaultVal = param.default !== undefined ? ` (default: ${param.default})` : '';
463
+ const sensitive = param.sensitive ? ' [SENSITIVE]' : '';
464
+ output += ` ${param.name}${required}: ${param.type}${defaultVal}${sensitive}\n`;
465
+ output += ` ${param.description}\n`;
466
+ }
467
+ output += '\n';
468
+ }
469
+ // Script content preview
470
+ output += `Script Content:\n`;
471
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
472
+ output += script.script_content;
473
+ output += '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
474
+ return { success: true, output };
475
+ }
476
+ catch (error) {
477
+ return { success: false, error: error.response?.data?.error || error.message };
478
+ }
479
+ }
480
+ /**
481
+ * Execute a script on a remote server
482
+ */
483
+ async executeScript(args) {
484
+ const scriptSlug = this.resolveScriptSlug(args.script);
485
+ const serverId = this.resolveServerId(args.server_id);
486
+ const params = args.parameters || {};
487
+ let output = `🚀 Executing Script\n`;
488
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
489
+ try {
490
+ // Step 1: Get script info
491
+ output += `[1/4] Retrieving script: ${scriptSlug}...\n`;
492
+ const scriptResponse = await this.client.get(`/api/v1/scripts/${scriptSlug}`);
493
+ if (!scriptResponse.data.success) {
494
+ throw new Error(scriptResponse.data.error || 'Script not found');
495
+ }
496
+ const script = scriptResponse.data.script;
497
+ output += ` ✓ Script: ${script.name}\n\n`;
498
+ // Step 2: Validate required parameters
499
+ output += `[2/4] Validating parameters...\n`;
500
+ const missingParams = [];
501
+ for (const param of script.parameters || []) {
502
+ if (param.required && params[param.name] === undefined) {
503
+ missingParams.push(param.name);
504
+ }
505
+ }
506
+ if (missingParams.length > 0) {
507
+ throw new Error(`Missing required parameters: ${missingParams.join(', ')}`);
508
+ }
509
+ output += ` ✓ Parameters validated\n\n`;
510
+ // Step 3: Get server info and credentials
511
+ output += `[3/4] Connecting to server...\n`;
512
+ const serverResponse = await this.client.get(`/api/v1/servers/${serverId}`);
513
+ if (!serverResponse.data.success) {
514
+ throw new Error(serverResponse.data.error || 'Server not found');
515
+ }
516
+ const server = serverResponse.data.server;
517
+ output += ` Target: ${server.username}@${server.hostname}:${server.port}\n`;
518
+ const credentials = await this.getServerCredentials(serverId);
519
+ output += ` ✓ Credentials retrieved\n\n`;
520
+ // Step 4: Execute script
521
+ output += `[4/4] Executing script on remote server...\n`;
522
+ output += ` Timeout: ${script.timeout_seconds} seconds\n\n`;
523
+ // Create log entry
524
+ const logId = await this.createExecutionLog(script.id, serverId, params);
525
+ // Build script with parameters as environment variables
526
+ const fullScript = this.buildScriptWithParams(script.script_content, params);
527
+ // Execute on remote server
528
+ const result = await this.executeRemoteScript(credentials, fullScript, script.timeout_seconds * 1000);
529
+ // Update log with results
530
+ await this.updateExecutionLog(logId, result);
531
+ if (result.success) {
532
+ output += `═══════════════════════════════════════════\n`;
533
+ output += `✅ EXECUTION SUCCESSFUL\n`;
534
+ output += `═══════════════════════════════════════════\n\n`;
535
+ output += `Output:\n${result.output || '(no output)'}\n`;
536
+ }
537
+ else {
538
+ output += `═══════════════════════════════════════════\n`;
539
+ output += `❌ EXECUTION FAILED\n`;
540
+ output += `═══════════════════════════════════════════\n\n`;
541
+ output += `Error: ${result.error}\n`;
542
+ if (result.output) {
543
+ output += `Output:\n${result.output}\n`;
544
+ }
545
+ }
546
+ return { success: result.success, output, error: result.error };
547
+ }
548
+ catch (error) {
549
+ output += `\n❌ Error: ${error.message}\n`;
550
+ return { success: false, output, error: error.message };
551
+ }
552
+ }
553
+ /**
554
+ * Get execution logs
555
+ */
556
+ async getExecutionLogs(args) {
557
+ try {
558
+ const params = new URLSearchParams();
559
+ if (args.script) {
560
+ params.append('script', this.resolveScriptSlug(args.script));
561
+ }
562
+ if (args.server_id) {
563
+ params.append('server_id', this.resolveServerId(args.server_id));
564
+ }
565
+ params.append('limit', String(args.limit || 10));
566
+ const query = `?${params.toString()}`;
567
+ const response = await this.client.get(`/api/v1/scripts/executions${query}`);
568
+ if (!response.data.success) {
569
+ return { success: false, error: response.data.error || 'Failed to get execution logs' };
570
+ }
571
+ const logs = response.data.logs;
572
+ if (logs.length === 0) {
573
+ return {
574
+ success: true,
575
+ output: 'No execution logs found.'
576
+ };
577
+ }
578
+ let output = `Recent Executions (${logs.length}):\n\n`;
579
+ for (const log of logs) {
580
+ const statusIcon = log.status === 'success' ? '✅' :
581
+ log.status === 'failed' ? '❌' :
582
+ log.status === 'running' ? '🔄' : '⚪';
583
+ output += `${statusIcon} ${new Date(log.started_at).toLocaleString()}\n`;
584
+ output += ` Script: ${log.script_id}\n`;
585
+ output += ` Server: ${log.server_id}\n`;
586
+ output += ` Status: ${log.status}`;
587
+ if (log.exit_code !== undefined) {
588
+ output += ` (exit code: ${log.exit_code})`;
589
+ }
590
+ if (log.duration_ms) {
591
+ output += ` - ${log.duration_ms}ms`;
592
+ }
593
+ output += '\n';
594
+ if (log.stdout) {
595
+ const preview = log.stdout.substring(0, 200);
596
+ output += ` Output: ${preview}${log.stdout.length > 200 ? '...' : ''}\n`;
597
+ }
598
+ if (log.stderr) {
599
+ const preview = log.stderr.substring(0, 200);
600
+ output += ` Errors: ${preview}${log.stderr.length > 200 ? '...' : ''}\n`;
601
+ }
602
+ output += '\n';
603
+ }
604
+ return { success: true, output };
605
+ }
606
+ catch (error) {
607
+ return { success: false, error: error.response?.data?.error || error.message };
608
+ }
609
+ }
610
+ // ============= HELPER METHODS =============
611
+ /**
612
+ * Get server credentials (internal use only)
613
+ */
614
+ async getServerCredentials(serverId) {
615
+ const response = await this.client.get(`/api/v1/servers/${serverId}/credentials`, {
616
+ headers: {
617
+ 'X-DevOps-Session-Id': this.sessionId || 'cli-session'
618
+ }
619
+ });
620
+ if (!response.data.success) {
621
+ throw new Error(response.data.error || 'Failed to get server credentials');
622
+ }
623
+ return response.data.credentials;
624
+ }
625
+ /**
626
+ * Build script with parameters as environment variables
627
+ */
628
+ buildScriptWithParams(scriptContent, params) {
629
+ // Build export statements for parameters
630
+ let exports = '';
631
+ for (const [key, value] of Object.entries(params)) {
632
+ // Escape single quotes in value
633
+ const escapedValue = String(value).replace(/'/g, "'\"'\"'");
634
+ exports += `export ${key}='${escapedValue}'\n`;
635
+ }
636
+ // Prepend exports to script
637
+ return `#!/bin/bash\n${exports}\n${scriptContent}`;
638
+ }
639
+ /**
640
+ * Create execution log entry
641
+ */
642
+ async createExecutionLog(scriptId, serverId, params) {
643
+ try {
644
+ const response = await this.client.post('/api/v1/scripts/executions', {
645
+ script_id: scriptId,
646
+ server_id: serverId,
647
+ parameters_used: params,
648
+ session_id: this.sessionId
649
+ });
650
+ return response.data.log_id;
651
+ }
652
+ catch {
653
+ return 'local-' + (0, crypto_1.randomBytes)(8).toString('hex');
654
+ }
655
+ }
656
+ /**
657
+ * Update execution log with results
658
+ */
659
+ async updateExecutionLog(logId, result) {
660
+ try {
661
+ await this.client.patch(`/api/v1/scripts/executions/${logId}`, {
662
+ status: result.success ? 'success' : 'failed',
663
+ stdout: result.output,
664
+ stderr: result.error,
665
+ exit_code: result.success ? 0 : 1,
666
+ completed_at: new Date().toISOString()
667
+ });
668
+ }
669
+ catch {
670
+ // Ignore log update failures
671
+ }
672
+ }
673
+ /**
674
+ * Execute script on remote server via SSH
675
+ */
676
+ async executeRemoteScript(credentials, script, timeoutMs = 300000) {
677
+ const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `langmart-script-${(0, crypto_1.randomBytes)(8).toString('hex')}`);
678
+ try {
679
+ await (0, promises_1.mkdir)(tempDir, { recursive: true });
680
+ // Write script to temp file
681
+ const scriptPath = (0, path_1.join)(tempDir, 'script.sh');
682
+ await (0, promises_1.writeFile)(scriptPath, script, { mode: 0o755 });
683
+ let sshCommand;
684
+ let scpCommand;
685
+ if (credentials.auth_type === 'password') {
686
+ const sshpassPrefix = `sshpass -p '${credentials.password}'`;
687
+ scpCommand = `${sshpassPrefix} scp -o StrictHostKeyChecking=no -P ${credentials.port} ${scriptPath} ${credentials.username}@${credentials.hostname}:/tmp/langmart_script.sh`;
688
+ sshCommand = `${sshpassPrefix} ssh -o StrictHostKeyChecking=no -p ${credentials.port} ${credentials.username}@${credentials.hostname} "chmod +x /tmp/langmart_script.sh && /tmp/langmart_script.sh && rm /tmp/langmart_script.sh"`;
689
+ }
690
+ else {
691
+ const keyPath = (0, path_1.join)(tempDir, 'id_rsa');
692
+ await (0, promises_1.writeFile)(keyPath, credentials.ssh_key, { mode: 0o600 });
693
+ const sshOpts = `-i ${keyPath} -o StrictHostKeyChecking=no`;
694
+ scpCommand = `scp ${sshOpts} -P ${credentials.port} ${scriptPath} ${credentials.username}@${credentials.hostname}:/tmp/langmart_script.sh`;
695
+ sshCommand = `ssh ${sshOpts} -p ${credentials.port} ${credentials.username}@${credentials.hostname} "chmod +x /tmp/langmart_script.sh && /tmp/langmart_script.sh && rm /tmp/langmart_script.sh"`;
696
+ }
697
+ // Copy script to remote
698
+ await execAsync(scpCommand, { timeout: 60000 });
699
+ // Execute script
700
+ const { stdout, stderr } = await execAsync(sshCommand, { timeout: timeoutMs });
701
+ return { success: true, output: stdout + (stderr ? `\nStderr: ${stderr}` : '') };
702
+ }
703
+ catch (error) {
704
+ return { success: false, error: error.message, output: error.stdout };
705
+ }
706
+ finally {
707
+ // Cleanup
708
+ try {
709
+ await execAsync(`rm -rf ${tempDir}`);
710
+ }
711
+ catch { }
712
+ }
713
+ }
714
+ }
715
+ exports.DevOpsTools = DevOpsTools;
716
+ DevOpsTools.instance = null;
717
+ exports.default = DevOpsTools;
718
+ //# sourceMappingURL=devops-tools.js.map