@linxin666/dsh-ssh 0.3.16 → 0.3.18
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/lib/client.js +3 -3
- package/lib/client.js.map +1 -1
- package/lib/index.js +17 -5
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/engine/cluster.d.ts +2 -2
- package/lib/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/locales.ts +2 -2
- package/src/engine/cluster.ts +9 -2
- package/src/index.ts +1 -1
- package/src/routes.ts +7 -0
- package/src/tools.ts +9 -5
package/lib/index.js
CHANGED
|
@@ -2094,6 +2094,10 @@ function stopAllTunnels(engine, alias) {
|
|
|
2094
2094
|
//#region src/engine/cluster.ts
|
|
2095
2095
|
/** Run one command against many hosts concurrently. */
|
|
2096
2096
|
async function cluster(engine, options) {
|
|
2097
|
+
const hasAliases = Array.isArray(options.aliases) && options.aliases.some((alias) => typeof alias === "string" && alias.trim() !== "");
|
|
2098
|
+
const hasEnvironment = typeof options.environment === "string" && options.environment.trim() !== "";
|
|
2099
|
+
const hasTags = Array.isArray(options.tags) && options.tags.some((tag) => typeof tag === "string" && tag.trim() !== "");
|
|
2100
|
+
if (!hasAliases && !hasEnvironment && !hasTags) throw new Error("ssh_cluster requires aliases, environment, or tags to limit the target set");
|
|
2097
2101
|
let targets = engine.store.list();
|
|
2098
2102
|
if (options.aliases !== void 0 && options.aliases.length > 0) targets = targets.filter((entry) => options.aliases.includes(entry.alias));
|
|
2099
2103
|
if (options.environment !== void 0 && options.environment !== "") targets = targets.filter((entry) => entry.environment === options.environment);
|
|
@@ -2552,6 +2556,13 @@ function makeRoutes(deps) {
|
|
|
2552
2556
|
const environment = typeof body?.environment === "string" ? body.environment : void 0;
|
|
2553
2557
|
const timeoutMs = typeof body?.timeoutMs === "number" ? body.timeoutMs : void 0;
|
|
2554
2558
|
const maxWorkers = typeof body?.maxWorkers === "number" ? body.maxWorkers : void 0;
|
|
2559
|
+
const hasAliases = aliases?.some((alias) => alias.trim() !== "") === true;
|
|
2560
|
+
const hasEnvironment = typeof environment === "string" && environment.trim() !== "";
|
|
2561
|
+
const hasTags = tags?.some((tag) => tag.trim() !== "") === true;
|
|
2562
|
+
if (!hasAliases && !hasEnvironment && !hasTags) {
|
|
2563
|
+
writeJson(res, 400, { error: "ssh_cluster requires aliases, environment, or tags to limit the target set" });
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2555
2566
|
try {
|
|
2556
2567
|
writeJson(res, 200, { results: await engine.cluster({
|
|
2557
2568
|
command,
|
|
@@ -3437,7 +3448,7 @@ function sshTunnelTool(engine) {
|
|
|
3437
3448
|
function sshClusterTool(engine) {
|
|
3438
3449
|
return defineTool({
|
|
3439
3450
|
name: "ssh_cluster",
|
|
3440
|
-
description: "Run one command concurrently across
|
|
3451
|
+
description: "Run one command concurrently across selected SSH hosts; at least one aliases, environment, or tags filter is required. Triggers: run on selected servers, batch operation, production servers, cluster command.",
|
|
3441
3452
|
parameters: {
|
|
3442
3453
|
command: {
|
|
3443
3454
|
type: "string",
|
|
@@ -3447,16 +3458,16 @@ function sshClusterTool(engine) {
|
|
|
3447
3458
|
aliases: {
|
|
3448
3459
|
type: "array",
|
|
3449
3460
|
items: { type: "string" },
|
|
3450
|
-
description: "
|
|
3461
|
+
description: "Optional alias filter; at least one of aliases, environment, or tags is required."
|
|
3451
3462
|
},
|
|
3452
3463
|
environment: {
|
|
3453
3464
|
type: "string",
|
|
3454
|
-
description: "
|
|
3465
|
+
description: "Optional environment filter; at least one selector is required."
|
|
3455
3466
|
},
|
|
3456
3467
|
tags: {
|
|
3457
3468
|
type: "array",
|
|
3458
3469
|
items: { type: "string" },
|
|
3459
|
-
description: "
|
|
3470
|
+
description: "Optional ALL-tags filter; at least one selector is required."
|
|
3460
3471
|
},
|
|
3461
3472
|
timeoutMs: {
|
|
3462
3473
|
type: "integer",
|
|
@@ -3499,6 +3510,7 @@ function sshClusterTool(engine) {
|
|
|
3499
3510
|
render: (_args, value) => text(renderCluster(value.results ?? []))
|
|
3500
3511
|
},
|
|
3501
3512
|
async execute(args) {
|
|
3513
|
+
if (!(Array.isArray(args.aliases) && args.aliases.some((alias) => typeof alias === "string" && alias.trim() !== "") || typeof args.environment === "string" && args.environment.trim() !== "" || Array.isArray(args.tags) && args.tags.some((tag) => typeof tag === "string" && tag.trim() !== ""))) throw new Error("ssh_cluster requires aliases, environment, or tags to limit the target set");
|
|
3502
3514
|
return { results: await engine.cluster(args) };
|
|
3503
3515
|
}
|
|
3504
3516
|
});
|
|
@@ -3570,7 +3582,7 @@ const DEFAULT_ANNOUNCE = false;
|
|
|
3570
3582
|
/** Order of the announcement section within the tool-guidance band. */
|
|
3571
3583
|
const SECTION_ORDER = 150;
|
|
3572
3584
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
3573
|
-
const SSH_GUIDANCE = "本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存 $DSH_HOME/dsh-ssh.json(默认 ~/.dsh)(可从 ~/.ssh/config 导入);持久连接池复用长连接(空闲 30 分钟自动断开);ssh_list 列出主机、ssh_exec 执行远程命令、ssh_upload/ssh_download 传输文件、ssh_tunnel 本地端口转发(访问远程数据库/内网服务)、ssh_cluster
|
|
3585
|
+
const SSH_GUIDANCE = "本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存 $DSH_HOME/dsh-ssh.json(默认 ~/.dsh)(可从 ~/.ssh/config 导入);持久连接池复用长连接(空闲 30 分钟自动断开);ssh_list 列出主机、ssh_exec 执行远程命令、ssh_upload/ssh_download 传输文件、ssh_tunnel 本地端口转发(访问远程数据库/内网服务)、ssh_cluster 按 aliases/environment/tags 至少一种非空 selector 筛选后集群并发执行;支持密钥/密码/ssh-agent 认证、passphrase 密钥与 ProxyJump 跳板机;Web 终端走 WebSocket。限制:主机操作由用户在 GUI 中配置后 agent 方可使用;密码以明文存在用户主目录私有文件(权限 0600);命令输出原样返回、可能含敏感信息;断线重连可能重放非幂等命令;传输/执行消耗真实远程资源,先确认再操作。路径区分:本机(dsh host)上的文件与命令一律用本地工具(read / write / edit / bash),ssh_* 工具只针对远程主机上的路径。用户提到「SSH / 远程服务器 / 服务器操作 / 跳板机 / 隧道 / 部署 / 上传下载」时即指本插件,请据此协作。";
|
|
3574
3586
|
/**
|
|
3575
3587
|
* Mount the SSH engine, routes, tools, and announcement.
|
|
3576
3588
|
* @param ctx - host plugin context carrying webServer/tools/systemPrompt.
|
|
@@ -126,7 +126,7 @@ export declare const zh: {
|
|
|
126
126
|
readonly 'cluster.command': "命令";
|
|
127
127
|
readonly 'cluster.run': "执行";
|
|
128
128
|
readonly 'cluster.confirm': "确定在匹配的主机上执行该命令吗?该操作会真实运行在远程服务器上。";
|
|
129
|
-
readonly 'cluster.aliases': "
|
|
129
|
+
readonly 'cluster.aliases': "主机别名(逗号分隔,与环境、标签至少填一项)";
|
|
130
130
|
readonly 'cluster.environment': "环境过滤";
|
|
131
131
|
readonly 'cluster.tags': "标签过滤(逗号分隔)";
|
|
132
132
|
readonly 'cluster.results': "执行结果";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cluster execution: run one command concurrently across
|
|
3
|
-
*
|
|
2
|
+
* Cluster execution: run one command concurrently across selected hosts; the
|
|
3
|
+
* caller must provide at least one non-empty aliases / environment / tags filter.
|
|
4
4
|
*/
|
|
5
5
|
import type { ClusterResult } from '../protocol.ts';
|
|
6
6
|
import { type PoolEngine } from './connection-pool.ts';
|
package/lib/types/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface Config {
|
|
|
39
39
|
}
|
|
40
40
|
export declare const Config: z<Config>;
|
|
41
41
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
42
|
-
export declare const SSH_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-ssh \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B SSH \u8FD0\u7EF4\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CSSH\u300D\u5165\u53E3\uFF1B\u5728 dsh-web \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-ssh\uFF09\u7EDF\u4E00\u7EF4\u62A4\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58 $DSH_HOME/dsh-ssh.json\uFF08\u9ED8\u8BA4 ~/.dsh\uFF09\uFF08\u53EF\u4ECE ~/.ssh/config \u5BFC\u5165\uFF09\uFF1B\u6301\u4E45\u8FDE\u63A5\u6C60\u590D\u7528\u957F\u8FDE\u63A5\uFF08\u7A7A\u95F2 30 \u5206\u949F\u81EA\u52A8\u65AD\u5F00\uFF09\uFF1Bssh_list \u5217\u51FA\u4E3B\u673A\u3001ssh_exec \u6267\u884C\u8FDC\u7A0B\u547D\u4EE4\u3001ssh_upload/ssh_download \u4F20\u8F93\u6587\u4EF6\u3001ssh_tunnel \u672C\u5730\u7AEF\u53E3\u8F6C\u53D1\uFF08\u8BBF\u95EE\u8FDC\u7A0B\u6570\u636E\u5E93/\u5185\u7F51\u670D\u52A1\uFF09\u3001ssh_cluster \u96C6\u7FA4\u5E76\u53D1\u6267\u884C\uFF1B\u652F\u6301\u5BC6\u94A5/\u5BC6\u7801/ssh-agent \u8BA4\u8BC1\u3001passphrase \u5BC6\u94A5\u4E0E ProxyJump \u8DF3\u677F\u673A\uFF1BWeb \u7EC8\u7AEF\u8D70 WebSocket\u3002\u9650\u5236\uFF1A\u4E3B\u673A\u64CD\u4F5C\u7531\u7528\u6237\u5728 GUI \u4E2D\u914D\u7F6E\u540E agent \u65B9\u53EF\u4F7F\u7528\uFF1B\u5BC6\u7801\u4EE5\u660E\u6587\u5B58\u5728\u7528\u6237\u4E3B\u76EE\u5F55\u79C1\u6709\u6587\u4EF6\uFF08\u6743\u9650 0600\uFF09\uFF1B\u547D\u4EE4\u8F93\u51FA\u539F\u6837\u8FD4\u56DE\u3001\u53EF\u80FD\u542B\u654F\u611F\u4FE1\u606F\uFF1B\u65AD\u7EBF\u91CD\u8FDE\u53EF\u80FD\u91CD\u653E\u975E\u5E42\u7B49\u547D\u4EE4\uFF1B\u4F20\u8F93/\u6267\u884C\u6D88\u8017\u771F\u5B9E\u8FDC\u7A0B\u8D44\u6E90\uFF0C\u5148\u786E\u8BA4\u518D\u64CD\u4F5C\u3002\u8DEF\u5F84\u533A\u5206\uFF1A\u672C\u673A\uFF08dsh host\uFF09\u4E0A\u7684\u6587\u4EF6\u4E0E\u547D\u4EE4\u4E00\u5F8B\u7528\u672C\u5730\u5DE5\u5177\uFF08read / write / edit / bash\uFF09\uFF0Cssh_* \u5DE5\u5177\u53EA\u9488\u5BF9\u8FDC\u7A0B\u4E3B\u673A\u4E0A\u7684\u8DEF\u5F84\u3002\u7528\u6237\u63D0\u5230\u300CSSH / \u8FDC\u7A0B\u670D\u52A1\u5668 / \u670D\u52A1\u5668\u64CD\u4F5C / \u8DF3\u677F\u673A / \u96A7\u9053 / \u90E8\u7F72 / \u4E0A\u4F20\u4E0B\u8F7D\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
42
|
+
export declare const SSH_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-ssh \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B SSH \u8FD0\u7EF4\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CSSH\u300D\u5165\u53E3\uFF1B\u5728 dsh-web \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-ssh\uFF09\u7EDF\u4E00\u7EF4\u62A4\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58 $DSH_HOME/dsh-ssh.json\uFF08\u9ED8\u8BA4 ~/.dsh\uFF09\uFF08\u53EF\u4ECE ~/.ssh/config \u5BFC\u5165\uFF09\uFF1B\u6301\u4E45\u8FDE\u63A5\u6C60\u590D\u7528\u957F\u8FDE\u63A5\uFF08\u7A7A\u95F2 30 \u5206\u949F\u81EA\u52A8\u65AD\u5F00\uFF09\uFF1Bssh_list \u5217\u51FA\u4E3B\u673A\u3001ssh_exec \u6267\u884C\u8FDC\u7A0B\u547D\u4EE4\u3001ssh_upload/ssh_download \u4F20\u8F93\u6587\u4EF6\u3001ssh_tunnel \u672C\u5730\u7AEF\u53E3\u8F6C\u53D1\uFF08\u8BBF\u95EE\u8FDC\u7A0B\u6570\u636E\u5E93/\u5185\u7F51\u670D\u52A1\uFF09\u3001ssh_cluster \u6309 aliases/environment/tags \u81F3\u5C11\u4E00\u79CD\u975E\u7A7A selector \u7B5B\u9009\u540E\u96C6\u7FA4\u5E76\u53D1\u6267\u884C\uFF1B\u652F\u6301\u5BC6\u94A5/\u5BC6\u7801/ssh-agent \u8BA4\u8BC1\u3001passphrase \u5BC6\u94A5\u4E0E ProxyJump \u8DF3\u677F\u673A\uFF1BWeb \u7EC8\u7AEF\u8D70 WebSocket\u3002\u9650\u5236\uFF1A\u4E3B\u673A\u64CD\u4F5C\u7531\u7528\u6237\u5728 GUI \u4E2D\u914D\u7F6E\u540E agent \u65B9\u53EF\u4F7F\u7528\uFF1B\u5BC6\u7801\u4EE5\u660E\u6587\u5B58\u5728\u7528\u6237\u4E3B\u76EE\u5F55\u79C1\u6709\u6587\u4EF6\uFF08\u6743\u9650 0600\uFF09\uFF1B\u547D\u4EE4\u8F93\u51FA\u539F\u6837\u8FD4\u56DE\u3001\u53EF\u80FD\u542B\u654F\u611F\u4FE1\u606F\uFF1B\u65AD\u7EBF\u91CD\u8FDE\u53EF\u80FD\u91CD\u653E\u975E\u5E42\u7B49\u547D\u4EE4\uFF1B\u4F20\u8F93/\u6267\u884C\u6D88\u8017\u771F\u5B9E\u8FDC\u7A0B\u8D44\u6E90\uFF0C\u5148\u786E\u8BA4\u518D\u64CD\u4F5C\u3002\u8DEF\u5F84\u533A\u5206\uFF1A\u672C\u673A\uFF08dsh host\uFF09\u4E0A\u7684\u6587\u4EF6\u4E0E\u547D\u4EE4\u4E00\u5F8B\u7528\u672C\u5730\u5DE5\u5177\uFF08read / write / edit / bash\uFF09\uFF0Cssh_* \u5DE5\u5177\u53EA\u9488\u5BF9\u8FDC\u7A0B\u4E3B\u673A\u4E0A\u7684\u8DEF\u5F84\u3002\u7528\u6237\u63D0\u5230\u300CSSH / \u8FDC\u7A0B\u670D\u52A1\u5668 / \u670D\u52A1\u5668\u64CD\u4F5C / \u8DF3\u677F\u673A / \u96A7\u9053 / \u90E8\u7F72 / \u4E0A\u4F20\u4E0B\u8F7D\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
43
43
|
/**
|
|
44
44
|
* Mount the SSH engine, routes, tools, and announcement.
|
|
45
45
|
* @param ctx - host plugin context carrying webServer/tools/systemPrompt.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-ssh",
|
|
3
3
|
"description": "Remote SSH operations for the dsh web GUI: host config store (~/.dsh/dsh-ssh.json, import from ~/.ssh/config), a persistent ssh2 connection pool with jump-host support, exec / PTY web terminal / SFTP transfer / local port-forward tunnels / cluster execution, plus agent tools (ssh_list, ssh_exec, ssh_upload, ssh_download, ssh_tunnel, ssh_cluster). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
package/src/client/locales.ts
CHANGED
|
@@ -133,7 +133,7 @@ export const zh = {
|
|
|
133
133
|
'cluster.command': '命令',
|
|
134
134
|
'cluster.run': '执行',
|
|
135
135
|
'cluster.confirm': '确定在匹配的主机上执行该命令吗?该操作会真实运行在远程服务器上。',
|
|
136
|
-
'cluster.aliases': '
|
|
136
|
+
'cluster.aliases': '主机别名(逗号分隔,与环境、标签至少填一项)',
|
|
137
137
|
'cluster.environment': '环境过滤',
|
|
138
138
|
'cluster.tags': '标签过滤(逗号分隔)',
|
|
139
139
|
'cluster.results': '执行结果',
|
|
@@ -282,7 +282,7 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
282
282
|
'cluster.command': 'Command',
|
|
283
283
|
'cluster.run': 'Run',
|
|
284
284
|
'cluster.confirm': 'Run this command on the matching hosts? It executes for real on the remote servers.',
|
|
285
|
-
'cluster.aliases': 'Host aliases (comma-separated;
|
|
285
|
+
'cluster.aliases': 'Host aliases (comma-separated; require at least one filter)',
|
|
286
286
|
'cluster.environment': 'Environment filter',
|
|
287
287
|
'cluster.tags': 'Tag filter (comma-separated)',
|
|
288
288
|
'cluster.results': 'Results',
|
package/src/engine/cluster.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cluster execution: run one command concurrently across
|
|
3
|
-
*
|
|
2
|
+
* Cluster execution: run one command concurrently across selected hosts; the
|
|
3
|
+
* caller must provide at least one non-empty aliases / environment / tags filter.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ClusterResult } from '../protocol.ts'
|
|
@@ -17,6 +17,13 @@ interface ClusterOptions {
|
|
|
17
17
|
|
|
18
18
|
/** Run one command against many hosts concurrently. */
|
|
19
19
|
export async function cluster(engine: PoolEngine, options: ClusterOptions): Promise<ClusterResult[]> {
|
|
20
|
+
const hasAliases = Array.isArray(options.aliases) && options.aliases.some(alias => typeof alias === 'string' && alias.trim() !== '')
|
|
21
|
+
const hasEnvironment = typeof options.environment === 'string' && options.environment.trim() !== ''
|
|
22
|
+
const hasTags = Array.isArray(options.tags) && options.tags.some(tag => typeof tag === 'string' && tag.trim() !== '')
|
|
23
|
+
if (!hasAliases && !hasEnvironment && !hasTags) {
|
|
24
|
+
throw new Error('ssh_cluster requires aliases, environment, or tags to limit the target set')
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
let targets = engine.store.list()
|
|
21
28
|
if (options.aliases !== undefined && options.aliases.length > 0) {
|
|
22
29
|
targets = targets.filter(entry => options.aliases!.includes(entry.alias))
|
package/src/index.ts
CHANGED
|
@@ -64,7 +64,7 @@ const DEFAULT_ANNOUNCE = false
|
|
|
64
64
|
const SECTION_ORDER = 150
|
|
65
65
|
|
|
66
66
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
67
|
-
export const SSH_GUIDANCE = '本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存 $DSH_HOME/dsh-ssh.json(默认 ~/.dsh)(可从 ~/.ssh/config 导入);持久连接池复用长连接(空闲 30 分钟自动断开);ssh_list 列出主机、ssh_exec 执行远程命令、ssh_upload/ssh_download 传输文件、ssh_tunnel 本地端口转发(访问远程数据库/内网服务)、ssh_cluster
|
|
67
|
+
export const SSH_GUIDANCE = '本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存 $DSH_HOME/dsh-ssh.json(默认 ~/.dsh)(可从 ~/.ssh/config 导入);持久连接池复用长连接(空闲 30 分钟自动断开);ssh_list 列出主机、ssh_exec 执行远程命令、ssh_upload/ssh_download 传输文件、ssh_tunnel 本地端口转发(访问远程数据库/内网服务)、ssh_cluster 按 aliases/environment/tags 至少一种非空 selector 筛选后集群并发执行;支持密钥/密码/ssh-agent 认证、passphrase 密钥与 ProxyJump 跳板机;Web 终端走 WebSocket。限制:主机操作由用户在 GUI 中配置后 agent 方可使用;密码以明文存在用户主目录私有文件(权限 0600);命令输出原样返回、可能含敏感信息;断线重连可能重放非幂等命令;传输/执行消耗真实远程资源,先确认再操作。路径区分:本机(dsh host)上的文件与命令一律用本地工具(read / write / edit / bash),ssh_* 工具只针对远程主机上的路径。用户提到「SSH / 远程服务器 / 服务器操作 / 跳板机 / 隧道 / 部署 / 上传下载」时即指本插件,请据此协作。'
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Mount the SSH engine, routes, tools, and announcement.
|
package/src/routes.ts
CHANGED
|
@@ -225,6 +225,13 @@ const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
|
|
|
225
225
|
const environment = typeof body?.environment === 'string' ? body.environment : undefined
|
|
226
226
|
const timeoutMs = typeof body?.timeoutMs === 'number' ? body.timeoutMs : undefined
|
|
227
227
|
const maxWorkers = typeof body?.maxWorkers === 'number' ? body.maxWorkers : undefined
|
|
228
|
+
const hasAliases = aliases?.some(alias => alias.trim() !== '') === true
|
|
229
|
+
const hasEnvironment = typeof environment === 'string' && environment.trim() !== ''
|
|
230
|
+
const hasTags = tags?.some(tag => tag.trim() !== '') === true
|
|
231
|
+
if (!hasAliases && !hasEnvironment && !hasTags) {
|
|
232
|
+
writeJson(res, 400, { error: 'ssh_cluster requires aliases, environment, or tags to limit the target set' })
|
|
233
|
+
return
|
|
234
|
+
}
|
|
228
235
|
try {
|
|
229
236
|
writeJson(res, 200, { results: await engine.cluster({ command, aliases, environment, tags, timeoutMs, maxWorkers }) })
|
|
230
237
|
} catch (error) {
|
package/src/tools.ts
CHANGED
|
@@ -327,13 +327,13 @@ export function sshTunnelTool(engine: SshEngine) {
|
|
|
327
327
|
export function sshClusterTool(engine: SshEngine) {
|
|
328
328
|
return defineTool({
|
|
329
329
|
name: 'ssh_cluster',
|
|
330
|
-
description: 'Run one command concurrently across
|
|
331
|
-
'Triggers: run on
|
|
330
|
+
description: 'Run one command concurrently across selected SSH hosts; at least one aliases, environment, or tags filter is required. ' +
|
|
331
|
+
'Triggers: run on selected servers, batch operation, production servers, cluster command.',
|
|
332
332
|
parameters: {
|
|
333
333
|
command: { type: 'string', required: true, description: 'The shell command to run on every matched host.' },
|
|
334
|
-
aliases: { type: 'array', items: { type: 'string' }, description: '
|
|
335
|
-
environment: { type: 'string', description: '
|
|
336
|
-
tags: { type: 'array', items: { type: 'string' }, description: '
|
|
334
|
+
aliases: { type: 'array', items: { type: 'string' }, description: 'Optional alias filter; at least one of aliases, environment, or tags is required.' },
|
|
335
|
+
environment: { type: 'string', description: 'Optional environment filter; at least one selector is required.' },
|
|
336
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional ALL-tags filter; at least one selector is required.' },
|
|
337
337
|
timeoutMs: { type: 'integer', description: 'Per-host timeout in milliseconds.' },
|
|
338
338
|
maxWorkers: { type: 'integer', description: 'Concurrency cap (default 8).' },
|
|
339
339
|
},
|
|
@@ -365,6 +365,10 @@ export function sshClusterTool(engine: SshEngine) {
|
|
|
365
365
|
render: (_args, value: { results?: ClusterResult[] }) => text(renderCluster(value.results ?? [])),
|
|
366
366
|
},
|
|
367
367
|
async execute(args) {
|
|
368
|
+
const hasSelector = (Array.isArray(args.aliases) && args.aliases.some((alias) => typeof alias === 'string' && alias.trim() !== '')) ||
|
|
369
|
+
(typeof args.environment === 'string' && args.environment.trim() !== '') ||
|
|
370
|
+
(Array.isArray(args.tags) && args.tags.some((tag) => typeof tag === 'string' && tag.trim() !== ''))
|
|
371
|
+
if (!hasSelector) throw new Error('ssh_cluster requires aliases, environment, or tags to limit the target set')
|
|
368
372
|
return { results: await engine.cluster(args) }
|
|
369
373
|
},
|
|
370
374
|
})
|