@linxin666/dsh-ssh 0.1.1

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 (52) hide show
  1. package/README.md +75 -0
  2. package/cordis.patch.yml +12 -0
  3. package/lib/client.js +15638 -0
  4. package/lib/index.js +3297 -0
  5. package/lib/invariant.js +6 -0
  6. package/lib/types/client/api.d.ts +72 -0
  7. package/lib/types/client/index.d.ts +37 -0
  8. package/lib/types/client/locales.d.ts +137 -0
  9. package/lib/types/client/mount.d.ts +12 -0
  10. package/lib/types/client/panel/ClusterTab.d.ts +7 -0
  11. package/lib/types/client/panel/HostFormDialog.d.ts +12 -0
  12. package/lib/types/client/panel/HostsTab.d.ts +9 -0
  13. package/lib/types/client/panel/SshPanel.d.ts +13 -0
  14. package/lib/types/client/panel/TerminalTab.d.ts +11 -0
  15. package/lib/types/client/panel/TransferTab.d.ts +7 -0
  16. package/lib/types/client/panel/TunnelsTab.d.ts +7 -0
  17. package/lib/types/client/panel/controller.d.ts +22 -0
  18. package/lib/types/client/panel/helpers.d.ts +14 -0
  19. package/lib/types/client/panel/xterm.css.d.ts +2 -0
  20. package/lib/types/client/sidebar-entry.d.ts +24 -0
  21. package/lib/types/engine.d.ts +135 -0
  22. package/lib/types/index.d.ts +40 -0
  23. package/lib/types/invariant.d.ts +3 -0
  24. package/lib/types/protocol.d.ts +189 -0
  25. package/lib/types/routes.d.ts +29 -0
  26. package/lib/types/store.d.ts +54 -0
  27. package/lib/types/tools.d.ts +18 -0
  28. package/package.json +106 -0
  29. package/src/client/api.ts +363 -0
  30. package/src/client/css-modules.d.ts +5 -0
  31. package/src/client/index.ts +68 -0
  32. package/src/client/locales.ts +284 -0
  33. package/src/client/mount.tsx +83 -0
  34. package/src/client/panel/ClusterTab.tsx +123 -0
  35. package/src/client/panel/HostFormDialog.tsx +221 -0
  36. package/src/client/panel/HostsTab.tsx +166 -0
  37. package/src/client/panel/SshPanel.tsx +77 -0
  38. package/src/client/panel/TerminalTab.tsx +176 -0
  39. package/src/client/panel/TransferTab.tsx +232 -0
  40. package/src/client/panel/TunnelsTab.tsx +177 -0
  41. package/src/client/panel/controller.ts +48 -0
  42. package/src/client/panel/helpers.ts +26 -0
  43. package/src/client/panel/panel.module.css +833 -0
  44. package/src/client/panel/xterm.css.ts +2 -0
  45. package/src/client/sidebar-entry.ts +123 -0
  46. package/src/engine.ts +780 -0
  47. package/src/index.ts +155 -0
  48. package/src/invariant.ts +4 -0
  49. package/src/protocol.ts +188 -0
  50. package/src/routes.ts +540 -0
  51. package/src/store.ts +344 -0
  52. package/src/tools.ts +371 -0
@@ -0,0 +1,6 @@
1
+ //#region src/invariant.ts
2
+ /** Invariant companion plugin (no assertions — nothing to check at runtime). */
3
+ /** Provides no assertions: the ssh plugin owns no cross-package runtime invariants. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Browser-side API client for the /api/dsh-ssh route family. The only data
3
+ * access path the panel components use — plain fetch/WebSocket, same origin.
4
+ */
5
+ import { type ClusterResult, type ExecResult, type HostPayload, type ImportResult, type RemoteDirEntry, type SshHostSummary, type TestResult, type TransferProgress, type TunnelInfo } from '../protocol.ts';
6
+ /** Error carrying the route's JSON error message. */
7
+ export declare class SshApiError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /** One open terminal connection (WebSocket JSON frames). */
11
+ export interface TerminalConnection {
12
+ /** Fired on the ready frame (shell is up). */
13
+ onReady: (() => void) | undefined;
14
+ /** Fired on every output frame. */
15
+ onOutput: ((data: string) => void) | undefined;
16
+ /** Fired on the exit frame (or transport error). */
17
+ onExit: ((code: number | null, error?: string) => void) | undefined;
18
+ /** Send raw input to the remote shell. */
19
+ send(data: string): void;
20
+ /** Resize the remote PTY. */
21
+ resize(cols: number, rows: number): void;
22
+ /** Close the socket and the remote session. */
23
+ close(): void;
24
+ }
25
+ /** The browser half's only data entry point. */
26
+ export declare class SshApi {
27
+ listHosts(queryText?: string): Promise<SshHostSummary[]>;
28
+ createHost(payload: HostPayload): Promise<SshHostSummary>;
29
+ updateHost(alias: string, patch: HostPayload): Promise<SshHostSummary>;
30
+ deleteHost(alias: string): Promise<void>;
31
+ importSshConfig(): Promise<ImportResult>;
32
+ testHost(alias: string): Promise<TestResult>;
33
+ exec(alias: string, command: string, timeoutMs?: number): Promise<ExecResult>;
34
+ cluster(options: {
35
+ command: string;
36
+ aliases?: string[];
37
+ environment?: string;
38
+ tags?: string[];
39
+ timeoutMs?: number;
40
+ maxWorkers?: number;
41
+ }): Promise<ClusterResult[]>;
42
+ ls(alias: string, path: string): Promise<RemoteDirEntry[]>;
43
+ listTunnels(): Promise<TunnelInfo[]>;
44
+ startTunnel(options: {
45
+ alias: string;
46
+ remotePort: number;
47
+ remoteHost?: string;
48
+ localPort?: number;
49
+ }): Promise<TunnelInfo>;
50
+ stopTunnel(tunnelId: string): Promise<boolean>;
51
+ stopAllTunnels(alias?: string): Promise<number>;
52
+ /**
53
+ * Upload one file (raw bytes) to a remote path. Progress arrives through
54
+ * the NDJSON response stream; resolves when the result frame lands.
55
+ */
56
+ uploadFile(file: File, alias: string, remotePath: string, onProgress?: (progress: TransferProgress) => void): Promise<{
57
+ transferredBytes: number;
58
+ }>;
59
+ /**
60
+ * Download a remote file with client-side progress. Streams straight to
61
+ * disk when the File System Access API is available (no full-file RAM
62
+ * copy); otherwise falls back to an in-memory Blob.
63
+ */
64
+ downloadFile(alias: string, remotePath: string, onProgress?: (progress: TransferProgress) => void): Promise<{
65
+ blob?: Blob;
66
+ filename: string;
67
+ streamed: boolean;
68
+ bytes: number;
69
+ }>;
70
+ /** Open a WebSocket terminal session. */
71
+ openTerminal(alias: string, cols: number, rows: number): TerminalConnection;
72
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Browser-half entry for the dsh-ssh plugin — runs inside the dsh web GUI.
3
+ *
4
+ * Registers the dsh-ssh locale dictionaries and mounts the two DOM surfaces:
5
+ * the sidebar entry row (toggles the panel) and the SSH operations panel in
6
+ * the center column. Failure policy: DOM mounting problems are logged, never
7
+ * thrown — the web shell fails the whole boot when a plugin apply throws, and
8
+ * an external plugin must not take the GUI down.
9
+ *
10
+ * Export discipline (packages/client rule): the /client surface carries what
11
+ * cordis loading needs plus types only — all value exports stay internal.
12
+ */
13
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
14
+ import { type SshKey } from './locales.ts';
15
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
16
+ interface LocaleNamespaceMap {
17
+ /** dsh-ssh surface copy. */
18
+ 'dsh-ssh': SshKey;
19
+ }
20
+ }
21
+ /** Required services (fiber inject waiting — the runtime must be up first). */
22
+ export declare const inject: string[];
23
+ /** Type-only surface (export discipline: no value exports beyond the plugin contract). */
24
+ export type { PanelControllerSnapshot } from './panel/controller.ts';
25
+ export type { SshPanelProps } from './panel/SshPanel.tsx';
26
+ export type { HostsTabProps } from './panel/HostsTab.tsx';
27
+ export type { HostFormDialogProps } from './panel/HostFormDialog.tsx';
28
+ export type { TerminalTabProps } from './panel/TerminalTab.tsx';
29
+ export type { TransferTabProps } from './panel/TransferTab.tsx';
30
+ export type { TunnelsTabProps } from './panel/TunnelsTab.tsx';
31
+ export type { ClusterTabProps } from './panel/ClusterTab.tsx';
32
+ export type { SshKey } from './locales.ts';
33
+ /**
34
+ * Mount the SSH panel.
35
+ * @param ctx - client root context (locale service).
36
+ */
37
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * dsh-ssh surface copy: zh is the key source, en mirrors every key.
3
+ */
4
+ export declare const zh: {
5
+ readonly 'entry.label': "SSH";
6
+ readonly 'entry.tooltip': "远程 SSH 运维面板";
7
+ readonly 'panel.title': "远程 SSH";
8
+ readonly 'tab.hosts': "主机";
9
+ readonly 'tab.terminal': "终端";
10
+ readonly 'tab.transfer': "传输";
11
+ readonly 'tab.tunnels': "隧道";
12
+ readonly 'tab.cluster': "集群";
13
+ readonly 'hosts.empty': "尚未配置主机。点击「新增主机」添加,或从 ~/.ssh/config 导入。";
14
+ readonly 'hosts.add': "新增主机";
15
+ readonly 'hosts.import': "导入 ~/.ssh/config";
16
+ readonly 'hosts.search': "搜索别名 / 描述 / 标签…";
17
+ readonly 'hosts.col.alias': "别名";
18
+ readonly 'hosts.col.host': "主机";
19
+ readonly 'hosts.col.user': "用户";
20
+ readonly 'hosts.col.auth': "认证";
21
+ readonly 'hosts.col.environment': "环境";
22
+ readonly 'hosts.col.tags': "标签";
23
+ readonly 'hosts.col.description': "备注";
24
+ readonly 'hosts.col.actions': "操作";
25
+ readonly 'hosts.test': "测试";
26
+ readonly 'hosts.edit': "编辑";
27
+ readonly 'hosts.delete': "删除";
28
+ readonly 'hosts.deleteConfirm': "确定删除主机「{alias}」?将同时停止其隧道。";
29
+ readonly 'hosts.imported': "解析 {parsed} 个配置块,新增 {added} 台主机,跳过 {skipped} 个。";
30
+ readonly 'hosts.testing': "连接测试中…";
31
+ readonly 'hosts.testOk': "连接成功({latency} ms)";
32
+ readonly 'hosts.testFail': "连接失败:{error}";
33
+ readonly 'hosts.connected': "连接";
34
+ readonly 'hosts.notConnected': "未连接";
35
+ readonly 'form.title.create': "新增主机";
36
+ readonly 'form.title.edit': "编辑主机 {alias}";
37
+ readonly 'form.alias': "别名";
38
+ readonly 'form.aliasHint': "小写字母、数字与单连字符,操作时使用";
39
+ readonly 'form.host': "主机地址";
40
+ readonly 'form.port': "端口";
41
+ readonly 'form.user': "用户名";
42
+ readonly 'form.auth': "认证方式";
43
+ readonly 'form.auth.key': "密钥";
44
+ readonly 'form.auth.password': "密码";
45
+ readonly 'form.authKeepHint': "编辑时密钥/密码留空将保留原认证方式与已存凭据;如需更换认证方式请填写新凭据。";
46
+ readonly 'form.keyPath': "私钥路径";
47
+ readonly 'form.keyPathHint': "如 ~/.ssh/id_ed25519";
48
+ readonly 'form.passphrase': "密钥口令";
49
+ readonly 'form.password': "密码";
50
+ readonly 'form.passwordHint': "明文保存在 ~/.dsh/dsh-ssh.json(本机私有文件)";
51
+ readonly 'form.proxyJump': "跳板机别名";
52
+ readonly 'form.proxyJumpHint': "逗号分隔的本地主机别名,按顺序连接";
53
+ readonly 'form.description': "备注";
54
+ readonly 'form.environment': "环境";
55
+ readonly 'form.environmentHint': "如 development / production";
56
+ readonly 'form.tags': "标签";
57
+ readonly 'form.tagsHint': "逗号分隔";
58
+ readonly 'form.location': "位置";
59
+ readonly 'form.save': "保存";
60
+ readonly 'form.cancel': "取消";
61
+ readonly 'form.required': "请填写必填字段:别名、主机地址、用户名。";
62
+ readonly 'form.portInvalid': "端口必须是 1-65535 之间的整数。";
63
+ readonly 'terminal.selectHost': "选择主机";
64
+ readonly 'terminal.connect': "连接";
65
+ readonly 'terminal.disconnect': "断开";
66
+ readonly 'terminal.placeholder': "选择主机后点击「连接」打开远程终端。";
67
+ readonly 'terminal.connecting': "正在连接…";
68
+ readonly 'terminal.ready': "终端已连接({alias})";
69
+ readonly 'terminal.exited': "终端已退出({alias})";
70
+ readonly 'terminal.error': "终端错误:{error}";
71
+ readonly 'transfer.selectHost': "选择主机";
72
+ readonly 'transfer.remotePath': "远程路径";
73
+ readonly 'transfer.remotePathHint': "如 /tmp/ 或 /home/user/app.tar.gz";
74
+ readonly 'transfer.upload': "上传";
75
+ readonly 'transfer.uploading': "正在上传 {file}…";
76
+ readonly 'transfer.download': "下载";
77
+ readonly 'transfer.downloading': "正在下载…";
78
+ readonly 'transfer.done': "完成:{bytes} 字节";
79
+ readonly 'transfer.failed': "传输失败:{error}";
80
+ readonly 'transfer.browseRemote': "浏览远程目录";
81
+ readonly 'transfer.refresh': "刷新";
82
+ readonly 'transfer.name': "名称";
83
+ readonly 'transfer.upLevel': "上级目录";
84
+ readonly 'transfer.size': "大小";
85
+ readonly 'transfer.modified': "修改时间";
86
+ readonly 'transfer.bytes': "{value} B";
87
+ readonly 'transfer.kib': "{value} KB";
88
+ readonly 'transfer.mib': "{value} MB";
89
+ readonly 'transfer.gib': "{value} GB";
90
+ readonly 'transfer.percent': "{value}%";
91
+ readonly 'transfer.speed': "{value}/s";
92
+ readonly 'transfer.dir': "目录";
93
+ readonly 'transfer.file': "文件";
94
+ readonly 'tunnel.empty': "没有活跃的隧道。";
95
+ readonly 'tunnel.start': "新建隧道";
96
+ readonly 'tunnel.alias': "主机别名";
97
+ readonly 'tunnel.remotePort': "远程端口";
98
+ readonly 'tunnel.remoteHost': "远程主机";
99
+ readonly 'tunnel.remoteHostHint': "默认 127.0.0.1(服务器自身)";
100
+ readonly 'tunnel.localPort': "本地端口";
101
+ readonly 'tunnel.localPortHint': "留空自动分配";
102
+ readonly 'tunnel.stop': "停止";
103
+ readonly 'tunnel.stopAll': "全部停止";
104
+ readonly 'tunnel.stopAllConfirm': "确定停止全部隧道吗?";
105
+ readonly 'tunnel.row': "{alias}: 127.0.0.1:{localPort} -> {remoteHost}:{remotePort}";
106
+ readonly 'tunnel.started': "隧道已建立:127.0.0.1:{localPort}";
107
+ readonly 'tunnel.failed': "隧道启动失败:{error}";
108
+ readonly 'cluster.command': "命令";
109
+ readonly 'cluster.run': "执行";
110
+ readonly 'cluster.confirm': "确定在匹配的主机上执行该命令吗?该操作会真实运行在远程服务器上。";
111
+ readonly 'cluster.aliases': "主机别名(逗号分隔,留空为全部)";
112
+ readonly 'cluster.environment': "环境过滤";
113
+ readonly 'cluster.tags': "标签过滤(逗号分隔)";
114
+ readonly 'cluster.results': "执行结果";
115
+ readonly 'cluster.empty': "输入命令后点击「执行」。";
116
+ readonly 'cluster.noMatch': "没有匹配的主机。";
117
+ readonly 'cluster.ok': "成功";
118
+ readonly 'cluster.fail': "失败";
119
+ readonly 'cluster.timeout': "超时";
120
+ readonly 'cluster.col.alias': "主机";
121
+ readonly 'cluster.col.status': "状态";
122
+ readonly 'cluster.col.exit': "退出码";
123
+ readonly 'cluster.col.stdout': "标准输出";
124
+ readonly 'cluster.col.stderr': "标准错误";
125
+ readonly 'cluster.col.error': "错误";
126
+ readonly 'cluster.col.duration': "耗时";
127
+ readonly 'common.loading': "加载中…";
128
+ readonly 'common.error': "出错:{error}";
129
+ readonly 'common.refresh': "刷新";
130
+ readonly 'common.close': "关闭";
131
+ readonly 'common.confirm': "确定";
132
+ };
133
+ export declare const en: Record<keyof typeof zh, string>;
134
+ /** Locale key union. */
135
+ export type SshKey = keyof typeof zh;
136
+ /** Tiny interpolation: {name} -> value. */
137
+ export declare function t(dictionary: Record<string, string>, key: string, values?: Record<string, string | number>): string;
@@ -0,0 +1,12 @@
1
+ import type { SshApi } from './api.ts';
2
+ import type { PanelController } from './panel/controller.ts';
3
+ /** The injected panel container (kept in the DOM, hidden when inactive). */
4
+ export declare const PANEL_VIEW_SELECTOR = "[data-dsh-ssh-view]";
5
+ /**
6
+ * Mount the panel React tree into the center column and bind its visibility
7
+ * to the controller's panelOpen state.
8
+ * @param controller - the panel controller driving the view.
9
+ * @param api - the SSH API client the tabs operate through.
10
+ * @returns disposer unmounting the tree and restoring the column.
11
+ */
12
+ export declare function mountPanel(controller: PanelController, api: SshApi): () => void;
@@ -0,0 +1,7 @@
1
+ import type { SshApi } from '../api.ts';
2
+ /** Cluster tab props. */
3
+ export interface ClusterTabProps {
4
+ api: SshApi;
5
+ }
6
+ /** The cluster execution tab. */
7
+ export declare function ClusterTab({ api }: ClusterTabProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import type { SshApi } from '../api.ts';
2
+ import type { SshHostSummary } from '../../protocol.ts';
3
+ /** Host form dialog props. */
4
+ export interface HostFormDialogProps {
5
+ api: SshApi;
6
+ /** The host being edited; null/undefined means create. */
7
+ editing?: SshHostSummary | null;
8
+ onClose: () => void;
9
+ onSaved: (host: SshHostSummary) => void;
10
+ }
11
+ /** The create/edit host modal. */
12
+ export declare function HostFormDialog({ api, editing, onClose, onSaved }: HostFormDialogProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import type { SshApi } from '../api.ts';
2
+ /** Hosts tab props. */
3
+ export interface HostsTabProps {
4
+ api: SshApi;
5
+ /** Connect the given alias in the terminal tab. */
6
+ onConnect: (alias: string) => void;
7
+ }
8
+ /** The hosts table plus its toolbar and dialogs. */
9
+ export declare function HostsTab({ api, onConnect }: HostsTabProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import type { SshApi } from '../api.ts';
2
+ import type { PanelController } from './controller.ts';
3
+ /** The panel's tab identifiers. */
4
+ export type SshTab = 'hosts' | 'terminal' | 'transfer' | 'tunnels' | 'cluster';
5
+ /** Panel shell props. */
6
+ export interface SshPanelProps {
7
+ /** The panel state owner (open/close/toggle). */
8
+ controller: PanelController;
9
+ /** The SSH API client every tab operates through. */
10
+ api: SshApi;
11
+ }
12
+ /** The tabbed SSH panel. */
13
+ export declare function SshPanel({ controller, api }: SshPanelProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import type { SshApi } from '../api.ts';
2
+ /** Terminal tab props. */
3
+ export interface TerminalTabProps {
4
+ api: SshApi;
5
+ /** Alias preselected by a "connect" action from the hosts tab. */
6
+ presetAlias?: string;
7
+ /** Monotonic id of the connect request (re-applies presetAlias). */
8
+ requestId?: number;
9
+ }
10
+ /** The xterm terminal view. */
11
+ export declare function TerminalTab({ api, presetAlias, requestId }: TerminalTabProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { SshApi } from '../api.ts';
2
+ /** Transfer tab props. */
3
+ export interface TransferTabProps {
4
+ api: SshApi;
5
+ }
6
+ /** The upload/download tab. */
7
+ export declare function TransferTab({ api }: TransferTabProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { SshApi } from '../api.ts';
2
+ /** Tunnels tab props. */
3
+ export interface TunnelsTabProps {
4
+ api: SshApi;
5
+ }
6
+ /** The tunnels tab. */
7
+ export declare function TunnelsTab({ api }: TunnelsTabProps): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * SSH panel controller: the single owner of the panel's open/closed state.
3
+ *
4
+ * Framework-free (structural runtime faces, task-board core/controller.ts
5
+ * style) so the DOM mounts and the React panel share one tiny subscription
6
+ * surface. The state lives only for the browser session (no persistence).
7
+ */
8
+ /** Immutable controller snapshot for UI subscriptions. */
9
+ export interface PanelControllerSnapshot {
10
+ panelOpen: boolean;
11
+ }
12
+ /** The panel state owner the sidebar entry toggles and the view renders from. */
13
+ export declare class PanelController {
14
+ private panelOpen;
15
+ private listeners;
16
+ getSnapshot(): PanelControllerSnapshot;
17
+ subscribe(fn: () => void): () => void;
18
+ open(): void;
19
+ close(): void;
20
+ toggle(): void;
21
+ private notify;
22
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared panel helpers: the active-dictionary pick (document-language based,
3
+ * task-board precedent) bound to the dsh-ssh interpolator in locales.ts, plus
4
+ * a small error-message extractor. All copy stays in the locale dictionaries.
5
+ */
6
+ import { type SshKey } from '../locales.ts';
7
+ /** Template values accepted by the interpolator. */
8
+ export type TranslateValues = Record<string, string | number>;
9
+ /** Active dictionary, picked by the document language at call time. */
10
+ export declare function dictionary(): Record<string, string>;
11
+ /** Translate a key with optional {name} template params (current language). */
12
+ export declare function tt(key: SshKey, values?: TranslateValues): string;
13
+ /** Human-readable error text from an unknown thrown value. */
14
+ export declare function errorMessage(error: unknown): string;
@@ -0,0 +1,2 @@
1
+ /** xterm.css text embedded at build-authoring time (the ?raw suffix is not resolvable by the tsdown/rolldown pipeline). Generated file — see the package docs. */
2
+ export declare const XTERM_CSS: string;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Sidebar entry injection.
3
+ *
4
+ * dsh's sidebar shell exposes no slot an external plugin can register into,
5
+ * so — following the task-board precedent of DOM-level extension — the entry
6
+ * row is injected between the shell's New Session button and the workspace
7
+ * browser. The injection self-heals: a MutationObserver watches the sidebar
8
+ * root and re-inserts the row whenever a React re-render displaces it
9
+ * (re-insertion happens in the same frame, before paint, so no flicker).
10
+ *
11
+ * The row is plain DOM (no React tree) so it can never disturb the shell's
12
+ * reconciliation; the panel view it toggles is a separate React root mounted
13
+ * in the center column (see mount.tsx).
14
+ */
15
+ import type { PanelController } from './panel/controller.ts';
16
+ /** Stable data attribute identifying the injected entry row. */
17
+ export declare const ENTRY_SELECTOR = "[data-dsh-ssh-entry]";
18
+ /**
19
+ * Mount the sidebar entry, waiting for the shell to render and self-healing
20
+ * on later React re-renders.
21
+ * @param controller - the panel controller the entry toggles.
22
+ * @returns disposer removing the entry and its observers.
23
+ */
24
+ export declare function mountSidebarEntry(controller: PanelController): () => void;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The SSH engine: a per-alias persistent connection pool (ssh2) with
3
+ * multi-hop jump support, command execution, PTY shells, SFTP transfers,
4
+ * local port-forward tunnels and cluster execution — the DSH counterpart of
5
+ * ssh-skill's daemon + scripts, living entirely in the host process.
6
+ */
7
+ import type { ClusterResult, ExecResult, SshHostSummary, TestResult, TransferProgress, TunnelInfo } from './protocol.ts';
8
+ import { type HostStore } from './store.ts';
9
+ /** Default engine knobs. */
10
+ export interface EngineOptions {
11
+ /** Connections idle longer than this are closed (ms). */
12
+ idleTimeoutMs?: number;
13
+ /** SSH handshake timeout (ms). */
14
+ connectTimeoutMs?: number;
15
+ /** Keepalive ping interval (ms). */
16
+ keepaliveIntervalMs?: number;
17
+ /** Cap on captured stdout/stderr bytes per exec (ms). */
18
+ maxOutputBytes?: number;
19
+ /** Default exec timeout (ms). */
20
+ defaultExecTimeoutMs?: number;
21
+ /** Default cluster concurrency. */
22
+ defaultMaxWorkers?: number;
23
+ /** SFTP concurrent channel count for transfers. */
24
+ sftpConcurrency?: number;
25
+ }
26
+ /** A live PTY shell session. */
27
+ export interface ShellSession {
28
+ /** Assign to receive remote output. */
29
+ onData?: (data: Buffer) => void;
30
+ /** Assign to be notified when the channel closes. */
31
+ onExit?: (code: number | null, error?: string) => void;
32
+ /** Write raw input to the shell. */
33
+ send(data: string): void;
34
+ /** Resize the remote PTY. */
35
+ resize(cols: number, rows: number): void;
36
+ /** Close the session and its channel. */
37
+ close(): void;
38
+ /** Pause remote output delivery (transport backpressure). */
39
+ pause(): void;
40
+ /** Resume remote output delivery. */
41
+ resume(): void;
42
+ }
43
+ /**
44
+ * The engine. Owns the pool, tunnels, and all operations. One instance per
45
+ * plugin apply; dispose() closes every connection.
46
+ */
47
+ export declare class SshEngine {
48
+ private readonly store;
49
+ private readonly opts;
50
+ private readonly pool;
51
+ private readonly tunnels;
52
+ private sweepTimer;
53
+ private nextTunnelId;
54
+ /**
55
+ * @param store - the host config store.
56
+ * @param options - engine knobs (defaults applied).
57
+ */
58
+ constructor(store: HostStore, options?: EngineOptions);
59
+ /** Secret-free host list (filtered by the optional query). */
60
+ list(query?: string): SshHostSummary[];
61
+ /** One host summary by alias. */
62
+ find(alias: string): SshHostSummary | undefined;
63
+ /**
64
+ * Run `fn` with a live client for `alias`, reconnecting (up to the
65
+ * attempt budget) when the connection broke mid-flight.
66
+ */
67
+ private withClient;
68
+ /**
69
+ * Build one full jump chain for an entry: hop clients connected through in
70
+ * order, each forwarding a stream to the next destination, ending with the
71
+ * target client. Shared by the pool and standalone shell sessions.
72
+ */
73
+ private connectChain;
74
+ /** In-flight acquire promises, deduped per alias (concurrent first use). */
75
+ private readonly acquireQueue;
76
+ /** Connect (or reuse) the pooled chain for one alias; pins nothing. */
77
+ private acquire;
78
+ private doAcquire;
79
+ /**
80
+ * Tear down one alias's record. When `record` is given and no longer the
81
+ * pooled record for the alias (a concurrent acquire replaced it), nothing
82
+ * is torn down — the connection belongs to someone else now.
83
+ */
84
+ private disposeRecord;
85
+ /** Close connections idle beyond the threshold (skips pinned and in-flight). */
86
+ private sweep;
87
+ /** Run one command on `alias` (reusing the pooled connection). */
88
+ exec(alias: string, command: string, timeoutMs?: number): Promise<ExecResult>;
89
+ /** Run one command against many hosts concurrently. */
90
+ cluster(options: {
91
+ command: string;
92
+ aliases?: string[];
93
+ environment?: string;
94
+ tags?: string[];
95
+ timeoutMs?: number;
96
+ maxWorkers?: number;
97
+ }): Promise<ClusterResult[]>;
98
+ /** Open a PTY shell session for the web terminal (standalone connection). */
99
+ openShell(alias: string, size: {
100
+ cols: number;
101
+ rows: number;
102
+ }): Promise<ShellSession>;
103
+ /** Upload one local file (or directory tree) to a remote path. */
104
+ upload(alias: string, localPath: string, remotePath: string, recursive: boolean, onProgress?: (progress: TransferProgress) => void): Promise<{
105
+ bytes: number;
106
+ files: number;
107
+ }>;
108
+ /** Download one remote file to a local path. */
109
+ download(alias: string, remotePath: string, localPath: string, onProgress?: (progress: TransferProgress) => void): Promise<{
110
+ bytes: number;
111
+ }>;
112
+ /** List a remote directory (file browser). */
113
+ ls(alias: string, path: string): Promise<import('./protocol.ts').RemoteDirEntry[]>;
114
+ private sftp;
115
+ /** Create a remote directory chain (stat-then-mkdir per segment). */
116
+ private ensureRemoteDir;
117
+ private fastPut;
118
+ private fastGet;
119
+ /** Start a local port-forward tunnel (listens on 127.0.0.1 only). */
120
+ startTunnel(alias: string, options: {
121
+ remotePort: number;
122
+ remoteHost?: string;
123
+ localPort?: number;
124
+ }): Promise<TunnelInfo>;
125
+ /** All active tunnels. */
126
+ listTunnels(): TunnelInfo[];
127
+ /** Stop one tunnel (closes the listener, live sockets, and the pinned connection). */
128
+ stopTunnel(id: string): boolean;
129
+ /** Stop all tunnels (optionally for one alias). */
130
+ stopAllTunnels(alias?: string): number;
131
+ /** Probe connectivity: connect, run `true`, close. */
132
+ test(alias: string): Promise<TestResult>;
133
+ /** Close every pooled connection and tunnel. */
134
+ dispose(): void;
135
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * dsh-ssh — host half. Mounts the SSH engine (persistent ssh2 connection
3
+ * pool, exec / PTY shell / SFTP / tunnels / cluster), the /api/dsh-ssh route
4
+ * family plus the terminal WebSocket upgrade, the agent tools (ssh_list,
5
+ * ssh_exec, ssh_upload, ssh_download, ssh_tunnel, ssh_cluster), and a
6
+ * system-prompt announcement. The browser half (./client) renders the host
7
+ * manager and web terminal. Everything rides official NPM SDK packages —
8
+ * no dsh source changes.
9
+ */
10
+ import type { Context } from '@deepseek-ai/cordis';
11
+ import z from 'schemastery';
12
+ /** Stable cordis plugin name. */
13
+ export declare const name = "ssh";
14
+ /** Services required before the SSH surfaces can mount. */
15
+ export declare const inject: string[];
16
+ /**
17
+ * Settings namespace of the SSH capability — the section the web settings
18
+ * surface edits. Spelled here rather than imported: the browser half spells
19
+ * the same value and must not depend on a Host package.
20
+ */
21
+ export declare const SSH_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
22
+ /** Plugin config, validated by the same-named schemastery schema. */
23
+ export interface Config {
24
+ /**
25
+ * When true (default), a system-prompt section announces the SSH plugin to
26
+ * every agent (tools + host store). Set false to keep it silent.
27
+ */
28
+ announceToAgent?: boolean;
29
+ /** Master switch for the plugin (routes, tools, prompt section). */
30
+ enabled?: boolean;
31
+ }
32
+ export declare const Config: z<Config>;
33
+ /** Model-facing announcement: plugin presence, capabilities, and limits. */
34
+ 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-ui \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/dsh-ssh.json\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\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\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";
35
+ /**
36
+ * Mount the SSH engine, routes, tools, and announcement.
37
+ * @param ctx - host plugin context carrying webServer/tools/systemPrompt.
38
+ * @param config - resolved plugin config (schema defaults applied by the loader).
39
+ */
40
+ export declare function apply(ctx: Context, config?: Config): void;
@@ -0,0 +1,3 @@
1
+ /** Invariant companion plugin (no assertions — nothing to check at runtime). */
2
+ /** Provides no assertions: the ssh plugin owns no cross-package runtime invariants. */
3
+ export declare function apply(): void;