@tiphareth/dsh-hardssh 0.1.2
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 +50 -0
- package/cordis.patch.yml +26 -0
- package/lib/client.js +17780 -0
- package/lib/environment-BL1jddfB.js +449 -0
- package/lib/fs.js +478 -0
- package/lib/index.js +6758 -0
- package/lib/subprocess.js +600 -0
- package/lib/switch-fs-CAJpFY9C.js +193 -0
- package/lib/switch-fs-RrZtG2gv.js +210 -0
- package/lib/types/backend.d.ts +108 -0
- package/lib/types/base/capability.d.ts +107 -0
- package/lib/types/base/index.d.ts +18 -0
- package/lib/types/base/ledger-router.d.ts +48 -0
- package/lib/types/base/ledger.d.ts +82 -0
- package/lib/types/base/model.d.ts +108 -0
- package/lib/types/base/namespace.d.ts +57 -0
- package/lib/types/base/plugin.d.ts +102 -0
- package/lib/types/base/registry.d.ts +60 -0
- package/lib/types/base/router.d.ts +40 -0
- package/lib/types/client/api.d.ts +85 -0
- package/lib/types/client/directory-flow.d.ts +82 -0
- package/lib/types/client/icons.d.ts +19 -0
- package/lib/types/client/index.d.ts +34 -0
- package/lib/types/client/locales.d.ts +104 -0
- package/lib/types/client/manager-button.d.ts +32 -0
- package/lib/types/client/migrate.d.ts +20 -0
- package/lib/types/client/ssh/api.d.ts +89 -0
- package/lib/types/client/ssh/apply.d.ts +23 -0
- package/lib/types/client/ssh/locales.d.ts +156 -0
- package/lib/types/client/ssh/mount.d.ts +13 -0
- package/lib/types/client/ssh/panel/ClusterTab.d.ts +8 -0
- package/lib/types/client/ssh/panel/HostFingerprintDialog.d.ts +16 -0
- package/lib/types/client/ssh/panel/HostFormDialog.d.ts +13 -0
- package/lib/types/client/ssh/panel/HostsTab.d.ts +10 -0
- package/lib/types/client/ssh/panel/SessionSecretDialog.d.ts +16 -0
- package/lib/types/client/ssh/panel/SshPanel.d.ts +14 -0
- package/lib/types/client/ssh/panel/TerminalTab.d.ts +12 -0
- package/lib/types/client/ssh/panel/TransferTab.d.ts +8 -0
- package/lib/types/client/ssh/panel/TunnelsTab.d.ts +8 -0
- package/lib/types/client/ssh/panel/controller.d.ts +23 -0
- package/lib/types/client/ssh/panel/helpers.d.ts +15 -0
- package/lib/types/client/ssh/panel/xterm.css.d.ts +3 -0
- package/lib/types/client/ssh/sidebar-entry.d.ts +25 -0
- package/lib/types/client/state.d.ts +32 -0
- package/lib/types/client/text.d.ts +11 -0
- package/lib/types/client/workspace-badges.d.ts +38 -0
- package/lib/types/client/workspace-gate.d.ts +15 -0
- package/lib/types/client-http.d.ts +15 -0
- package/lib/types/core.d.ts +43 -0
- package/lib/types/fs.d.ts +36 -0
- package/lib/types/host-http.d.ts +25 -0
- package/lib/types/index.d.ts +53 -0
- package/lib/types/ledger.d.ts +132 -0
- package/lib/types/protocol.d.ts +101 -0
- package/lib/types/providers/index.d.ts +19 -0
- package/lib/types/providers/local/provider.d.ts +58 -0
- package/lib/types/providers/ssh/provider.d.ts +112 -0
- package/lib/types/remote/environment.d.ts +29 -0
- package/lib/types/remote/output.d.ts +38 -0
- package/lib/types/remote/remote-fs.d.ts +69 -0
- package/lib/types/remote/remote-process.d.ts +41 -0
- package/lib/types/remote/remote-subprocess.d.ts +31 -0
- package/lib/types/remote/remote-terminal.d.ts +41 -0
- package/lib/types/remote-runner.d.ts +83 -0
- package/lib/types/remote-search.d.ts +50 -0
- package/lib/types/routes.d.ts +24 -0
- package/lib/types/runtime/workspace-core.d.ts +59 -0
- package/lib/types/seam-state.d.ts +69 -0
- package/lib/types/shell.d.ts +8 -0
- package/lib/types/ssh/connection/lease.d.ts +16 -0
- package/lib/types/ssh/connection/pool.d.ts +57 -0
- package/lib/types/ssh/engine.d.ts +434 -0
- package/lib/types/ssh/exec/output.d.ts +31 -0
- package/lib/types/ssh/known-hosts.d.ts +89 -0
- package/lib/types/ssh/plugin.d.ts +57 -0
- package/lib/types/ssh/protocol.d.ts +236 -0
- package/lib/types/ssh/routes.d.ts +44 -0
- package/lib/types/ssh/store.d.ts +107 -0
- package/lib/types/ssh/tools.d.ts +35 -0
- package/lib/types/ssh/transfer/progress.d.ts +14 -0
- package/lib/types/ssh/vault.d.ts +110 -0
- package/lib/types/subprocess.d.ts +32 -0
- package/lib/types/switch/switch-fs.d.ts +88 -0
- package/lib/types/switch/switch-subprocess.d.ts +34 -0
- package/lib/types/tools.d.ts +12 -0
- package/package.json +134 -0
- package/src/backend.ts +624 -0
- package/src/base/capability.ts +86 -0
- package/src/base/index.ts +18 -0
- package/src/base/ledger-router.ts +128 -0
- package/src/base/ledger.ts +299 -0
- package/src/base/model.ts +118 -0
- package/src/base/namespace.ts +102 -0
- package/src/base/plugin.ts +170 -0
- package/src/base/registry.ts +109 -0
- package/src/base/router.ts +43 -0
- package/src/client/api.ts +196 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/directory-flow.tsx +482 -0
- package/src/client/icons.tsx +50 -0
- package/src/client/index.ts +210 -0
- package/src/client/locales.ts +105 -0
- package/src/client/manager-button.tsx +269 -0
- package/src/client/migrate.ts +109 -0
- package/src/client/ssh/api.ts +411 -0
- package/src/client/ssh/apply.ts +50 -0
- package/src/client/ssh/locales.ts +322 -0
- package/src/client/ssh/mount.tsx +83 -0
- package/src/client/ssh/panel/ClusterTab.tsx +123 -0
- package/src/client/ssh/panel/HostFingerprintDialog.tsx +82 -0
- package/src/client/ssh/panel/HostFormDialog.tsx +228 -0
- package/src/client/ssh/panel/HostsTab.tsx +236 -0
- package/src/client/ssh/panel/SessionSecretDialog.tsx +80 -0
- package/src/client/ssh/panel/SshPanel.tsx +77 -0
- package/src/client/ssh/panel/TerminalTab.tsx +176 -0
- package/src/client/ssh/panel/TransferTab.tsx +232 -0
- package/src/client/ssh/panel/TunnelsTab.tsx +177 -0
- package/src/client/ssh/panel/controller.ts +48 -0
- package/src/client/ssh/panel/helpers.ts +26 -0
- package/src/client/ssh/panel/panel.module.css +1006 -0
- package/src/client/ssh/panel/xterm.css.ts +2 -0
- package/src/client/ssh/sidebar-entry.ts +123 -0
- package/src/client/state.ts +99 -0
- package/src/client/text.ts +22 -0
- package/src/client/workspace-badges.ts +96 -0
- package/src/client/workspace-gate.ts +225 -0
- package/src/client/workspace.module.css +283 -0
- package/src/client-http.ts +45 -0
- package/src/core.ts +47 -0
- package/src/fs.ts +85 -0
- package/src/host-http.ts +74 -0
- package/src/index.ts +244 -0
- package/src/ledger.ts +416 -0
- package/src/protocol.ts +114 -0
- package/src/providers/index.ts +34 -0
- package/src/providers/local/provider.ts +179 -0
- package/src/providers/ssh/provider.ts +274 -0
- package/src/remote/environment.ts +123 -0
- package/src/remote/output.ts +142 -0
- package/src/remote/remote-fs.ts +532 -0
- package/src/remote/remote-process.ts +203 -0
- package/src/remote/remote-subprocess.ts +159 -0
- package/src/remote/remote-terminal.ts +141 -0
- package/src/remote-runner.ts +201 -0
- package/src/remote-search.ts +163 -0
- package/src/routes.ts +395 -0
- package/src/runtime/workspace-core.ts +154 -0
- package/src/seam-state.ts +185 -0
- package/src/shell.ts +10 -0
- package/src/ssh/connection/lease.ts +17 -0
- package/src/ssh/connection/pool.ts +275 -0
- package/src/ssh/engine.ts +1761 -0
- package/src/ssh/exec/output.ts +70 -0
- package/src/ssh/known-hosts.ts +214 -0
- package/src/ssh/plugin.ts +184 -0
- package/src/ssh/protocol.ts +227 -0
- package/src/ssh/routes.ts +892 -0
- package/src/ssh/store.ts +544 -0
- package/src/ssh/tools.ts +402 -0
- package/src/ssh/transfer/progress.ts +75 -0
- package/src/ssh/vault.ts +477 -0
- package/src/subprocess.ts +71 -0
- package/src/switch/switch-fs.ts +253 -0
- package/src/switch/switch-subprocess.ts +59 -0
- package/src/tools.ts +221 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Client } from 'ssh2';
|
|
2
|
+
/** What kind of resource owns a lease (drives diagnostics and teardown). */
|
|
3
|
+
export type LeaseKind = 'operation' | 'stream' | 'session' | 'tunnel';
|
|
4
|
+
/** One unit of ownership over a pooled SSH connection. */
|
|
5
|
+
export interface ClientLease {
|
|
6
|
+
readonly alias: string;
|
|
7
|
+
readonly client: Client;
|
|
8
|
+
readonly generation: number;
|
|
9
|
+
readonly kind: LeaseKind;
|
|
10
|
+
readonly released: boolean;
|
|
11
|
+
/** Mark the underlying connection broken (surfaces to every holder). */
|
|
12
|
+
markBroken(error?: unknown): void;
|
|
13
|
+
/** Idempotent: release this holder's ownership of the connection. */
|
|
14
|
+
release(): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=lease.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Client } from 'ssh2';
|
|
2
|
+
import type { ClientLease, LeaseKind } from './lease.ts';
|
|
3
|
+
export interface AcquireLeaseOptions {
|
|
4
|
+
kind: LeaseKind;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
/** The connection-ownership service (replaces pinned+inFlight bookkeeping). */
|
|
8
|
+
export interface SshConnectionService {
|
|
9
|
+
acquire(alias: string, options: AcquireLeaseOptions): Promise<ClientLease>;
|
|
10
|
+
invalidate(alias: string, options?: {
|
|
11
|
+
includeDependents?: boolean;
|
|
12
|
+
mode?: 'drain' | 'force';
|
|
13
|
+
}): void;
|
|
14
|
+
invalidateAll(): void;
|
|
15
|
+
/** Aliases currently holding a live pooled transport. */
|
|
16
|
+
liveAliases(): string[];
|
|
17
|
+
}
|
|
18
|
+
export interface ConnectionPoolOptions {
|
|
19
|
+
idleTimeoutMs: number;
|
|
20
|
+
/** Open a fresh SSH connection (with jump chain) for one alias. */
|
|
21
|
+
connect(alias: string): Promise<{
|
|
22
|
+
client: Client;
|
|
23
|
+
hops: Client[];
|
|
24
|
+
}>;
|
|
25
|
+
/** Called when a pooled connection is torn down (e.g. drop SFTP cache). */
|
|
26
|
+
onDispose?(client: Client): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-alias connection pool with explicit leases. A connection is swept only
|
|
30
|
+
* when it has zero leases AND it is idle past the threshold (or draining);
|
|
31
|
+
* `invalidate` bumps the generation so a half-open handshake can never
|
|
32
|
+
* re-enter the pool after a config change.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ConnectionPool implements SshConnectionService {
|
|
35
|
+
private readonly records;
|
|
36
|
+
private readonly acquireQueue;
|
|
37
|
+
private readonly generations;
|
|
38
|
+
private readonly options;
|
|
39
|
+
private readonly sweepTimer;
|
|
40
|
+
constructor(options: ConnectionPoolOptions);
|
|
41
|
+
acquire(alias: string, options: AcquireLeaseOptions): Promise<ClientLease>;
|
|
42
|
+
invalidate(alias: string, options?: {
|
|
43
|
+
includeDependents?: boolean;
|
|
44
|
+
mode?: 'drain' | 'force';
|
|
45
|
+
}): void;
|
|
46
|
+
invalidateAll(): void;
|
|
47
|
+
/** Aliases currently holding a live pooled transport. A record that is
|
|
48
|
+
* closed / broken / draining is retired and does not count as connected. */
|
|
49
|
+
liveAliases(): string[];
|
|
50
|
+
private acquireRecord;
|
|
51
|
+
private connectRecord;
|
|
52
|
+
private createLease;
|
|
53
|
+
private sweep;
|
|
54
|
+
private disposeRecord;
|
|
55
|
+
private closeTransport;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1,434 @@
|
|
|
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 ConnectConfig } from 'ssh2';
|
|
8
|
+
import { type SshConnectionService } from './connection/pool.ts';
|
|
9
|
+
import type { ClusterResult, ExecResult, SshHostEntry, SshHostSummary, TestResult, TransferProgress, TunnelInfo } from './protocol.ts';
|
|
10
|
+
import type { HostStoreView } from '../core.ts';
|
|
11
|
+
import { HostKeyPolicy, type HostKeyCheck, type KnownHostsStore } from './known-hosts.ts';
|
|
12
|
+
/** Options used when retiring pooled connections after host configuration changes. */
|
|
13
|
+
export interface SshInvalidateOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Also invalidate hosts whose ProxyJump chain depends, directly or
|
|
16
|
+
* transitively, on the changed alias.
|
|
17
|
+
*/
|
|
18
|
+
includeDependents?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* drain: reject/reconnect subsequent acquisitions, allow existing leases
|
|
21
|
+
* to complete before closing their transport.
|
|
22
|
+
* force: close the current transport immediately.
|
|
23
|
+
*/
|
|
24
|
+
mode?: 'drain' | 'force';
|
|
25
|
+
}
|
|
26
|
+
/** Default engine knobs. */
|
|
27
|
+
export interface EngineOptions {
|
|
28
|
+
/** Connections idle longer than this are closed (ms). */
|
|
29
|
+
idleTimeoutMs?: number;
|
|
30
|
+
/** SSH handshake timeout (ms). */
|
|
31
|
+
connectTimeoutMs?: number;
|
|
32
|
+
/** Keepalive ping interval (ms). */
|
|
33
|
+
keepaliveIntervalMs?: number;
|
|
34
|
+
/** Cap on captured stdout/stderr bytes per exec (ms). */
|
|
35
|
+
maxOutputBytes?: number;
|
|
36
|
+
/** Default exec timeout (ms). */
|
|
37
|
+
defaultExecTimeoutMs?: number;
|
|
38
|
+
/** Default cluster concurrency. */
|
|
39
|
+
defaultMaxWorkers?: number;
|
|
40
|
+
/** SFTP concurrent channel count for transfers. */
|
|
41
|
+
sftpConcurrency?: number;
|
|
42
|
+
/** Optional server-host-key algorithm whitelist (e.g. ['ssh-ed25519']). */
|
|
43
|
+
hostKeyAlgorithms?: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Optional engine dependencies for host-key TOFU and secret resolution.
|
|
47
|
+
* All fields are optional and their absence preserves the pre-security
|
|
48
|
+
* behavior exactly (plaintext inline auth, no host verification) — the
|
|
49
|
+
* existing tests and call sites keep working unchanged.
|
|
50
|
+
*/
|
|
51
|
+
export interface EngineDeps {
|
|
52
|
+
/** Known-hosts trust store; when set, connections require a trusted host key. */
|
|
53
|
+
knownHosts?: KnownHostsStore;
|
|
54
|
+
/** Fingerprint check policy (defaults to a HostKeyPolicy over knownHosts). */
|
|
55
|
+
hostKeyPolicy?: HostKeyPolicy;
|
|
56
|
+
/**
|
|
57
|
+
* Secret resolution for one entry. Absent: read `password`/`passphrase`
|
|
58
|
+
* inline from the entry (plaintext store / test compatibility).
|
|
59
|
+
*/
|
|
60
|
+
resolveSecrets?: (entry: SshHostEntry) => Promise<ResolvedAuthDeps>;
|
|
61
|
+
/** Optional server-host-key algorithm whitelist override. */
|
|
62
|
+
hostKeyAlgorithms?: string[];
|
|
63
|
+
}
|
|
64
|
+
/** The resolved-auth shape passed into connect config building (vault-aware). */
|
|
65
|
+
export interface ResolvedAuthDeps {
|
|
66
|
+
kind: SshHostEntry['auth']['kind'];
|
|
67
|
+
keyPath?: string;
|
|
68
|
+
password?: string;
|
|
69
|
+
passphrase?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Thrown when a connection needs a password/passphrase that is not yet
|
|
73
|
+
* available in this session (secretStorage='none' and the user hasn't entered
|
|
74
|
+
* it yet). The GUI intercepts this and prompts for the credential, then
|
|
75
|
+
* injects it via engine.setSessionPassword and retries.
|
|
76
|
+
*/
|
|
77
|
+
export declare class NeedsPasswordError extends Error {
|
|
78
|
+
/** Which secret the connection needs: 'password' or 'passphrase'. */
|
|
79
|
+
readonly secret: 'password' | 'passphrase';
|
|
80
|
+
constructor(alias: string, secret: 'password' | 'passphrase');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* How much an operation may be retried:
|
|
84
|
+
* - never: one acquisition + one operation attempt.
|
|
85
|
+
* - connect-only: connection acquisition may be retried, but once the
|
|
86
|
+
* operation function starts it is invoked at most once (default).
|
|
87
|
+
* - idempotent: the operation may also be retried until it calls
|
|
88
|
+
* markCommitted() (i.e. while the exec channel is still opening).
|
|
89
|
+
*
|
|
90
|
+
* SFTP operations must never use 'idempotent': they have no commit point, so
|
|
91
|
+
* a replay after a mid-flight timeout would duplicate a remote write.
|
|
92
|
+
*/
|
|
93
|
+
export type RetryPolicy = 'never' | 'connect-only' | 'idempotent';
|
|
94
|
+
/** Options for a one-shot remote command. */
|
|
95
|
+
export interface ExecOptions {
|
|
96
|
+
timeoutMs?: number;
|
|
97
|
+
retry?: RetryPolicy;
|
|
98
|
+
}
|
|
99
|
+
/** A live PTY shell session. */
|
|
100
|
+
export interface ShellSession {
|
|
101
|
+
/** Assign to receive remote output. */
|
|
102
|
+
onData?: (data: Buffer) => void;
|
|
103
|
+
/** Assign to be notified when the channel closes. */
|
|
104
|
+
onExit?: (code: number | null, error?: string) => void;
|
|
105
|
+
/** Write raw input to the shell. */
|
|
106
|
+
send(data: string): void;
|
|
107
|
+
/** Resize the remote PTY. */
|
|
108
|
+
resize(cols: number, rows: number): void;
|
|
109
|
+
/** Send an SSH signal (e.g. 'TERM', 'KILL') to the remote process group. */
|
|
110
|
+
signal(name: string): void;
|
|
111
|
+
/** Close the session and its channel. */
|
|
112
|
+
close(): void;
|
|
113
|
+
/** Pause remote output delivery (transport backpressure). */
|
|
114
|
+
pause(): void;
|
|
115
|
+
/** Resume remote output delivery. */
|
|
116
|
+
resume(): void;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A live streaming exec channel (no PTY): separate stdout/stderr delivery,
|
|
120
|
+
* stdin writes, SSH signals (TERM/KILL), and an explicit end for the final
|
|
121
|
+
* input burst. Used by the subprocess capability seam's remote provider.
|
|
122
|
+
*/
|
|
123
|
+
export interface ExecSession extends ShellSession {
|
|
124
|
+
/** Assign to receive the remote stderr stream. */
|
|
125
|
+
onErrData?: (data: Buffer) => void;
|
|
126
|
+
/** Send an SSH signal (e.g. 'TERM', 'KILL') to the remote process. */
|
|
127
|
+
signal(name: string): void;
|
|
128
|
+
/** Write the final input burst and half-close stdin. */
|
|
129
|
+
end(data?: string): void;
|
|
130
|
+
}
|
|
131
|
+
/** One host-key verification outcome captured during a connect attempt (used
|
|
132
|
+
* to rewrite the generic handshake failure into a typed host-key error). */
|
|
133
|
+
interface HostKeyOutcome {
|
|
134
|
+
alias: string;
|
|
135
|
+
check: HostKeyCheck;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the ssh-agent socket to offer to ssh2 (zero-input key auth,
|
|
139
|
+
* VSCode-style): `$SSH_AUTH_SOCK` — the standard OpenSSH agent socket (also
|
|
140
|
+
* exported by Git for Windows' ssh-agent and WSL). Deliberately NOT probing
|
|
141
|
+
* named pipes (Pageant / Windows OpenSSH agent): an absent pipe makes ssh2's
|
|
142
|
+
* agent query stall the whole handshake until readyTimeout instead of
|
|
143
|
+
* falling through to the next method. Keep Pageant compatibility for a
|
|
144
|
+
* future explicit opt-in. An agent that yields no keys makes ssh2 fall
|
|
145
|
+
* through to the configured methods (privateKey → password), so enabling it
|
|
146
|
+
* when a socket is present is safe. Exported for tests.
|
|
147
|
+
*/
|
|
148
|
+
export declare function sshAgentConfig(): string | undefined;
|
|
149
|
+
/** Build the ssh2 connect config for one entry (key read from disk). The
|
|
150
|
+
* timeout/keepalive knobs come from EngineOptions so they actually take
|
|
151
|
+
* effect instead of being hard-coded. Exported for tests. */
|
|
152
|
+
export declare function buildConnectConfig(entry: SshHostEntry, options: Pick<Required<EngineOptions>, 'connectTimeoutMs' | 'keepaliveIntervalMs'>, sock?: ConnectConfig['sock'], buildContext?: {
|
|
153
|
+
hostKeyPolicy?: HostKeyPolicy;
|
|
154
|
+
hostKeyAlgorithms?: string[];
|
|
155
|
+
/** Writes the verified/refused outcome back to the caller's capture slot. */
|
|
156
|
+
setOutcome?: (value: HostKeyOutcome) => void;
|
|
157
|
+
/** Vault-resolved authentication (overrides entry.auth secrets). */
|
|
158
|
+
authOverride?: ResolvedAuthDeps;
|
|
159
|
+
}): ConnectConfig;
|
|
160
|
+
/**
|
|
161
|
+
* The engine. Owns the pool, tunnels, and all operations. One instance per
|
|
162
|
+
* plugin apply; dispose() closes every connection.
|
|
163
|
+
*/
|
|
164
|
+
export declare class SshEngine {
|
|
165
|
+
private readonly store;
|
|
166
|
+
private readonly opts;
|
|
167
|
+
private readonly tunnels;
|
|
168
|
+
/**
|
|
169
|
+
* One cached SFTP subsystem channel per live client. `Client.sftp()` opens a
|
|
170
|
+
* NEW subsystem channel on every call and OpenSSH caps open sessions per
|
|
171
|
+
* connection (MaxSessions, default 10) —reopening SFTP per operation lets
|
|
172
|
+
* channels pile up on the pooled long-lived connection until listing/reading
|
|
173
|
+
* fails intermittently. Caching one channel per client fixes that; the pool
|
|
174
|
+
* drops the cache via onDispose when a connection is torn down.
|
|
175
|
+
*/
|
|
176
|
+
private readonly sftpChannels;
|
|
177
|
+
private readonly connectionPool;
|
|
178
|
+
private readonly deps;
|
|
179
|
+
private readonly hostKeyPolicy;
|
|
180
|
+
private nextTunnelId;
|
|
181
|
+
/**
|
|
182
|
+
* Session-scoped secrets (secretStorage='none'): keyed by alias, populated
|
|
183
|
+
* by the GUI on first connect, used by connectChain's resolve step, and
|
|
184
|
+
* cleared on dispose. Never persisted.
|
|
185
|
+
*/
|
|
186
|
+
private readonly sessionPasswords;
|
|
187
|
+
/**
|
|
188
|
+
* @param store - the host config store.
|
|
189
|
+
* @param options - engine knobs (defaults applied).
|
|
190
|
+
* @param deps - optional security deps (host-key TOFU, secret resolution).
|
|
191
|
+
* Absent → pre-security behavior (inline auth, no host verification).
|
|
192
|
+
*/
|
|
193
|
+
constructor(store: HostStoreView, options?: EngineOptions, deps?: EngineDeps);
|
|
194
|
+
/**
|
|
195
|
+
* Provide a secret for `alias` for THIS session only (never persisted).
|
|
196
|
+
* Used by the GUI when a connection needs a password/passphrase under
|
|
197
|
+
* secretStorage='none'. Once set, pooled connections reuse it until the
|
|
198
|
+
* session ends or clearSessionSecrets() is called.
|
|
199
|
+
*/
|
|
200
|
+
setSessionPassword(alias: string, secret: {
|
|
201
|
+
password?: string;
|
|
202
|
+
passphrase?: string;
|
|
203
|
+
}): void;
|
|
204
|
+
/** Read the session secret for one alias (undefined = not provided yet). */
|
|
205
|
+
getSessionPassword(alias: string): {
|
|
206
|
+
password?: string;
|
|
207
|
+
passphrase?: string;
|
|
208
|
+
} | undefined;
|
|
209
|
+
/** Drop every session secret (e.g. on secretStorage change / lock). */
|
|
210
|
+
clearSessionSecrets(): void;
|
|
211
|
+
/** Secret-free host list (filtered by the optional query). */
|
|
212
|
+
list(query?: string): SshHostSummary[];
|
|
213
|
+
/** One host summary by alias. */
|
|
214
|
+
find(alias: string): SshHostSummary | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Shared connection/lease service.
|
|
217
|
+
*
|
|
218
|
+
* The returned service is owned by this engine. Consumers may acquire
|
|
219
|
+
* leases or invalidate individual aliases, but must not treat it as a
|
|
220
|
+
* separately owned pool.
|
|
221
|
+
*/
|
|
222
|
+
get connections(): SshConnectionService;
|
|
223
|
+
/** Aliases with a live pooled transport right now (for connection-state
|
|
224
|
+
* indicators — the GUI badge colors bound workspaces by it). */
|
|
225
|
+
connectedAliases(): string[];
|
|
226
|
+
/**
|
|
227
|
+
* Retire the pooled connection for one alias.
|
|
228
|
+
*
|
|
229
|
+
* ConnectionPool knows nothing about HostStore or ProxyJump configuration
|
|
230
|
+
* (every target owns its complete jump chain, no hop records are shared),
|
|
231
|
+
* so dependent-host expansion belongs here: with includeDependents the
|
|
232
|
+
* transitive reverse ProxyJump closure of `alias` is invalidated too.
|
|
233
|
+
*/
|
|
234
|
+
invalidate(alias: string, options?: SshInvalidateOptions): void;
|
|
235
|
+
/**
|
|
236
|
+
* Run `fn` with a live client for `alias`.
|
|
237
|
+
*
|
|
238
|
+
* Acquisition retry and operation replay are deliberately separate:
|
|
239
|
+
* - never: one acquire + one operation attempt.
|
|
240
|
+
* - connect-only: acquire may be retried, but fn is invoked at most once —
|
|
241
|
+
* once fn starts, the remote may already have observed the request, so
|
|
242
|
+
* replay could duplicate non-idempotent work.
|
|
243
|
+
* - idempotent: fn may be retried until it calls control.markCommitted()
|
|
244
|
+
* (exec marks this when the server accepted the channel).
|
|
245
|
+
*
|
|
246
|
+
* A failed operation retires the transport via lease.markBroken(); the
|
|
247
|
+
* lease is always released before the next acquire attempt.
|
|
248
|
+
*/
|
|
249
|
+
private withClient;
|
|
250
|
+
/** Resolve an entry's authentication for one connect: session password
|
|
251
|
+
* table first (secretStorage='none'), then deps.resolveSecrets (vault),
|
|
252
|
+
* then the inline store entry; when a password/passphrase is required but
|
|
253
|
+
* unavailable, throw NeedsPasswordError for the GUI to prompt. */
|
|
254
|
+
private resolveEntryAuth;
|
|
255
|
+
/**
|
|
256
|
+
* Build one full jump chain for an entry: hop clients connected through in
|
|
257
|
+
* order, each forwarding a stream to the next destination, ending with the
|
|
258
|
+
* target client. Shared by the pool and standalone shell sessions.
|
|
259
|
+
*/
|
|
260
|
+
private connectChain;
|
|
261
|
+
/** Run one command on `alias` (reusing the pooled connection). */
|
|
262
|
+
exec(alias: string, command: string, timeoutMs?: number): Promise<ExecResult>;
|
|
263
|
+
exec(alias: string, command: string, options: ExecOptions): Promise<ExecResult>;
|
|
264
|
+
/** Run one command against many hosts concurrently. */
|
|
265
|
+
cluster(options: {
|
|
266
|
+
command: string;
|
|
267
|
+
aliases?: string[];
|
|
268
|
+
environment?: string;
|
|
269
|
+
tags?: string[];
|
|
270
|
+
timeoutMs?: number;
|
|
271
|
+
maxWorkers?: number;
|
|
272
|
+
}): Promise<ClusterResult[]>;
|
|
273
|
+
/**
|
|
274
|
+
* Open one standalone channel on its own connection (never a pooled one).
|
|
275
|
+
* Shared by openShell/openExec (P1-18): alias lookup, jump chain,
|
|
276
|
+
* channel-open timeout, idempotent teardown, and late-callback cleanup live
|
|
277
|
+
* here, so the two public methods keep only their session-specific assembly.
|
|
278
|
+
*/
|
|
279
|
+
private openStandaloneChannel;
|
|
280
|
+
/** Open a PTY shell session for the web terminal (standalone connection). */
|
|
281
|
+
openShell(alias: string, size: {
|
|
282
|
+
cols: number;
|
|
283
|
+
rows: number;
|
|
284
|
+
}): Promise<ShellSession>;
|
|
285
|
+
/**
|
|
286
|
+
* Open a streaming exec channel (no PTY) for the remote subprocess seam.
|
|
287
|
+
* Like the PTY shell, the channel rides its own connection so closing it
|
|
288
|
+
* can never tear down a pooled exec/tunnel sharing the alias.
|
|
289
|
+
*/
|
|
290
|
+
openExec(alias: string, command: string): Promise<ExecSession>;
|
|
291
|
+
/** Upload one local file (or directory tree) to a remote path. */ upload(alias: string, localPath: string, remotePath: string, recursive: boolean, onProgress?: (progress: TransferProgress) => void): Promise<{
|
|
292
|
+
bytes: number;
|
|
293
|
+
files: number;
|
|
294
|
+
}>;
|
|
295
|
+
/** Download one remote file to a local path. */
|
|
296
|
+
download(alias: string, remotePath: string, localPath: string, onProgress?: (progress: TransferProgress) => void): Promise<{
|
|
297
|
+
bytes: number;
|
|
298
|
+
}>;
|
|
299
|
+
/** List a remote directory (file browser). Bounded by a timeout so a
|
|
300
|
+
* stalled SFTP request fails instead of leaving the file tree spinning. */
|
|
301
|
+
ls(alias: string, path: string): Promise<import('./protocol.ts').RemoteDirEntry[]>;
|
|
302
|
+
/**
|
|
303
|
+
* Resolve many remote paths to their canonical form in one SFTP pass
|
|
304
|
+
* (P1-26): one lease + one batch of `sftp.realpath` calls instead of N
|
|
305
|
+
* `realpath` execs. A path that cannot resolve (dangling symlink, vanished
|
|
306
|
+
* entry) fails the whole batch — callers treat an unresolvable listing as
|
|
307
|
+
* an error rather than silently using an uncanonical path.
|
|
308
|
+
*/
|
|
309
|
+
realpaths(alias: string, remotePaths: readonly string[]): Promise<string[]>;
|
|
310
|
+
/**
|
|
311
|
+
* Classify readdir entries, following symlinks so a link to a directory
|
|
312
|
+
* (e.g. AutoDL's /root/autodl-tmp) lists as a directory instead of 'other'.
|
|
313
|
+
* Symlinks are stat'd in PARALLEL batches: serializing them turns a conda /
|
|
314
|
+
* venv bin full of links into N round-trips (seconds to tens of seconds on a
|
|
315
|
+
* slow link) — batching keeps it to a handful of round-trips. The whole pass
|
|
316
|
+
* is bounded by ls()'s timeout.
|
|
317
|
+
*/
|
|
318
|
+
private classifyEntries;
|
|
319
|
+
/** Stat one remote path (file browser / conflict checks). Bounded by a timeout. */
|
|
320
|
+
stat(alias: string, remotePath: string): Promise<{
|
|
321
|
+
type: 'dir' | 'file' | 'other';
|
|
322
|
+
size: number;
|
|
323
|
+
mtimeMs: number;
|
|
324
|
+
mode: number;
|
|
325
|
+
}>;
|
|
326
|
+
/**
|
|
327
|
+
* Lstat one remote path without following the final symlink. Returns
|
|
328
|
+
* undefined when the path is absent (the fs seam's lstat contract).
|
|
329
|
+
*/
|
|
330
|
+
lstat(alias: string, remotePath: string): Promise<{
|
|
331
|
+
type: 'file' | 'directory' | 'symlink' | 'other';
|
|
332
|
+
size: number;
|
|
333
|
+
mtimeMs: number;
|
|
334
|
+
mode: number;
|
|
335
|
+
} | undefined>;
|
|
336
|
+
/**
|
|
337
|
+
* Open a remote file read stream (the fs seam's streamText). The returned
|
|
338
|
+
* stream must be consumed or destroyed; the pooled connection stays busy
|
|
339
|
+
* for the stream's lifetime.
|
|
340
|
+
*/
|
|
341
|
+
/**
|
|
342
|
+
* Open a remote file read stream (the fs seam's streamText). The returned
|
|
343
|
+
* stream must be consumed or destroyed; the pooled connection stays busy
|
|
344
|
+
* for the stream's lifetime (P0-10: a 'stream' lease, released on
|
|
345
|
+
* end/close/error/destroy — not when this function returns).
|
|
346
|
+
*/
|
|
347
|
+
readStream(alias: string, remotePath: string): Promise<import('node:stream').Readable>;
|
|
348
|
+
/**
|
|
349
|
+
* Read one remote file fully into memory (text or binary) with its mtime.
|
|
350
|
+
* The workspace plugin's text gate (UTF-8 + size caps) lives on its caller.
|
|
351
|
+
*/
|
|
352
|
+
readFile(alias: string, remotePath: string): Promise<{
|
|
353
|
+
content: Buffer;
|
|
354
|
+
mtime: number;
|
|
355
|
+
size: number;
|
|
356
|
+
}>;
|
|
357
|
+
/**
|
|
358
|
+
* Write one remote file from memory (parents are created). When
|
|
359
|
+
* `expectedMtime` is given, a stat-then-write conflict check throws before
|
|
360
|
+
* any byte is written (the GUI and the workspace tools use it for
|
|
361
|
+
* overwrite protection).
|
|
362
|
+
*/
|
|
363
|
+
writeFile(alias: string, remotePath: string, content: Buffer, expectedMtime?: number): Promise<{
|
|
364
|
+
mtime: number;
|
|
365
|
+
}>;
|
|
366
|
+
/** Create a remote directory chain (mkdir -p semantics). */
|
|
367
|
+
mkdir(alias: string, remotePath: string): Promise<void>;
|
|
368
|
+
/**
|
|
369
|
+
* Remove a remote file or directory. Directories require `recursive: true`
|
|
370
|
+
* and are walked depth-first (children first, then the directory itself).
|
|
371
|
+
*
|
|
372
|
+
* Deletion never follows symlinks: every node is classified with lstat, so
|
|
373
|
+
* a symlink pointing at a directory is unlinked (only the link), never
|
|
374
|
+
* recursed into — the old stat/readdir-attr check could delete the link
|
|
375
|
+
* target's contents.
|
|
376
|
+
*/
|
|
377
|
+
rm(alias: string, remotePath: string, recursive?: boolean): Promise<void>;
|
|
378
|
+
/** Rename / move a remote path (mv semantics, same filesystem). */
|
|
379
|
+
rename(alias: string, fromPath: string, toPath: string): Promise<void>;
|
|
380
|
+
/** Reject a promise after `ms` (unref'd so it never keeps the process alive).
|
|
381
|
+
* `onTimeout` (when given) runs right before the rejection: ssh2 SFTP
|
|
382
|
+
* requests have no cancel API, so callers that hold an abort handle (e.g.
|
|
383
|
+
* a read stream) destroy it here — otherwise the underlying transfer would
|
|
384
|
+
* keep running (and, for reads, keep buffering) after the caller was told
|
|
385
|
+
* it timed out. */
|
|
386
|
+
private withTimeout;
|
|
387
|
+
/** Stat wrapper (one SFTP stat call). */
|
|
388
|
+
private sftpStat;
|
|
389
|
+
/** Lstat wrapper (does NOT follow symlinks — the deletion safety gate). */
|
|
390
|
+
private sftpLstat;
|
|
391
|
+
/**
|
|
392
|
+
* The (cached) SFTP channel for a pooled client. `Client.sftp()` opens a new
|
|
393
|
+
* subsystem channel per call, so this memoizes one channel per live client;
|
|
394
|
+
* when the channel closes the cache entry is dropped so the next call opens
|
|
395
|
+
* SFTP on the replacement connection. Failed opens are also evicted so a
|
|
396
|
+
* transient channel failure can be retried.
|
|
397
|
+
*/
|
|
398
|
+
private sftpFor;
|
|
399
|
+
/** Create a remote directory chain (stat-then-mkdir per segment). */
|
|
400
|
+
private ensureRemoteDir;
|
|
401
|
+
private fastPut;
|
|
402
|
+
private fastGet;
|
|
403
|
+
/** Remove one tunnel's transport-failure listener (both events). */
|
|
404
|
+
private removeTunnelClientFailureListener;
|
|
405
|
+
/**
|
|
406
|
+
* Mark every tunnel sharing a failed physical SSH client as failed. Failed
|
|
407
|
+
* records stay in this.tunnels so listTunnels() exposes the terminal state
|
|
408
|
+
* and stopTunnel() remains the single place that deletes + releases.
|
|
409
|
+
*/
|
|
410
|
+
private markTunnelsFailedForClient;
|
|
411
|
+
/** Start a local port-forward tunnel (listens on 127.0.0.1 only). */
|
|
412
|
+
startTunnel(alias: string, options: {
|
|
413
|
+
remotePort: number;
|
|
414
|
+
remoteHost?: string;
|
|
415
|
+
localPort?: number;
|
|
416
|
+
}): Promise<TunnelInfo>;
|
|
417
|
+
/** All active tunnels. */
|
|
418
|
+
listTunnels(): TunnelInfo[];
|
|
419
|
+
/** Stop one tunnel (closes listener and live sockets, releases its lease). */
|
|
420
|
+
stopTunnel(id: string): boolean;
|
|
421
|
+
/** Stop all tunnels (optionally for one alias). */
|
|
422
|
+
stopAllTunnels(alias?: string): number;
|
|
423
|
+
/** Probe connectivity: connect, run `true`, close. Typed errors the GUI
|
|
424
|
+
* must react to (host-key TOFU, session password) are NOT flattened into
|
|
425
|
+
* a plain message — callers (routes → panel / workspace gate) key their
|
|
426
|
+
* interactive dialogs on the typed error. Everything else (unreachable,
|
|
427
|
+
* timeout, auth failure) returns a failed result. */
|
|
428
|
+
test(alias: string): Promise<TestResult>;
|
|
429
|
+
/** Close every pooled connection and tunnel, and wipe the in-memory
|
|
430
|
+
* session password table (secrets are never persisted anywhere). */
|
|
431
|
+
dispose(): void;
|
|
432
|
+
}
|
|
433
|
+
export {};
|
|
434
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental UTF-8 output collector with a strict BYTE budget.
|
|
3
|
+
*
|
|
4
|
+
* The previous implementation compared `text.length` (UTF-16 code units)
|
|
5
|
+
* against `chunk.length` (bytes) and decoded each chunk in isolation, so
|
|
6
|
+
* multi-byte characters split across chunks turned into U+FFFD and the
|
|
7
|
+
* budget was never truly enforced. This collector counts raw input bytes,
|
|
8
|
+
* decodes across chunk boundaries with StringDecoder, and truncates on a
|
|
9
|
+
* complete UTF-8 sequence boundary.
|
|
10
|
+
* @module dsh-ssh/exec/output
|
|
11
|
+
*/
|
|
12
|
+
/** Marker appended when the captured output hits the byte budget. */
|
|
13
|
+
export declare const TRUNCATION_MARKER = "\n[output truncated]";
|
|
14
|
+
/** One captured stream (stdout or stderr). */
|
|
15
|
+
export declare class BoundedUtf8Output {
|
|
16
|
+
private readonly maxBytes;
|
|
17
|
+
private readonly decoder;
|
|
18
|
+
private readonly parts;
|
|
19
|
+
private _bytesAccepted;
|
|
20
|
+
private _truncated;
|
|
21
|
+
private _ended;
|
|
22
|
+
constructor(maxBytes: number);
|
|
23
|
+
/** Raw bytes accepted so far (before truncation). */
|
|
24
|
+
get bytesAccepted(): number;
|
|
25
|
+
/** True once the byte budget was hit and output was cut. */
|
|
26
|
+
get truncated(): boolean;
|
|
27
|
+
append(chunk: Buffer): void;
|
|
28
|
+
/** The captured text (truncation marker appended when cut). Idempotent. */
|
|
29
|
+
finish(): string;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH host-key trust store (TOFU): first-connection trust-on-first-use.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, the engine refuses to connect to a host whose public-key
|
|
5
|
+
* fingerprint has not been explicitly trusted: the first encounter records a
|
|
6
|
+
* `pending` entry and the connection is REJECTED (credentials are never sent
|
|
7
|
+
* to an unverified host), the operator confirms the fingerprint through the
|
|
8
|
+
* GUI, and every later connection re-checks the stored fingerprint with a
|
|
9
|
+
* constant-time comparison.
|
|
10
|
+
*
|
|
11
|
+
* The store is deliberately independent of the credentials vault: a
|
|
12
|
+
* fingerprint is not a secret, so verifying the host needs no unlock, and
|
|
13
|
+
* Host-Key protectio n can ship (and be tested) before the vault lands.
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-hardssh/known-hosts
|
|
16
|
+
*/
|
|
17
|
+
/** One known-host record. */
|
|
18
|
+
export interface KnownHostRecord {
|
|
19
|
+
alias: string;
|
|
20
|
+
host: string;
|
|
21
|
+
port: number;
|
|
22
|
+
/** Server public-key algorithm (ssh-ed25519, ssh-rsa, …) for display. */
|
|
23
|
+
keyType: string;
|
|
24
|
+
/** Canonical fingerprint: 'SHA256:' + base64 (no '=' padding). */
|
|
25
|
+
fingerprint: string;
|
|
26
|
+
/** 'pending' = seen once, waiting for explicit trust; 'trusted' = confirmed. */
|
|
27
|
+
status: 'pending' | 'trusted';
|
|
28
|
+
firstSeenAt: number;
|
|
29
|
+
confirmedAt: number | null;
|
|
30
|
+
}
|
|
31
|
+
/** Default file location: <home>/.dsh/ssh-known-hosts.json. */
|
|
32
|
+
export declare function knownHostsPath(): string;
|
|
33
|
+
/** Normalize a fingerprint to the canonical 'SHA256:<b64-no-padding>' form. */
|
|
34
|
+
export declare function normalizeFingerprint(input: string): string;
|
|
35
|
+
/** Constant-time comparison of two fingerprints (canonical form, length-safe). */
|
|
36
|
+
export declare function fingerprintsEqual(a: string, b: string): boolean;
|
|
37
|
+
/** Compute the canonical OpenSSH-style fingerprint of a server key blob. */
|
|
38
|
+
export declare function fingerprintOf(rawKey: Buffer): string;
|
|
39
|
+
/** Host-key check outcome. */
|
|
40
|
+
export type HostKeyCheck = {
|
|
41
|
+
kind: 'trusted';
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'unknown';
|
|
44
|
+
fingerprintSha256: string;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'mismatch';
|
|
47
|
+
expected: string;
|
|
48
|
+
actual: string;
|
|
49
|
+
};
|
|
50
|
+
/** Persisted host-key trust store (atomic write, 0600). */
|
|
51
|
+
export declare class KnownHostsStore {
|
|
52
|
+
private readonly path;
|
|
53
|
+
private records;
|
|
54
|
+
constructor(filePath?: string);
|
|
55
|
+
private load;
|
|
56
|
+
private save;
|
|
57
|
+
lookup(alias: string): KnownHostRecord | undefined;
|
|
58
|
+
/** First encounter: record as pending (non-destructive re-observe keeps status). */
|
|
59
|
+
observe(alias: string, meta: {
|
|
60
|
+
host: string;
|
|
61
|
+
port: number;
|
|
62
|
+
keyType: string;
|
|
63
|
+
fingerprintSha256: string;
|
|
64
|
+
}): void;
|
|
65
|
+
/** pending → trusted (must match the recorded fingerprint). */
|
|
66
|
+
trust(alias: string): void;
|
|
67
|
+
/** Forget a host (key rotation /误信 reset → next connect re-TOFU). */
|
|
68
|
+
forget(alias: string): void;
|
|
69
|
+
list(): KnownHostRecord[];
|
|
70
|
+
}
|
|
71
|
+
/** Per-alias host-key policy: check a server key against the store. */
|
|
72
|
+
export declare class HostKeyPolicy {
|
|
73
|
+
private readonly knownHosts;
|
|
74
|
+
constructor(knownHosts: KnownHostsStore);
|
|
75
|
+
/** Evaluate one server key: trusted / unknown (records pending) / mismatch. */
|
|
76
|
+
check(alias: string, rawKey: Buffer): HostKeyCheck;
|
|
77
|
+
}
|
|
78
|
+
/** Error: the host key is not trusted yet (first encounter, awaiting confirm). */
|
|
79
|
+
export declare class HostKeyUnknownError extends Error {
|
|
80
|
+
readonly fingerprintSha256: string;
|
|
81
|
+
constructor(alias: string, fingerprintSha256: string);
|
|
82
|
+
}
|
|
83
|
+
/** Error: the host key changed since it was trusted (possible MITM / key rotation). */
|
|
84
|
+
export declare class HostKeyMismatchError extends Error {
|
|
85
|
+
readonly expected: string;
|
|
86
|
+
readonly actual: string;
|
|
87
|
+
constructor(alias: string, expected: string, actual: string);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=known-hosts.d.ts.map
|