@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,189 @@
1
+ /**
2
+ * Wire contract between the host half (routes.ts) and the browser half
3
+ * (client/api.ts). Pure types only — imported by both halves, bundled into
4
+ * each, no runtime identity to share.
5
+ */
6
+ /** Authentication flavors a host entry may carry. */
7
+ export type SshAuthKind = 'key' | 'password';
8
+ /** One stored host entry (the ~/.dsh/dsh-ssh.json store shape). */
9
+ export interface SshHostEntry {
10
+ /** Stable, user-chosen identifier used by every operation. */
11
+ alias: string;
12
+ /** Hostname or IP of the target. */
13
+ host: string;
14
+ /** SSH port (default 22). */
15
+ port: number;
16
+ /** Login user. */
17
+ user: string;
18
+ /** Authentication. */
19
+ auth: {
20
+ kind: SshAuthKind;
21
+ /** Absolute path to the private key for 'key' auth. */
22
+ keyPath?: string;
23
+ /** Passphrase for an encrypted key. */
24
+ passphrase?: string;
25
+ /** Password for 'password' auth. */
26
+ password?: string;
27
+ };
28
+ /** Jump chain: local aliases connected through in order (ProxyJump). */
29
+ proxyJump: string[];
30
+ /** Free-form note. */
31
+ description?: string;
32
+ /** Deployment environment label (development / production / ...). */
33
+ environment?: string;
34
+ /** Free-form tags. */
35
+ tags: string[];
36
+ /** Physical location note. */
37
+ location?: string;
38
+ createdAt: number;
39
+ updatedAt: number;
40
+ }
41
+ /** Public (secret-free) projection of an entry, safe for the browser/agent. */
42
+ export interface SshHostSummary {
43
+ alias: string;
44
+ host: string;
45
+ port: number;
46
+ user: string;
47
+ auth: SshAuthKind;
48
+ /** Whether the key path exists on the host machine (key auth only). */
49
+ keyReady: boolean;
50
+ proxyJump: string[];
51
+ description?: string;
52
+ environment?: string;
53
+ tags: string[];
54
+ location?: string;
55
+ createdAt: number;
56
+ updatedAt: number;
57
+ }
58
+ /** Result of one non-interactive command execution. */
59
+ export interface ExecResult {
60
+ success: boolean;
61
+ /** Remote exit code, or null when the channel died without one. */
62
+ exitCode: number | null;
63
+ timedOut: boolean;
64
+ stdout: string;
65
+ stderr: string;
66
+ /** Wall-clock duration of the round trip in ms. */
67
+ durationMs: number;
68
+ /** Connection error message when the command never ran. */
69
+ error?: string;
70
+ }
71
+ /** One server entry in a cluster run. */
72
+ export interface ClusterResult {
73
+ alias: string;
74
+ ok: boolean;
75
+ exitCode?: number | null;
76
+ timedOut?: boolean;
77
+ stdout?: string;
78
+ stderr?: string;
79
+ durationMs?: number;
80
+ error?: string;
81
+ }
82
+ /** SFTP transfer progress frame (upload stream). */
83
+ export interface TransferProgress {
84
+ phase: 'connecting' | 'transferring' | 'done' | 'error';
85
+ file: string;
86
+ transferred: number;
87
+ total: number;
88
+ percent: number;
89
+ speedBps?: number;
90
+ error?: string;
91
+ }
92
+ /** One active local port-forward tunnel. */
93
+ export interface TunnelInfo {
94
+ id: string;
95
+ alias: string;
96
+ localPort: number;
97
+ remoteHost: string;
98
+ remotePort: number;
99
+ state: 'forwarding' | 'connecting' | 'failed';
100
+ error?: string;
101
+ startedAt: number;
102
+ }
103
+ /** One directory listing entry (remote file browser). */
104
+ export interface RemoteDirEntry {
105
+ name: string;
106
+ type: 'dir' | 'file' | 'other';
107
+ size: number;
108
+ mtimeMs: number;
109
+ mode?: number;
110
+ }
111
+ /** Test-connection outcome. */
112
+ export interface TestResult {
113
+ ok: boolean;
114
+ latencyMs?: number;
115
+ error?: string;
116
+ }
117
+ /** Host edit payload (create/update); 'alias' comes from the URL for updates. */
118
+ export interface HostPayload {
119
+ alias?: string;
120
+ host: string;
121
+ port?: number;
122
+ user: string;
123
+ /**
124
+ * Authentication. Required on create; on update an omitted auth keeps the
125
+ * stored secrets (the browser never receives them back).
126
+ */
127
+ auth?: SshHostEntry['auth'];
128
+ proxyJump?: string[];
129
+ description?: string;
130
+ environment?: string;
131
+ tags?: string[];
132
+ location?: string;
133
+ }
134
+ /** Import outcome from ~/.ssh/config. */
135
+ export interface ImportResult {
136
+ parsed: number;
137
+ added: number;
138
+ skipped: number;
139
+ /** Aliases that failed to map (wildcard patterns, missing HostName, ...). */
140
+ skippedNames: string[];
141
+ }
142
+ /** JSON error body used by every route. */
143
+ export interface ApiErrorBody {
144
+ error: string;
145
+ }
146
+ /** WebSocket terminal protocol frames (host -> client and client -> host). */
147
+ export type TerminalServerFrame = {
148
+ type: 'ready';
149
+ alias: string;
150
+ } | {
151
+ type: 'output';
152
+ data: string;
153
+ } | {
154
+ type: 'exit';
155
+ code: number | null;
156
+ error?: string;
157
+ };
158
+ export type TerminalClientFrame = {
159
+ type: 'input';
160
+ data: string;
161
+ } | {
162
+ type: 'resize';
163
+ cols: number;
164
+ rows: number;
165
+ };
166
+ /** Route paths the client calls (shared literals). */
167
+ export declare const SSH_API_BASE: "/api/dsh-ssh";
168
+ export declare const SSH_API: {
169
+ readonly hosts: string;
170
+ readonly importSshConfig: string;
171
+ readonly test: string;
172
+ readonly exec: string;
173
+ readonly cluster: string;
174
+ readonly upload: string;
175
+ readonly download: string;
176
+ readonly ls: string;
177
+ readonly tunnel: string;
178
+ readonly terminal: string;
179
+ };
180
+ /** NDJSON transfer stream line shapes (upload). */
181
+ export type TransferStreamLine = {
182
+ type: 'progress';
183
+ progress: TransferProgress;
184
+ } | {
185
+ type: 'result';
186
+ ok: boolean;
187
+ transferredBytes?: number;
188
+ error?: string;
189
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The /api/dsh-ssh route family: host CRUD, exec, cluster, SFTP transfer
3
+ * (NDJSON progress stream for uploads, binary stream for downloads), remote
4
+ * listing, tunnels, and the WebSocket PTY terminal upgrade. Every route
5
+ * carries a loopback-only trust fence (plus browser same-origin markers) —
6
+ * these endpoints execute commands on remote servers, so LAN-exposed dsh web
7
+ * deployments must not serve them.
8
+ */
9
+ import type { WebRoute, WebUpgradeRoute } from '@deepseek-ai/dsh-host-webserver';
10
+ import type { SshEngine } from './engine.ts';
11
+ import type { HostStore } from './store.ts';
12
+ /** Route family dependencies. */
13
+ export interface SshRoutesDeps {
14
+ /** The host store (CRUD). */
15
+ store: HostStore;
16
+ /** The engine (ops). */
17
+ engine: SshEngine;
18
+ /** Temp dir for upload/download staging (tests inject a sandbox). */
19
+ stagingDir?: string;
20
+ }
21
+ /**
22
+ * Build every /api/dsh-ssh route (exact paths) plus the terminal upgrade.
23
+ * @param deps - store, engine, staging dir.
24
+ * @returns routes and the upgrade route.
25
+ */
26
+ export declare function makeRoutes(deps: SshRoutesDeps): {
27
+ routes: WebRoute[];
28
+ upgrade: WebUpgradeRoute;
29
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Host config store: one JSON file (`~/.dsh/dsh-ssh.json`) holding every
3
+ * SSH host entry, written atomically (tmp + rename). Also parses the user's
4
+ * standard `~/.ssh/config` for one-shot import. Secrets (passwords,
5
+ * passphrases) live in this user-owned file in plaintext — same trust model
6
+ * as ssh-skill's annotated ssh-config comments; document it, never log it.
7
+ */
8
+ import type { HostPayload, ImportResult, SshHostEntry, SshHostSummary } from './protocol.ts';
9
+ /** Store file location: <home>/.dsh/dsh-ssh.json. */
10
+ export declare function storePath(): string;
11
+ /** The user's standard OpenSSH config path. */
12
+ export declare function sshConfigPath(): string;
13
+ /** Validate the wire shape of a host payload; returns a message or undefined. */
14
+ export declare function validateHostPayload(payload: unknown): string | undefined;
15
+ /** Validate an alias for creation. */
16
+ export declare function validateAlias(alias: string): string | undefined;
17
+ /**
18
+ * The host store. Pure file I/O — no cordis dependency, unit-testable.
19
+ */
20
+ export declare class HostStore {
21
+ /** The JSON file path. */
22
+ readonly path: string;
23
+ /** Optional override of the ~/.ssh/config path (tests). */
24
+ private readonly sshConfigOverride;
25
+ /**
26
+ * @param path - store file path (defaults to the standard location).
27
+ * @param sshConfigOverride - ssh config path override (tests only).
28
+ */
29
+ constructor(path?: string, sshConfigOverride?: string);
30
+ /** Load all entries (empty store when the file is absent). */
31
+ list(): SshHostEntry[];
32
+ /** Find one entry by alias. */
33
+ find(alias: string): SshHostEntry | undefined;
34
+ /** Secret-free projection for the browser and agent surfaces. */
35
+ summarize(entry: SshHostEntry): SshHostSummary;
36
+ /** Create one entry. Throws on alias collision or invalid payload. */
37
+ create(payload: HostPayload): SshHostEntry;
38
+ /** Update the fields present in `patch`; unknown aliases throw. */
39
+ update(alias: string, patch: Partial<HostPayload>): SshHostEntry;
40
+ /** Remove one entry. */
41
+ delete(alias: string): void;
42
+ /**
43
+ * Import hosts from `~/.ssh/config`: Host blocks with a single non-wildcard
44
+ * pattern and a HostName become entries (key auth via IdentityFile, jump
45
+ * hosts via ProxyJump). Existing aliases are skipped.
46
+ * @returns import statistics.
47
+ */
48
+ importFromSshConfig(): ImportResult;
49
+ private skippedNames;
50
+ private load;
51
+ private save;
52
+ }
53
+ /** Expand a leading `~` in a filesystem path. */
54
+ export declare function expandHome(path: string): string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Agent tools: the DSH-native counterpart of ssh-skill's CLI. Every tool
3
+ * talks to the same engine the web UI uses, so a host configured in the GUI
4
+ * is immediately operable by any agent, and vice versa.
5
+ */
6
+ import type { SshEngine } from './engine.ts';
7
+ /** The host-list tool. */
8
+ export declare function sshListTool(engine: SshEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
9
+ /** The command-execution tool. */
10
+ export declare function sshExecTool(engine: SshEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
11
+ /** The upload tool. */
12
+ export declare function sshUploadTool(engine: SshEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
13
+ /** The download tool. */
14
+ export declare function sshDownloadTool(engine: SshEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
15
+ /** The tunnel tool. */
16
+ export declare function sshTunnelTool(engine: SshEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
17
+ /** The cluster tool. */
18
+ export declare function sshClusterTool(engine: SshEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@linxin666/dsh-ssh",
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.1.1",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": "^22.19.0 || >=24.0.0"
8
+ },
9
+ "main": "lib/index.js",
10
+ "types": "lib/types/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/types/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./invariant": {
17
+ "types": "./lib/types/invariant.d.ts",
18
+ "default": "./lib/invariant.js"
19
+ },
20
+ "./client": {
21
+ "types": "./lib/types/client/index.d.ts",
22
+ "default": "./lib/client.js"
23
+ },
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "dsh": {
28
+ "bundle": {
29
+ "patch": "./cordis.patch.yml"
30
+ },
31
+ "client": {
32
+ "inject": [
33
+ "@deepseek-ai/dsh-client-runtime",
34
+ "@deepseek-ai/dsh-client-connection",
35
+ "@deepseek-ai/dsh-client-ui-settings"
36
+ ],
37
+ "platform": "web"
38
+ }
39
+ },
40
+ "dependencies": {
41
+ "@xterm/addon-fit": "^0.11.0",
42
+ "@xterm/xterm": "^6.0.0",
43
+ "ssh2": "^1.17.0",
44
+ "ws": "^8.18.0"
45
+ },
46
+ "peerDependencies": {
47
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
48
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
49
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
50
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
51
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
52
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
53
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
54
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
56
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
57
+ "react": "^18.2.0",
58
+ "react-dom": "^18.2.0"
59
+ },
60
+ "devDependencies": {
61
+ "@deepseek-ai/cordis": "^4.0.1",
62
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
65
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
66
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
68
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
69
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
71
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
72
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
73
+ "@types/node": "^22.20.0",
74
+ "@types/react": "~18.3.1",
75
+ "@types/react-dom": "^18.3.5",
76
+ "@types/ssh2": "^1.15.5",
77
+ "@types/ws": "^8.5.13",
78
+ "jsdom": "^25.0.0",
79
+ "lightningcss": "1.32.0",
80
+ "react": "^18.3.1",
81
+ "react-dom": "^18.3.1",
82
+ "schemastery": "^3.18.0",
83
+ "tsdown": "0.22.2",
84
+ "typescript": "~5.7.2",
85
+ "vitest": "^3.0.0"
86
+ },
87
+ "files": [
88
+ "lib/index.js",
89
+ "lib/invariant.js",
90
+ "lib/client.js",
91
+ "lib/types/**/*.d.ts",
92
+ "lib/types/**/*.d.ts.map",
93
+ "src",
94
+ "cordis.patch.yml",
95
+ "README.md"
96
+ ],
97
+ "license": "BSD-3-Clause",
98
+ "scripts": {
99
+ "build": "tsc -p tsconfig.build.json && tsdown",
100
+ "bundle": "tsdown",
101
+ "watch": "tsdown --watch",
102
+ "typecheck": "tsc --noEmit",
103
+ "test": "vitest run",
104
+ "test:watch": "vitest"
105
+ }
106
+ }