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.
- package/.env.example +29 -0
- package/README.md +480 -0
- package/dist/bash-tools.d.ts +56 -0
- package/dist/bash-tools.d.ts.map +1 -0
- package/dist/bash-tools.js +188 -0
- package/dist/bash-tools.js.map +1 -0
- package/dist/core-tools.d.ts +94 -0
- package/dist/core-tools.d.ts.map +1 -0
- package/dist/core-tools.js +694 -0
- package/dist/core-tools.js.map +1 -0
- package/dist/debug-utils.d.ts +22 -0
- package/dist/debug-utils.d.ts.map +1 -0
- package/dist/debug-utils.js +37 -0
- package/dist/debug-utils.js.map +1 -0
- package/dist/devops-tools.d.ts +147 -0
- package/dist/devops-tools.d.ts.map +1 -0
- package/dist/devops-tools.js +718 -0
- package/dist/devops-tools.js.map +1 -0
- package/dist/gateway-config.d.ts +56 -0
- package/dist/gateway-config.d.ts.map +1 -0
- package/dist/gateway-config.js +198 -0
- package/dist/gateway-config.js.map +1 -0
- package/dist/gateway-mode.d.ts +58 -0
- package/dist/gateway-mode.d.ts.map +1 -0
- package/dist/gateway-mode.js +240 -0
- package/dist/gateway-mode.js.map +1 -0
- package/dist/gateway-server.d.ts +208 -0
- package/dist/gateway-server.d.ts.map +1 -0
- package/dist/gateway-server.js +1811 -0
- package/dist/gateway-server.js.map +1 -0
- package/dist/headless-session.d.ts +192 -0
- package/dist/headless-session.d.ts.map +1 -0
- package/dist/headless-session.js +584 -0
- package/dist/headless-session.js.map +1 -0
- package/dist/index-server.d.ts +4 -0
- package/dist/index-server.d.ts.map +1 -0
- package/dist/index-server.js +129 -0
- package/dist/index-server.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/key-vault.d.ts +102 -0
- package/dist/key-vault.d.ts.map +1 -0
- package/dist/key-vault.js +365 -0
- package/dist/key-vault.js.map +1 -0
- package/dist/local-vault.d.ts +195 -0
- package/dist/local-vault.d.ts.map +1 -0
- package/dist/local-vault.js +571 -0
- package/dist/local-vault.js.map +1 -0
- package/dist/marketplace-tools.d.ts +104 -0
- package/dist/marketplace-tools.d.ts.map +1 -0
- package/dist/marketplace-tools.js +2846 -0
- package/dist/marketplace-tools.js.map +1 -0
- package/dist/mcp-manager.d.ts +114 -0
- package/dist/mcp-manager.d.ts.map +1 -0
- package/dist/mcp-manager.js +338 -0
- package/dist/mcp-manager.js.map +1 -0
- package/dist/web-tools.d.ts +86 -0
- package/dist/web-tools.d.ts.map +1 -0
- package/dist/web-tools.js +431 -0
- package/dist/web-tools.js.map +1 -0
- package/dist/websocket-handler.d.ts +131 -0
- package/dist/websocket-handler.d.ts.map +1 -0
- package/dist/websocket-handler.js +596 -0
- package/dist/websocket-handler.js.map +1 -0
- package/dist/welcome-pages.d.ts +6 -0
- package/dist/welcome-pages.d.ts.map +1 -0
- package/dist/welcome-pages.js +200 -0
- package/dist/welcome-pages.js.map +1 -0
- package/package.json +168 -0
- package/scripts/install-remote.sh +282 -0
- package/scripts/start.sh +85 -0
- package/scripts/status.sh +79 -0
- 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
|