ssh-agent-workspace 1.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/README.md +319 -0
- package/dist/__tests__/SSHManager.test.d.ts +2 -0
- package/dist/__tests__/SSHManager.test.d.ts.map +1 -0
- package/dist/__tests__/SSHManager.test.js +134 -0
- package/dist/__tests__/SSHManager.test.js.map +1 -0
- package/dist/__tests__/SessionManager.test.d.ts +2 -0
- package/dist/__tests__/SessionManager.test.d.ts.map +1 -0
- package/dist/__tests__/SessionManager.test.js +141 -0
- package/dist/__tests__/SessionManager.test.js.map +1 -0
- package/dist/__tests__/StorageManager.test.d.ts +2 -0
- package/dist/__tests__/StorageManager.test.d.ts.map +1 -0
- package/dist/__tests__/StorageManager.test.js +171 -0
- package/dist/__tests__/StorageManager.test.js.map +1 -0
- package/dist/__tests__/ansi.test.d.ts +2 -0
- package/dist/__tests__/ansi.test.d.ts.map +1 -0
- package/dist/__tests__/ansi.test.js +41 -0
- package/dist/__tests__/ansi.test.js.map +1 -0
- package/dist/__tests__/security.test.d.ts +2 -0
- package/dist/__tests__/security.test.d.ts.map +1 -0
- package/dist/__tests__/security.test.js +87 -0
- package/dist/__tests__/security.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +2 -0
- package/dist/__tests__/validation.test.d.ts.map +1 -0
- package/dist/__tests__/validation.test.js +23 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/core/HostSecurityManager.d.ts +25 -0
- package/dist/core/HostSecurityManager.d.ts.map +1 -0
- package/dist/core/HostSecurityManager.js +76 -0
- package/dist/core/HostSecurityManager.js.map +1 -0
- package/dist/core/SSHManager.d.ts +48 -0
- package/dist/core/SSHManager.d.ts.map +1 -0
- package/dist/core/SSHManager.js +288 -0
- package/dist/core/SSHManager.js.map +1 -0
- package/dist/core/SessionManager.d.ts +15 -0
- package/dist/core/SessionManager.d.ts.map +1 -0
- package/dist/core/SessionManager.js +96 -0
- package/dist/core/SessionManager.js.map +1 -0
- package/dist/core/StorageManager.d.ts +27 -0
- package/dist/core/StorageManager.d.ts.map +1 -0
- package/dist/core/StorageManager.js +87 -0
- package/dist/core/StorageManager.js.map +1 -0
- package/dist/core/TmuxManager.d.ts +21 -0
- package/dist/core/TmuxManager.d.ts.map +1 -0
- package/dist/core/TmuxManager.js +110 -0
- package/dist/core/TmuxManager.js.map +1 -0
- package/dist/core/ToolConfigManager.d.ts +15 -0
- package/dist/core/ToolConfigManager.d.ts.map +1 -0
- package/dist/core/ToolConfigManager.js +57 -0
- package/dist/core/ToolConfigManager.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +44 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +152 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/backup.d.ts +74 -0
- package/dist/tools/backup.d.ts.map +1 -0
- package/dist/tools/backup.js +152 -0
- package/dist/tools/backup.js.map +1 -0
- package/dist/tools/connect.d.ts +46 -0
- package/dist/tools/connect.d.ts.map +1 -0
- package/dist/tools/connect.js +235 -0
- package/dist/tools/connect.js.map +1 -0
- package/dist/tools/connection_status.d.ts +39 -0
- package/dist/tools/connection_status.d.ts.map +1 -0
- package/dist/tools/connection_status.js +67 -0
- package/dist/tools/connection_status.js.map +1 -0
- package/dist/tools/db_query.d.ts +103 -0
- package/dist/tools/db_query.d.ts.map +1 -0
- package/dist/tools/db_query.js +194 -0
- package/dist/tools/db_query.js.map +1 -0
- package/dist/tools/deploy.d.ts +127 -0
- package/dist/tools/deploy.d.ts.map +1 -0
- package/dist/tools/deploy.js +201 -0
- package/dist/tools/deploy.js.map +1 -0
- package/dist/tools/disconnect.d.ts +46 -0
- package/dist/tools/disconnect.d.ts.map +1 -0
- package/dist/tools/disconnect.js +77 -0
- package/dist/tools/disconnect.js.map +1 -0
- package/dist/tools/exec.d.ts +69 -0
- package/dist/tools/exec.d.ts.map +1 -0
- package/dist/tools/exec.js +188 -0
- package/dist/tools/exec.js.map +1 -0
- package/dist/tools/group_exec.d.ts +80 -0
- package/dist/tools/group_exec.d.ts.map +1 -0
- package/dist/tools/group_exec.js +150 -0
- package/dist/tools/group_exec.js.map +1 -0
- package/dist/tools/health_check.d.ts +38 -0
- package/dist/tools/health_check.d.ts.map +1 -0
- package/dist/tools/health_check.js +161 -0
- package/dist/tools/health_check.js.map +1 -0
- package/dist/tools/host_security.d.ts +52 -0
- package/dist/tools/host_security.d.ts.map +1 -0
- package/dist/tools/host_security.js +127 -0
- package/dist/tools/host_security.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/interrupt.d.ts +47 -0
- package/dist/tools/interrupt.d.ts.map +1 -0
- package/dist/tools/interrupt.js +77 -0
- package/dist/tools/interrupt.js.map +1 -0
- package/dist/tools/list_hosts.d.ts +15 -0
- package/dist/tools/list_hosts.d.ts.map +1 -0
- package/dist/tools/list_hosts.js +18 -0
- package/dist/tools/list_hosts.js.map +1 -0
- package/dist/tools/list_sessions.d.ts +16 -0
- package/dist/tools/list_sessions.d.ts.map +1 -0
- package/dist/tools/list_sessions.js +20 -0
- package/dist/tools/list_sessions.js.map +1 -0
- package/dist/tools/read_output.d.ts +46 -0
- package/dist/tools/read_output.d.ts.map +1 -0
- package/dist/tools/read_output.js +73 -0
- package/dist/tools/read_output.js.map +1 -0
- package/dist/tools/reconnect_to_tmux.d.ts +53 -0
- package/dist/tools/reconnect_to_tmux.d.ts.map +1 -0
- package/dist/tools/reconnect_to_tmux.js +199 -0
- package/dist/tools/reconnect_to_tmux.js.map +1 -0
- package/dist/tools/send_input.d.ts +45 -0
- package/dist/tools/send_input.d.ts.map +1 -0
- package/dist/tools/send_input.js +83 -0
- package/dist/tools/send_input.js.map +1 -0
- package/dist/tools/sftp_download.d.ts +52 -0
- package/dist/tools/sftp_download.d.ts.map +1 -0
- package/dist/tools/sftp_download.js +90 -0
- package/dist/tools/sftp_download.js.map +1 -0
- package/dist/tools/sftp_list.d.ts +46 -0
- package/dist/tools/sftp_list.d.ts.map +1 -0
- package/dist/tools/sftp_list.js +93 -0
- package/dist/tools/sftp_list.js.map +1 -0
- package/dist/tools/sftp_upload.d.ts +52 -0
- package/dist/tools/sftp_upload.d.ts.map +1 -0
- package/dist/tools/sftp_upload.js +98 -0
- package/dist/tools/sftp_upload.js.map +1 -0
- package/dist/tools/ssh_tunnel.d.ts +116 -0
- package/dist/tools/ssh_tunnel.d.ts.map +1 -0
- package/dist/tools/ssh_tunnel.js +282 -0
- package/dist/tools/ssh_tunnel.js.map +1 -0
- package/dist/tools/sync.d.ts +71 -0
- package/dist/tools/sync.d.ts.map +1 -0
- package/dist/tools/sync.js +310 -0
- package/dist/tools/sync.js.map +1 -0
- package/dist/tools/tail_log.d.ts +61 -0
- package/dist/tools/tail_log.d.ts.map +1 -0
- package/dist/tools/tail_log.js +111 -0
- package/dist/tools/tail_log.js.map +1 -0
- package/dist/tools/tools_config.d.ts +34 -0
- package/dist/tools/tools_config.d.ts.map +1 -0
- package/dist/tools/tools_config.js +98 -0
- package/dist/tools/tools_config.js.map +1 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ansi.d.ts +2 -0
- package/dist/utils/ansi.d.ts.map +1 -0
- package/dist/utils/ansi.js +7 -0
- package/dist/utils/ansi.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +8 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/security.d.ts +7 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +58 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/ssh.d.ts +4 -0
- package/dist/utils/ssh.d.ts.map +1 -0
- package/dist/utils/ssh.js +29 -0
- package/dist/utils/ssh.js.map +1 -0
- package/dist/utils/sshConfig.d.ts +4 -0
- package/dist/utils/sshConfig.d.ts.map +1 -0
- package/dist/utils/sshConfig.js +85 -0
- package/dist/utils/sshConfig.js.map +1 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +12 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/SECURITY.md +213 -0
- package/docs/TOOLS.md +425 -0
- package/keygen.bat +325 -0
- package/package.json +48 -0
- package/test_check.bat +9 -0
- package/test_delayed.bat +12 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { SessionManager } from '../core/SessionManager.js';
|
|
3
|
+
import { SSHManager } from '../core/SSHManager.js';
|
|
4
|
+
export declare const dbQuerySchema: z.ZodObject<{
|
|
5
|
+
session_id: z.ZodString;
|
|
6
|
+
type: z.ZodEnum<["mysql", "postgresql", "mongodb"]>;
|
|
7
|
+
database: z.ZodString;
|
|
8
|
+
query: z.ZodString;
|
|
9
|
+
db_user: z.ZodOptional<z.ZodString>;
|
|
10
|
+
db_password: z.ZodOptional<z.ZodString>;
|
|
11
|
+
db_host: z.ZodOptional<z.ZodString>;
|
|
12
|
+
db_port: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
collection: z.ZodOptional<z.ZodString>;
|
|
14
|
+
timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
type: "mysql" | "postgresql" | "mongodb";
|
|
17
|
+
session_id: string;
|
|
18
|
+
database: string;
|
|
19
|
+
query: string;
|
|
20
|
+
timeout_ms: number;
|
|
21
|
+
db_user?: string | undefined;
|
|
22
|
+
db_password?: string | undefined;
|
|
23
|
+
db_host?: string | undefined;
|
|
24
|
+
db_port?: number | undefined;
|
|
25
|
+
collection?: string | undefined;
|
|
26
|
+
}, {
|
|
27
|
+
type: "mysql" | "postgresql" | "mongodb";
|
|
28
|
+
session_id: string;
|
|
29
|
+
database: string;
|
|
30
|
+
query: string;
|
|
31
|
+
db_user?: string | undefined;
|
|
32
|
+
db_password?: string | undefined;
|
|
33
|
+
db_host?: string | undefined;
|
|
34
|
+
db_port?: number | undefined;
|
|
35
|
+
collection?: string | undefined;
|
|
36
|
+
timeout_ms?: number | undefined;
|
|
37
|
+
}>;
|
|
38
|
+
export declare const dbQueryTool: {
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object";
|
|
43
|
+
properties: {
|
|
44
|
+
session_id: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
type: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
enum: string[];
|
|
52
|
+
};
|
|
53
|
+
database: {
|
|
54
|
+
type: string;
|
|
55
|
+
description: string;
|
|
56
|
+
};
|
|
57
|
+
query: {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
};
|
|
61
|
+
db_user: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
db_password: {
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
db_host: {
|
|
70
|
+
type: string;
|
|
71
|
+
description: string;
|
|
72
|
+
};
|
|
73
|
+
db_port: {
|
|
74
|
+
type: string;
|
|
75
|
+
description: string;
|
|
76
|
+
};
|
|
77
|
+
collection: {
|
|
78
|
+
type: string;
|
|
79
|
+
description: string;
|
|
80
|
+
};
|
|
81
|
+
timeout_ms: {
|
|
82
|
+
type: string;
|
|
83
|
+
description: string;
|
|
84
|
+
default: number;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
required: string[];
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
export declare function handleDbQuery(args: unknown, sessionManager: SessionManager, sshManager: SSHManager): Promise<{
|
|
91
|
+
content: {
|
|
92
|
+
type: "text";
|
|
93
|
+
text: string;
|
|
94
|
+
}[];
|
|
95
|
+
isError: boolean;
|
|
96
|
+
} | {
|
|
97
|
+
content: {
|
|
98
|
+
type: "text";
|
|
99
|
+
text: string;
|
|
100
|
+
}[];
|
|
101
|
+
isError?: undefined;
|
|
102
|
+
}>;
|
|
103
|
+
//# sourceMappingURL=db_query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db_query.d.ts","sourceRoot":"","sources":["../../src/tools/db_query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAqBnD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWxB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBvB,CAAC;AA0EF,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,EACb,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,UAAU;;;;;;;;;;;;GAwFvB"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isReadOnlyMode } from '../utils/security.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
const SQL_KEYWORDS_DENY = [
|
|
5
|
+
/\bDROP\b/i, /\bDELETE\b/i, /\bINSERT\b/i, /\bUPDATE\b/i,
|
|
6
|
+
/\bALTER\b/i, /\bCREATE\b/i, /\bTRUNCATE\b/i,
|
|
7
|
+
/\bGRANT\b/i, /\bREVOKE\b/i, /\bREPLACE\b/i,
|
|
8
|
+
/\bLOAD\b/i, /\bIMPORT\b/i, /\bINTO\s+OUTFILE\b/i,
|
|
9
|
+
/\bINTO\s+DUMPFILE\b/i, /\bEXEC\b/i, /\bEXECUTE\b/i,
|
|
10
|
+
/\bCALL\b/i, /\bRENAME\b/i, /\bLOCK\b/i, /\bUNLOCK\b/i,
|
|
11
|
+
/\bSET\b/i, /\bFLUSH\b/i, /\bKILL\b/i, /\bSHUTDOWN\b/i,
|
|
12
|
+
];
|
|
13
|
+
const MONGO_DENY = [
|
|
14
|
+
/\bdeleteOne\b/i, /\bdeleteMany\b/i, /\binsertOne\b/i, /\binsertMany\b/i,
|
|
15
|
+
/\bupdateOne\b/i, /\bupdateMany\b/i, /\breplaceOne\b/i,
|
|
16
|
+
/\bdrop\b/i, /\bremove\b/i, /\bcreateCollection\b/i, /\bcreateIndex\b/i,
|
|
17
|
+
/\brenameCollection\b/i, /\bcollMod\b/i,
|
|
18
|
+
];
|
|
19
|
+
export const dbQuerySchema = z.object({
|
|
20
|
+
session_id: z.string().min(1),
|
|
21
|
+
type: z.enum(['mysql', 'postgresql', 'mongodb']).describe('Database type'),
|
|
22
|
+
database: z.string().min(1).describe('Database name'),
|
|
23
|
+
query: z.string().min(1).max(5000).describe('Read-only query (SELECT, SHOW, EXPLAIN, find, aggregate)'),
|
|
24
|
+
db_user: z.string().min(1).optional().describe('Database username (defaults to session user)'),
|
|
25
|
+
db_password: z.string().min(1).optional().describe('Database password'),
|
|
26
|
+
db_host: z.string().min(1).optional().describe('Database host on the remote side (default: 127.0.0.1)'),
|
|
27
|
+
db_port: z.number().int().min(1).max(65535).optional().describe('Database port'),
|
|
28
|
+
collection: z.string().min(1).optional().describe('MongoDB collection name (required for find/aggregate queries)'),
|
|
29
|
+
timeout_ms: z.number().min(1000).max(60000).default(15000).describe('Query timeout in ms'),
|
|
30
|
+
});
|
|
31
|
+
export const dbQueryTool = {
|
|
32
|
+
name: 'db_query',
|
|
33
|
+
description: 'Execute a read-only database query on a remote host via SSH exec channel. Supports MySQL, PostgreSQL, and MongoDB. Enforces SELECT-only (or find/aggregate for Mongo). Returns structured JSON rows.',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
session_id: { type: 'string', description: 'Session ID returned by connect' },
|
|
38
|
+
type: { type: 'string', description: 'Database type', enum: ['mysql', 'postgresql', 'mongodb'] },
|
|
39
|
+
database: { type: 'string', description: 'Database name' },
|
|
40
|
+
query: { type: 'string', description: 'Read-only query (SELECT, SHOW, EXPLAIN, find, aggregate)' },
|
|
41
|
+
db_user: { type: 'string', description: 'Database username (defaults to session user)' },
|
|
42
|
+
db_password: { type: 'string', description: 'Database password' },
|
|
43
|
+
db_host: { type: 'string', description: 'Database host on remote (default: 127.0.0.1)' },
|
|
44
|
+
db_port: { type: 'number', description: 'Database port' },
|
|
45
|
+
collection: { type: 'string', description: 'MongoDB collection (for find/aggregate queries)' },
|
|
46
|
+
timeout_ms: { type: 'number', description: 'Query timeout in ms (default: 15000)', default: 15000 },
|
|
47
|
+
},
|
|
48
|
+
required: ['session_id', 'type', 'database', 'query'],
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
function q(value) {
|
|
52
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
53
|
+
}
|
|
54
|
+
function isReadOnlyQuery(query, type) {
|
|
55
|
+
const trimmed = query.trim();
|
|
56
|
+
if (type === 'mysql' || type === 'postgresql') {
|
|
57
|
+
for (const pattern of SQL_KEYWORDS_DENY) {
|
|
58
|
+
if (pattern.test(trimmed)) {
|
|
59
|
+
return `SQL keyword blocked: ${trimmed.match(pattern)[0].toUpperCase()}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const upper = trimmed.toUpperCase();
|
|
63
|
+
if (!upper.startsWith('SELECT') &&
|
|
64
|
+
!upper.startsWith('SHOW') &&
|
|
65
|
+
!upper.startsWith('EXPLAIN') &&
|
|
66
|
+
!upper.startsWith('DESCRIBE') &&
|
|
67
|
+
!upper.startsWith('DESC ') &&
|
|
68
|
+
!upper.startsWith('USE ')) {
|
|
69
|
+
return 'Only SELECT, SHOW, EXPLAIN, DESCRIBE, and USE queries are allowed';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (type === 'mongodb') {
|
|
73
|
+
for (const pattern of MONGO_DENY) {
|
|
74
|
+
if (pattern.test(trimmed)) {
|
|
75
|
+
return `MongoDB method blocked: ${trimmed.match(pattern)[0]}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const lower = trimmed.toLowerCase();
|
|
79
|
+
if (!lower.startsWith('db.') &&
|
|
80
|
+
!lower.startsWith('show ') &&
|
|
81
|
+
!lower.startsWith('rs.') &&
|
|
82
|
+
!lower.startsWith('sh.')) {
|
|
83
|
+
return 'MongoDB queries must start with db., show, rs., or sh.';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function parseTabularOutput(stdout) {
|
|
89
|
+
const lines = stdout.split('\n').filter((l) => l.trim().length > 0);
|
|
90
|
+
if (lines.length === 0)
|
|
91
|
+
return [];
|
|
92
|
+
const headerMatch = lines[0];
|
|
93
|
+
if (headerMatch.includes('\t')) {
|
|
94
|
+
const headers = lines.shift().split('\t');
|
|
95
|
+
return lines.map((line) => {
|
|
96
|
+
const cols = line.split('\t');
|
|
97
|
+
const row = {};
|
|
98
|
+
headers.forEach((h, i) => { row[h] = cols[i] || ''; });
|
|
99
|
+
return row;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return [{ result: stdout }];
|
|
103
|
+
}
|
|
104
|
+
function parseMongoOutput(stdout) {
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(stdout);
|
|
107
|
+
if (Array.isArray(parsed))
|
|
108
|
+
return parsed;
|
|
109
|
+
return [parsed];
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [{ raw: stdout.slice(0, 10000) }];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export async function handleDbQuery(args, sessionManager, sshManager) {
|
|
116
|
+
const parsed = dbQuerySchema.safeParse(args);
|
|
117
|
+
if (!parsed.success) {
|
|
118
|
+
return { content: [{ type: 'text', text: `Invalid arguments: ${parsed.error.message}` }], isError: true };
|
|
119
|
+
}
|
|
120
|
+
const { session_id, type, database, query, db_user, db_password, db_host, db_port, collection, timeout_ms } = parsed.data;
|
|
121
|
+
const session = sessionManager.get(session_id);
|
|
122
|
+
if (!session) {
|
|
123
|
+
return { content: [{ type: 'text', text: `Error: Session '${session_id}' not found` }], isError: true };
|
|
124
|
+
}
|
|
125
|
+
if (!sshManager.isAlive(session.ssh)) {
|
|
126
|
+
return { content: [{ type: 'text', text: `Error: SSH connection dead for session '${session_id}'` }], isError: true };
|
|
127
|
+
}
|
|
128
|
+
if (isReadOnlyMode(session.host)) {
|
|
129
|
+
return { content: [{ type: 'text', text: `Error: Host '${session.host}' is in read-only mode. DB query is disabled.` }], isError: true };
|
|
130
|
+
}
|
|
131
|
+
const denyReason = isReadOnlyQuery(query, type);
|
|
132
|
+
if (denyReason) {
|
|
133
|
+
return {
|
|
134
|
+
content: [{ type: 'text', text: `Error: Query rejected: ${denyReason}` }],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const host = db_host || '127.0.0.1';
|
|
139
|
+
const port = db_port || (type === 'mysql' ? 3306 : type === 'postgresql' ? 5432 : 27017);
|
|
140
|
+
const user = db_user || session.host.split('.')[0] || 'root';
|
|
141
|
+
let cmd;
|
|
142
|
+
if (type === 'mysql') {
|
|
143
|
+
const passArg = db_password ? `-p${q(db_password)}` : '';
|
|
144
|
+
cmd = `mysql -h ${q(host)} -P ${port} -u ${q(user)} ${passArg} -D ${q(database)} --batch --skip-column-names --default-character-set=utf8mb4 -e ${q(query)} 2>&1`;
|
|
145
|
+
}
|
|
146
|
+
else if (type === 'postgresql') {
|
|
147
|
+
const passEnv = db_password ? `PGPASSWORD=${q(db_password)} ` : '';
|
|
148
|
+
cmd = `${passEnv}psql -h ${q(host)} -p ${port} -U ${q(user)} -d ${q(database)} -A -t --no-align -c ${q(query)} 2>&1`;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const collArg = collection ? `.${collection}` : '';
|
|
152
|
+
cmd = `mongosh --host ${q(host)} --port ${port} --quiet --eval "printjson(JSON.stringify(${query.replace(/"/g, '\\"')}))" ${q(database)} 2>&1 || mongo --host ${q(host)} --port ${port} --quiet --eval "printjson(JSON.stringify(${query.replace(/"/g, '\\"')}))" ${q(database)} 2>&1`;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const result = await sshManager.exec(session.ssh, cmd, timeout_ms);
|
|
156
|
+
if (result.code !== 0 && !result.stdout.trim()) {
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: 'text', text: `Error: Query failed (exit ${result.code}): ${result.stderr || 'unknown error'}` }],
|
|
159
|
+
isError: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
let rows;
|
|
163
|
+
if (type === 'mongodb') {
|
|
164
|
+
rows = parseMongoOutput(result.stdout);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
rows = parseTabularOutput(result.stdout);
|
|
168
|
+
}
|
|
169
|
+
const rowCount = Array.isArray(rows) ? rows.length : 1;
|
|
170
|
+
logger.info({ sessionId: session_id, type, database, rows: rowCount }, 'DB query executed');
|
|
171
|
+
return {
|
|
172
|
+
content: [{
|
|
173
|
+
type: 'text',
|
|
174
|
+
text: JSON.stringify({
|
|
175
|
+
type,
|
|
176
|
+
database,
|
|
177
|
+
query: query.length > 200 ? query.slice(0, 200) + '...' : query,
|
|
178
|
+
row_count: rowCount,
|
|
179
|
+
rows,
|
|
180
|
+
stderr: result.stderr ? result.stderr.slice(0, 500) : undefined,
|
|
181
|
+
exit_code: result.code,
|
|
182
|
+
}, null, 2),
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
logger.error({ sessionId: session_id, type, database, error: err.message }, 'DB query failed');
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: 'text', text: `Error: DB query failed: ${err.message}` }],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=db_query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db_query.js","sourceRoot":"","sources":["../../src/tools/db_query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { SessionManager } from '../core/SessionManager.js';
|
|
3
|
+
import { SSHManager } from '../core/SSHManager.js';
|
|
4
|
+
export declare const deploySchema: z.ZodObject<{
|
|
5
|
+
session_id: z.ZodString;
|
|
6
|
+
files: z.ZodArray<z.ZodObject<{
|
|
7
|
+
local_path: z.ZodString;
|
|
8
|
+
remote_path: z.ZodString;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
local_path: string;
|
|
11
|
+
remote_path: string;
|
|
12
|
+
}, {
|
|
13
|
+
local_path: string;
|
|
14
|
+
remote_path: string;
|
|
15
|
+
}>, "many">;
|
|
16
|
+
chmod: z.ZodOptional<z.ZodString>;
|
|
17
|
+
chown: z.ZodOptional<z.ZodString>;
|
|
18
|
+
backup: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
restart_service: z.ZodOptional<z.ZodString>;
|
|
20
|
+
pre_deploy_cmd: z.ZodOptional<z.ZodString>;
|
|
21
|
+
post_deploy_cmd: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
session_id: string;
|
|
24
|
+
files: {
|
|
25
|
+
local_path: string;
|
|
26
|
+
remote_path: string;
|
|
27
|
+
}[];
|
|
28
|
+
backup: boolean;
|
|
29
|
+
chmod?: string | undefined;
|
|
30
|
+
chown?: string | undefined;
|
|
31
|
+
restart_service?: string | undefined;
|
|
32
|
+
pre_deploy_cmd?: string | undefined;
|
|
33
|
+
post_deploy_cmd?: string | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
session_id: string;
|
|
36
|
+
files: {
|
|
37
|
+
local_path: string;
|
|
38
|
+
remote_path: string;
|
|
39
|
+
}[];
|
|
40
|
+
chmod?: string | undefined;
|
|
41
|
+
chown?: string | undefined;
|
|
42
|
+
backup?: boolean | undefined;
|
|
43
|
+
restart_service?: string | undefined;
|
|
44
|
+
pre_deploy_cmd?: string | undefined;
|
|
45
|
+
post_deploy_cmd?: string | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export declare const deployTool: {
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object";
|
|
52
|
+
properties: {
|
|
53
|
+
session_id: {
|
|
54
|
+
type: string;
|
|
55
|
+
description: string;
|
|
56
|
+
};
|
|
57
|
+
files: {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
items: {
|
|
61
|
+
type: string;
|
|
62
|
+
properties: {
|
|
63
|
+
local_path: {
|
|
64
|
+
type: string;
|
|
65
|
+
description: string;
|
|
66
|
+
};
|
|
67
|
+
remote_path: {
|
|
68
|
+
type: string;
|
|
69
|
+
description: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
required: string[];
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
chmod: {
|
|
76
|
+
type: string;
|
|
77
|
+
description: string;
|
|
78
|
+
};
|
|
79
|
+
chown: {
|
|
80
|
+
type: string;
|
|
81
|
+
description: string;
|
|
82
|
+
};
|
|
83
|
+
backup: {
|
|
84
|
+
type: string;
|
|
85
|
+
description: string;
|
|
86
|
+
default: boolean;
|
|
87
|
+
};
|
|
88
|
+
restart_service: {
|
|
89
|
+
type: string;
|
|
90
|
+
description: string;
|
|
91
|
+
};
|
|
92
|
+
pre_deploy_cmd: {
|
|
93
|
+
type: string;
|
|
94
|
+
description: string;
|
|
95
|
+
};
|
|
96
|
+
post_deploy_cmd: {
|
|
97
|
+
type: string;
|
|
98
|
+
description: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
required: string[];
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
export declare function handleDeploy(args: unknown, sessionManager: SessionManager, sshManager: SSHManager): Promise<{
|
|
105
|
+
content: {
|
|
106
|
+
type: "text";
|
|
107
|
+
text: string;
|
|
108
|
+
}[];
|
|
109
|
+
isError: boolean;
|
|
110
|
+
}>;
|
|
111
|
+
export declare function handleDeployCommand(args: {
|
|
112
|
+
session_id: string;
|
|
113
|
+
command: string;
|
|
114
|
+
}, sessionManager: SessionManager, sshManager: SSHManager): Promise<{
|
|
115
|
+
success: boolean;
|
|
116
|
+
error: string;
|
|
117
|
+
exit_code?: undefined;
|
|
118
|
+
stdout?: undefined;
|
|
119
|
+
stderr?: undefined;
|
|
120
|
+
} | {
|
|
121
|
+
success: boolean;
|
|
122
|
+
exit_code: number | null;
|
|
123
|
+
stdout: string;
|
|
124
|
+
stderr: string;
|
|
125
|
+
error?: undefined;
|
|
126
|
+
}>;
|
|
127
|
+
//# sourceMappingURL=deploy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAInD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYvB,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BtB,CAAC;AAEF,wBAAsB,YAAY,CAChC,IAAI,EAAE,OAAO,EACb,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,UAAU;;;;;;GAiJvB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC7C,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,UAAU;;;;;;;;;;;;GAWvB"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { isReadOnlyMode } from '../utils/security.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
export const deploySchema = z.object({
|
|
7
|
+
session_id: z.string().min(1),
|
|
8
|
+
files: z.array(z.object({
|
|
9
|
+
local_path: z.string().min(1).describe('Absolute path to the local file'),
|
|
10
|
+
remote_path: z.string().min(1).describe('Absolute destination path on the remote host'),
|
|
11
|
+
})).min(1).max(50).describe('Array of {local_path, remote_path} mappings to deploy'),
|
|
12
|
+
chmod: z.string().regex(/^[0-7]{3,4}$/).optional().describe('Octal permissions to set on each file (e.g. "644", "755")'),
|
|
13
|
+
chown: z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_.-]*:[a-zA-Z_][a-zA-Z0-9_.-]*$/).optional().describe('Owner:group to set on each file (e.g. "www-data:www-data")'),
|
|
14
|
+
backup: z.boolean().default(true).describe('Create .bak backup of each existing remote file before overwriting'),
|
|
15
|
+
restart_service: z.string().min(1).optional().describe('Service name to restart after deployment (e.g. "nginx")'),
|
|
16
|
+
pre_deploy_cmd: z.string().min(1).optional().describe('Command to run on remote before uploading (e.g. "systemctl stop nginx")'),
|
|
17
|
+
post_deploy_cmd: z.string().min(1).optional().describe('Command to run on remote after all steps complete'),
|
|
18
|
+
});
|
|
19
|
+
export const deployTool = {
|
|
20
|
+
name: 'deploy',
|
|
21
|
+
description: 'Deploy files to a remote host with permission management, backup, and optional service restart. Executes: pre-command -> backup -> upload -> chmod/chown -> post-command -> restart.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
session_id: { type: 'string', description: 'Session ID returned by connect' },
|
|
26
|
+
files: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
description: 'Array of {local_path, remote_path} objects to deploy',
|
|
29
|
+
items: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
local_path: { type: 'string', description: 'Absolute path to the local file' },
|
|
33
|
+
remote_path: { type: 'string', description: 'Absolute destination path on the remote host' },
|
|
34
|
+
},
|
|
35
|
+
required: ['local_path', 'remote_path'],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
chmod: { type: 'string', description: 'Octal permissions (e.g. "644", "755")' },
|
|
39
|
+
chown: { type: 'string', description: 'Owner:group (e.g. "www-data:www-data")' },
|
|
40
|
+
backup: { type: 'boolean', description: 'Create .bak backup of existing remote files (default: true)', default: true },
|
|
41
|
+
restart_service: { type: 'string', description: 'Service name to restart after deployment' },
|
|
42
|
+
pre_deploy_cmd: { type: 'string', description: 'Command to run on remote before uploading' },
|
|
43
|
+
post_deploy_cmd: { type: 'string', description: 'Command to run on remote after all steps' },
|
|
44
|
+
},
|
|
45
|
+
required: ['session_id', 'files'],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export async function handleDeploy(args, sessionManager, sshManager) {
|
|
49
|
+
const parsed = deploySchema.safeParse(args);
|
|
50
|
+
if (!parsed.success) {
|
|
51
|
+
return { content: [{ type: 'text', text: `Invalid arguments: ${parsed.error.message}` }], isError: true };
|
|
52
|
+
}
|
|
53
|
+
const { session_id, files, chmod, chown, backup, restart_service, pre_deploy_cmd, post_deploy_cmd } = parsed.data;
|
|
54
|
+
const session = sessionManager.get(session_id);
|
|
55
|
+
if (!session) {
|
|
56
|
+
return { content: [{ type: 'text', text: `Error: Session '${session_id}' not found` }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
if (!sshManager.isAlive(session.ssh)) {
|
|
59
|
+
return { content: [{ type: 'text', text: `Error: SSH connection dead for session '${session_id}'` }], isError: true };
|
|
60
|
+
}
|
|
61
|
+
if (isReadOnlyMode(session.host)) {
|
|
62
|
+
return { content: [{ type: 'text', text: `Error: Host '${session.host}' is in read-only mode. Deploy is disabled.` }], isError: true };
|
|
63
|
+
}
|
|
64
|
+
const report = [];
|
|
65
|
+
let hadError = false;
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
const resolvedLocal = path.resolve(file.local_path);
|
|
68
|
+
const stepReport = {
|
|
69
|
+
local_path: resolvedLocal,
|
|
70
|
+
remote_path: file.remote_path,
|
|
71
|
+
};
|
|
72
|
+
if (!fs.existsSync(resolvedLocal)) {
|
|
73
|
+
stepReport.status = 'error';
|
|
74
|
+
stepReport.error = 'Local file not found';
|
|
75
|
+
report.push(stepReport);
|
|
76
|
+
hadError = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!fs.statSync(resolvedLocal).isFile()) {
|
|
80
|
+
stepReport.status = 'error';
|
|
81
|
+
stepReport.error = 'Local path is not a file';
|
|
82
|
+
report.push(stepReport);
|
|
83
|
+
hadError = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (backup) {
|
|
87
|
+
try {
|
|
88
|
+
const exists = await sshManager.sftpExists(session.ssh, file.remote_path);
|
|
89
|
+
if (exists) {
|
|
90
|
+
const backupCmd = `cp '${file.remote_path.replace(/'/g, "'\\''")}' '${file.remote_path.replace(/'/g, "'\\''")}'.bak`;
|
|
91
|
+
const bkResult = await sshManager.exec(session.ssh, backupCmd, 5000);
|
|
92
|
+
if (bkResult.code === 0) {
|
|
93
|
+
stepReport.backup = `${file.remote_path}.bak`;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
stepReport.backup_warning = 'Backup failed, continuing without backup';
|
|
97
|
+
logger.warn({ sessionId: session_id, remote: file.remote_path, stderr: bkResult.stderr }, 'Deploy backup failed');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
stepReport.backup_warning = `Backup check failed: ${err.message}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await sshManager.sftpUpload(session.ssh, resolvedLocal, file.remote_path);
|
|
107
|
+
stepReport.uploaded = true;
|
|
108
|
+
stepReport.size = fs.statSync(resolvedLocal).size;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
stepReport.status = 'error';
|
|
112
|
+
stepReport.error = `Upload failed: ${err.message}`;
|
|
113
|
+
report.push(stepReport);
|
|
114
|
+
hadError = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (chmod) {
|
|
118
|
+
try {
|
|
119
|
+
const chmodResult = await sshManager.exec(session.ssh, `chmod ${chmod} '${file.remote_path.replace(/'/g, "'\\''")}'`, 5000);
|
|
120
|
+
if (chmodResult.code === 0) {
|
|
121
|
+
stepReport.chmod = chmod;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
stepReport.chmod_error = chmodResult.stderr || 'unknown';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
stepReport.chmod_error = err.message;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (chown) {
|
|
132
|
+
try {
|
|
133
|
+
const chownResult = await sshManager.exec(session.ssh, `chown ${chown} '${file.remote_path.replace(/'/g, "'\\''")}'`, 5000);
|
|
134
|
+
if (chownResult.code === 0) {
|
|
135
|
+
stepReport.chown = chown;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
stepReport.chown_error = chownResult.stderr || 'unknown';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
stepReport.chown_error = err.message;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
stepReport.status = 'deployed';
|
|
146
|
+
report.push(stepReport);
|
|
147
|
+
}
|
|
148
|
+
if (restart_service) {
|
|
149
|
+
try {
|
|
150
|
+
const restartResult = await sshManager.exec(session.ssh, `systemctl restart '${restart_service.replace(/'/g, "'\\''")}'`, 15000);
|
|
151
|
+
if (restartResult.code === 0) {
|
|
152
|
+
report.push({ service: restart_service, restarted: true });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
report.push({ service: restart_service, restarted: false, error: restartResult.stderr || 'unknown' });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
report.push({ service: restart_service, restarted: false, error: err.message });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const result = { session_id, files: report, total: files.length, deployed: report.filter((r) => r.status === 'deployed').length };
|
|
163
|
+
if (pre_deploy_cmd) {
|
|
164
|
+
try {
|
|
165
|
+
const preResult = await sshManager.exec(session.ssh, pre_deploy_cmd, 30000);
|
|
166
|
+
result.pre_deploy = { command: pre_deploy_cmd, exit_code: preResult.code, stdout: preResult.stdout.slice(0, 1000) };
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
result.pre_deploy_error = err.message;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (post_deploy_cmd) {
|
|
173
|
+
try {
|
|
174
|
+
const postResult = await sshManager.exec(session.ssh, post_deploy_cmd, 30000);
|
|
175
|
+
result.post_deploy = { command: post_deploy_cmd, exit_code: postResult.code, stdout: postResult.stdout.slice(0, 1000) };
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
result.post_deploy_error = err.message;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
logger.info({ sessionId: session_id, deployed: result.deployed, hadError }, 'Deploy complete');
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
184
|
+
isError: hadError,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export async function handleDeployCommand(args, sessionManager, sshManager) {
|
|
188
|
+
const session = sessionManager.get(args.session_id);
|
|
189
|
+
if (!session)
|
|
190
|
+
return { success: false, error: 'Session not found' };
|
|
191
|
+
if (!sshManager.isAlive(session.ssh))
|
|
192
|
+
return { success: false, error: 'SSH dead' };
|
|
193
|
+
try {
|
|
194
|
+
const result = await sshManager.exec(session.ssh, args.command, 15000);
|
|
195
|
+
return { success: result.code === 0, exit_code: result.code, stdout: result.stdout, stderr: result.stderr };
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return { success: false, error: err.message };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=deploy.js.map
|