ai-remote 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ export type ComputerPoint = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export type ComputerAction = ({
6
+ type: 'click';
7
+ button?: number;
8
+ count?: number;
9
+ intervalMs?: number;
10
+ } & ComputerPoint) | ({
11
+ type: 'move';
12
+ } & ComputerPoint) | ({
13
+ type: 'scroll';
14
+ dx?: number;
15
+ dy?: number;
16
+ } & ComputerPoint) | {
17
+ type: 'type';
18
+ text: string;
19
+ chunkSize?: number;
20
+ intervalMs?: number;
21
+ } | {
22
+ type: 'key';
23
+ chord: string;
24
+ } | {
25
+ type: 'wait';
26
+ ms: number;
27
+ } | {
28
+ type: 'drag';
29
+ path: ComputerPoint[];
30
+ button?: number;
31
+ intervalMs?: number;
32
+ };
33
+ export interface ObservationOptions {
34
+ region?: {
35
+ x: number;
36
+ y: number;
37
+ width: number;
38
+ height: number;
39
+ };
40
+ maxEdge?: number;
41
+ }
42
+ export interface ComputerScreenshot {
43
+ base64: string;
44
+ width: number;
45
+ height: number;
46
+ desktopWidth: number;
47
+ desktopHeight: number;
48
+ viewport?: {
49
+ x: number;
50
+ y: number;
51
+ width: number;
52
+ height: number;
53
+ };
54
+ generation: number;
55
+ changed: boolean;
56
+ quiet: boolean;
57
+ painted: boolean;
58
+ waitedMs: number;
59
+ }
60
+ export interface ComputerBatchResult {
61
+ actions: number;
62
+ clicks: number;
63
+ /** Input dispatch and requested pacing; not proof of application completion. */
64
+ dispatchMs: number;
65
+ }
66
+ /** Map a point in a returned image back to full-desktop coordinates. */
67
+ export declare function desktopPoint(image: ComputerScreenshot, point: ComputerPoint): ComputerPoint;
68
+ export declare class RemoteComputer {
69
+ readonly session: string;
70
+ constructor(session: string);
71
+ private call;
72
+ screenshot(options?: ObservationOptions): Promise<ComputerScreenshot>;
73
+ /** One IPC call, no reconnect or model invocation between actions. */
74
+ act(actions: ComputerAction[], options?: ObservationOptions): Promise<ComputerBatchResult & ComputerScreenshot>;
75
+ /** Explicitly skip observation for a known sequence; inspect when needed. */
76
+ dispatch(actions: ComputerAction[]): Promise<ComputerBatchResult>;
77
+ }
@@ -0,0 +1,3 @@
1
+ import g from"node:net";import{StringDecoder as f}from"node:string_decoder";function w(t,e){let r=new f("utf8"),n="";t.on("data",u=>{for(n+=r.write(u);;){let s=n.indexOf(`
2
+ `);if(s===-1)return;let o=n.slice(0,s);if(n=n.slice(s+1),!!o.trim())try{e(JSON.parse(o))}catch{}}})}var x=(t,e)=>{t.write(`${JSON.stringify(e)}
3
+ `)};function p(t,e,r={},n=12e4){return new Promise((u,s)=>{let o=g.connect(t),a=!1,c=(i,l)=>{a||(a=!0,clearTimeout(b),o.destroy(),i?s(i):u(l))},b=setTimeout(()=>c(new Error(`The session did not answer "${e}" in time.`)),n);o.on("connect",()=>x(o,{id:1,op:e,args:r})),o.on("error",i=>{c(Object.assign(i,{notRunning:i.code==="ENOENT"||i.code==="ECONNREFUSED"}))}),w(o,i=>c(null,i))})}import{homedir as y}from"node:os";import{join as m}from"node:path";var k=m(y(),".ai-remote"),P=m(k,"run");var h=t=>m(P,`${t}.sock`);function T(t,e){if(!Number.isFinite(e.x)||!Number.isFinite(e.y)||e.x<0||e.y<0||e.x>=t.width||e.y>=t.height)throw new Error("Point must be inside the screenshot.");let r=t.viewport??{x:0,y:0,width:t.desktopWidth,height:t.desktopHeight};return{x:Math.min(r.x+r.width-1,Math.floor(r.x+e.x*r.width/t.width)),y:Math.min(r.y+r.height-1,Math.floor(r.y+e.y*r.height/t.height))}}var d=class{session;constructor(e){if(!/^[\w.-]+$/.test(e)||e==="."||e==="..")throw new Error("Use a session name from ai-remote status --json.");this.session=e}async call(e,r){let n=await p(h(this.session),e,r);if(!n.ok)throw new Error(n.error??"Remote operation failed.");return n.result}screenshot(e={}){return this.call("shot",{...e})}act(e,r={}){return this.call("batch",{...r,actions:e})}dispatch(e){return this.call("batch",{actions:e,observe:!1})}};export{d as RemoteComputer,T as desktopPoint};
package/dist/index.d.ts CHANGED
@@ -98,17 +98,72 @@ export declare function connectionId(input: Pick<ConnectionSettings, 'protocol'
98
98
  /** A blank connection, ready for the dialog to fill in. */
99
99
  export declare function emptyConnection(protocol?: RemoteProtocol): ConnectionSettings;
100
100
  export declare function normalizeDisplaySettings(value: unknown): DisplaySettings;
101
+ /**
102
+ * Anything a caller might have.
103
+ *
104
+ * Every field is `unknown` on purpose. This is the function that a saved card,
105
+ * a URL, a host-list entry and a localStorage blob all arrive at, and none of
106
+ * those had their types checked by anyone. Declaring them as already the right
107
+ * shape would not make them so -- it would only move the guesswork from here,
108
+ * where each field is coerced deliberately, to the callers, where it is not.
109
+ */
110
+ export type ConnectionInput = {
111
+ [K in keyof ConnectionSettings]?: unknown;
112
+ } & Record<string, unknown>;
101
113
  /**
102
114
  * Build a complete, valid connection out of whatever a caller happens to have.
103
115
  * Partial input from a saved card, a host list entry, a URL or localStorage all
104
116
  * arrive here, so the dialog never has to reason about missing fields.
105
117
  */
106
- export declare function normalizeConnection(input: Partial<ConnectionSettings> & Record<string, unknown>): ConnectionSettings;
118
+ export declare function normalizeConnection(input: ConnectionInput): ConnectionSettings;
107
119
  /** What a connection is worth remembering by -- never the password. */
108
120
  export type PersistedConnection = Omit<ConnectionSettings, 'password'>;
109
121
  export declare function withoutPassword(settings: ConnectionSettings): PersistedConnection;
110
122
  /** A one-line description for the dialog's summary row and the status bar. */
111
123
  export declare function describeConnection(settings: ConnectionSettings): string;
124
+ /**
125
+ * Carrying VNC credentials on the WebSocket handshake instead of in its URL.
126
+ *
127
+ * VNC is the one protocol whose authentication the gateway performs itself:
128
+ * macOS Screen Sharing needs the Apple Diffie-Hellman (type 30) exchange that
129
+ * noVNC does not implement, so the password has to reach the Worker. Where it
130
+ * travels decides who else gets to read it, and a query string is the one place
131
+ * it must not go -- request URLs are recorded by the platform's own request
132
+ * logging, which no amount of care in this codebase can redact.
133
+ *
134
+ * A browser cannot set headers on a WebSocket, so `Sec-WebSocket-Protocol` is
135
+ * the only field it controls. That is exactly how Kubernetes passes a bearer
136
+ * token to `exec`, and headers are not part of the logged request line.
137
+ *
138
+ * This is confidentiality against logging, not against the gateway: the Worker
139
+ * still sees the password, because Apple DH cannot be computed without it. RDP
140
+ * and SSH authenticate in the browser and never send one here at all.
141
+ */
142
+ /** Subprotocol the byte relay actually speaks, and the one it echoes back. */
143
+ export declare const WS_BINARY_SUBPROTOCOL = "binary";
144
+ export interface VncCredentials {
145
+ username: string;
146
+ password: string;
147
+ }
148
+ /**
149
+ * The subprotocols a VNC WebSocket should offer: the real one first, so a
150
+ * gateway that ignores credentials entirely still negotiates a working session.
151
+ */
152
+ export declare function websocketSubprotocols(credentials: Partial<VncCredentials>): string[];
153
+ /**
154
+ * The credentials an offer list carries, or null when it carries none.
155
+ *
156
+ * A malformed offer is treated as absent rather than as an error: it is
157
+ * attacker-reachable input, and the handshake that follows will fail on its own
158
+ * terms with a message about authentication rather than about base64.
159
+ */
160
+ export declare function readCredentialSubprotocol(header: string | null): VncCredentials | null;
161
+ /**
162
+ * The subprotocol to accept, which is never the credential offer: echoing that
163
+ * back would put the password in the response headers, and in whatever the
164
+ * browser hands to `WebSocket.protocol`.
165
+ */
166
+ export declare function selectSubprotocol(header: string | null): string | null;
112
167
  export interface SavedDevice {
113
168
  id: string;
114
169
  label: string;
@@ -262,3 +317,27 @@ export declare function websocketCloseReason(reason: string): string;
262
317
  export declare function sendableCloseCode(code: number): number;
263
318
  /** Normalize any WebSocket message payload to bytes. */
264
319
  export declare function messageToBytes(data: unknown): Promise<Uint8Array | null>;
320
+ /**
321
+ * The part of a WebSocket the protocol engines actually use.
322
+ *
323
+ * The engines were written against a browser WebSocket pointed at the gateway's
324
+ * relay, and the CLI hands them a TCP socket wearing the same face (see
325
+ * `cli/transport.ts`). Naming that face is what lets both be passed without the
326
+ * engines being told which one they got -- and without the seam being an `any`,
327
+ * which is the same thing said less carefully.
328
+ */
329
+ export interface ByteTransport extends EventTarget {
330
+ readonly readyState: number;
331
+ binaryType: string;
332
+ /** The full range a browser WebSocket takes, so a real one satisfies this. */
333
+ send(data: string | ArrayBufferLike | ArrayBufferView | Blob): void;
334
+ close(code?: number, reason?: string): void;
335
+ }
336
+ /**
337
+ * A `CustomEvent` whose detail this code knows the shape of.
338
+ *
339
+ * The engines communicate by dispatching events, so their consumers spend a lot
340
+ * of time reaching into `event.detail`. Saying which shape is expected at the
341
+ * point of listening is what keeps that from being unchecked.
342
+ */
343
+ export type DetailListener<T> = (event: CustomEvent<T>) => void;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var A=["vnc","rdp"],k=["vnc","rdp","ssh"],P=["auto","nla","tls","rdp"],a={vnc:5900,rdp:3389,ssh:22},f=a.ssh,v=15e3,D=5*6e4;function C(t){return A.includes(t)}function g(t){return P.includes(t)}function h(t,e="vnc"){let n=String(t??"").toLowerCase();return C(n)?n:e}function M(t,e="auto"){let n=String(t??"").toLowerCase();return g(n)?n:e}function u(t,e){let n=typeof t=="number"?t:Number.parseInt(String(t??""),10);return Number.isInteger(n)&&n>0&&n<65536?n:e}var d={quality:6,compression:2,scaling:!0,viewOnly:!1,audio:!1};function E(t){let e=t.remoteApp?.program?.trim()??"",n=`${t.protocol||"vnc"}:${t.host.trim()}:${t.port||""}`;return(e?`${n}:${e}`:n).toLowerCase().slice(0,64)}function V(t="vnc"){let e=a[t];return{id:"",label:"",protocol:t,host:"",port:e,username:"",password:"",domain:"",security:"auto",remoteApp:null,display:{...d},ssh:{enabled:!1,port:a.ssh,username:""},openSshOnConnect:!1}}function x(t,e,n,r){let o=typeof t=="number"?t:Number.parseInt(String(t??""),10);return Number.isInteger(o)?Math.min(r,Math.max(n,o)):e}function _(t){let e=t??{};return{quality:x(e.quality,d.quality,1,9),compression:x(e.compression,d.compression,0,9),scaling:typeof e.scaling=="boolean"?e.scaling:d.scaling,viewOnly:!!e.viewOnly,audio:!!e.audio}}function T(t){if(typeof t=="string"){let i=t.trim();return i?{program:i}:null}if(!t||typeof t!="object")return null;let e=t,n=String(e.program??"").trim();if(!n)return null;let r=String(e.workingDir??"").trim(),o=String(e.arguments??"").trim();return{program:n,...r?{workingDir:r}:{},...o?{arguments:o}:{}}}function z(t){let e=h(t.protocol),n=String(t.host??"").trim(),r=u(t.port,a[e]),o=T(t.remoteApp??t.program),i=t.ssh??{},c={enabled:!!i.enabled,port:u(i.port,a.ssh),username:String(i.username??"").trim()},p={id:"",label:String(t.label??"").trim()||n,protocol:e,host:n,port:r,username:String(t.username??"").trim(),password:String(t.password??""),domain:String(t.domain??"").trim(),security:M(t.security),remoteApp:o,display:_(t.display),ssh:c,openSshOnConnect:!!t.openSshOnConnect};return p.id=String(t.id??"").trim()||E(p),p}function $(t){let{password:e,...n}=t;return n}function U(t){let e=`${t.host||"no address"}:${t.port}`,n=t.remoteApp?` \xB7 ${t.remoteApp.program}`:"";return`${t.protocol.toUpperCase()} \xB7 ${e}${n}`}function L(t){if(typeof t=="string"&&t.trim())return{program:t.trim()};if(!t||typeof t!="object")return;let e=t,n=String(e.program??"").trim();if(n)return{program:n,...e.workingDir?{workingDir:String(e.workingDir)}:{},...e.arguments?{arguments:String(e.arguments)}:{}}}function O(t,e){let n=typeof e=="string"&&e.trim()?e.trim():void 0;if(t===!0)return{port:f,...n?{username:n}:{}};if(typeof t=="number"||typeof t=="string"&&/^\d+$/.test(t.trim())){let i=u(t,0);return i?{port:i,...n?{username:n}:{}}:void 0}if(!t||typeof t!="object")return;let r=t;if(r.enabled===!1)return;let o=typeof r.username=="string"&&r.username.trim()?r.username.trim():n;return{port:u(r.port,f),...o?{username:o}:{}}}function R(t,e,n,r,o){if(!t)return[];let i;try{i=JSON.parse(t)}catch(c){return console.warn(`Failed to parse ${o.toUpperCase()}_HOSTS JSON, ignoring it:`,c),[]}return!Array.isArray(i)||i.length===0?[]:i.map((c,p)=>{let s=c??{},l=h(s.protocol,e),m=l==="rdp"?L(s.remoteApp):void 0,S=O(s.ssh??s.sshPort,s.sshUser??s.sshUsername),y=String(s.host??"")||n,w=String(s.name??"").trim()||y;return{id:String(s.id??"")||`${o}-${p+1}`,name:w,host:y,port:u(s.port??r,a[l]),protocol:l,...s.domain?{domain:String(s.domain)}:{},...g(s.security)?{security:s.security}:{},...m?{remoteApp:m}:{},...S?{ssh:S}:{}}})}function F(t){let e=t.defaultHost||"10.0.0.10",n=t.defaultPort||String(a.vnc),r=[...R(t.vncHostsJson,"vnc",e,n,"vnc-host"),...R(t.rdpHostsJson,"rdp",t.defaultRdpHost||e,t.defaultRdpPort||String(a.rdp),"rdp-host")];return r.length>0?r:[{id:"default-host",name:"Primary Host",host:e,port:u(n,a.vnc),protocol:"vnc"}]}function b(t){let e=String(t).trim().split(".");if(e.length!==4)return null;let n=0;for(let r of e){if(!/^\d{1,3}$/.test(r))return null;let o=Number.parseInt(r,10);if(o>255)return null;n=n<<8|o}return n>>>0}function H(t){if(!t)return[];let e=[];for(let n of t.split(",")){let r=n.trim();if(!r)continue;let o=r.split(".").findIndex(m=>m==="*"),[i,c]=o>=0?[r.replaceAll("*","0"),String(o*8)]:r.split("/"),p=b(i??"");if(p===null)continue;let s=c===void 0?32:Number.parseInt(c,10);if(!Number.isInteger(s)||s<0||s>32)continue;let l=s===0?0:4294967295<<32-s>>>0;e.push({base:(p&l)>>>0,mask:l})}return e}function G(t,e){let n=b(t);return n===null?!1:H(e).some(({base:r,mask:o})=>(n&o)>>>0===r)}var J={monitor:'<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',link:'<path d="M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 1 1 0 10h-2M8 12h8"/>',power:'<path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.8 0"/>',maximize:'<path d="M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3"/>',minimize:'<path d="M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3"/>',keyboard:'<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/>',clipboard:'<rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>',check:'<path d="m5 13 4 4 10-10"/>',camera:'<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/>',eye:'<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z"/><circle cx="12" cy="12" r="3"/>',sliders:'<path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M2 14h4M10 8h4M18 16h4"/>',expand:'<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>',shield:'<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',chevron:'<path d="m6 9 6 6 6-6"/>',back:'<path d="m15 18-6-6 6-6"/>',zap:'<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>',cube:'<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m3.3 7 8.7 5 8.7-5M12 22V12"/>',logout:'<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/>',clock:'<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',activity:'<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',transfer:'<path d="m3 16 4 4 4-4M7 20V4M21 8l-4-4-4 4M17 4v16"/>',screen:'<rect x="2" y="5" width="20" height="13" rx="2"/><path d="M7 21h10"/>',command:'<path d="M15 6a3 3 0 1 1 3 3h-3zm0 0v12m0 0a3 3 0 1 0 3-3h-3zM9 6a3 3 0 1 0-3 3h3zm0 0v12m0 0a3 3 0 1 1-3-3h3z"/>',apps:'<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',terminal:'<path d="m4 17 6-6-6-6M12 19h8"/>',folder:'<path d="M3 7h6l2 2h10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V6a2 2 0 0 1 2-2h4l2 3"/>',close:'<path d="M18 6 6 18M6 6l12 12"/>',sparkles:'<path d="m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3z"/><path d="m19 15 .8 2.2 2.2.8-2.2.8L19 21l-.8-2.2-2.2-.8 2.2-.8z"/>',broom:'<path d="M10 10V4a2 2 0 0 1 4 0v6"/><path d="M6 10h12a2 2 0 0 1 2 2v2H4v-2a2 2 0 0 1 2-2Z"/><path d="m5 14-2 7h18l-2-7M9 18l-1 3M15 18l1 3"/>',panel:'<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>',menu:'<path d="M4 7h16M4 12h16M4 17h16"/>',plus:'<path d="M12 5v14M5 12h14"/>',star:'<path d="m12 3 2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.8 6.2 20.9l1.1-6.5L2.6 9.8l6.5-.9L12 3z"/>',trash:'<path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>'},j="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f38020' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='3' width='20' height='14' rx='2'/%3E%3Cpath d='M8 21h8M12 17v4'/%3E%3C/svg%3E";var K="VNC_GATEWAY_READY_v1",X="VNC_GATEWAY_KEEPALIVE_v1",Z=25e3,Q=123;function tt(t){return!t.established||!t.close?!1:t.close.code===1006?!0:t.close.code===1011&&/tcp|socket|network|host closed/i.test(t.close.reason)}function et(t){let e=new TextEncoder;if(e.encode(t).byteLength<=123)return t;let n="";for(let r of t){if(e.encode(`${n}${r}\u2026`).byteLength>123)break;n+=r}return`${n}\u2026`}function nt(t){return t===1005||t===1006||t===1015?1011:t}async function rt(t){return typeof t=="string"?new TextEncoder().encode(t):t instanceof ArrayBuffer?new Uint8Array(t):t instanceof Blob?new Uint8Array(await t.arrayBuffer()):ArrayBuffer.isView(t)?new Uint8Array(t.buffer,t.byteOffset,t.byteLength):null}export{D as COMMAND_IDLE_MS,v as COMMAND_START_MS,d as DEFAULT_DISPLAY_SETTINGS,a as DEFAULT_PORTS,f as DEFAULT_SSH_PORT,j as FAVICON_DATA_URI,X as GATEWAY_KEEPALIVE_SIGNAL,K as GATEWAY_READY_SIGNAL,J as ICON_PATHS,Z as KEEPALIVE_INTERVAL_MS,Q as MAX_CLOSE_REASON_BYTES,P as RDP_SECURITY_MODES,A as REMOTE_PROTOCOLS,k as TRANSPORT_PROTOCOLS,E as connectionId,U as describeConnection,V as emptyConnection,G as hostInAllowedSubnets,b as ipv4ToInt,g as isRdpSecurity,C as isRemoteProtocol,tt as isRetryableDisconnect,rt as messageToBytes,z as normalizeConnection,_ as normalizeDisplaySettings,H as parseAllowedSubnets,F as parseConfiguredHosts,nt as sendableCloseCode,u as toPort,M as toRdpSecurity,h as toRemoteProtocol,et as websocketCloseReason,$ as withoutPassword};
1
+ var _=["vnc","rdp"],V=["vnc","rdp","ssh"],P=["auto","nla","tls","rdp"],a={vnc:5900,rdp:3389,ssh:22},g=a.ssh,z=15e3,U=5*6e4;function T(t){return _.includes(t)}function y(t){return P.includes(t)}function d(t,e="vnc"){let n=String(t??"").toLowerCase();return T(n)?n:e}function b(t,e="auto"){let n=String(t??"").toLowerCase();return y(n)?n:e}function u(t,e){let n=typeof t=="number"?t:Number.parseInt(String(t??""),10);return Number.isInteger(n)&&n>0&&n<65536?n:e}var h={quality:6,compression:2,scaling:!0,viewOnly:!1,audio:!1};function O(t){let e=t.remoteApp?.program?.trim()??"",n=`${t.protocol||"vnc"}:${t.host.trim()}:${t.port||""}`;return(e?`${n}:${e}`:n).toLowerCase().slice(0,64)}function Y(t="vnc"){let e=a[t];return{id:"",label:"",protocol:t,host:"",port:e,username:"",password:"",domain:"",security:"auto",remoteApp:null,display:{...h},ssh:{enabled:!1,port:a.ssh,username:""},openSshOnConnect:!1}}function M(t,e,n,r){let o=typeof t=="number"?t:Number.parseInt(String(t??""),10);return Number.isInteger(o)?Math.min(r,Math.max(n,o)):e}function L(t){let e=t??{};return{quality:M(e.quality,h.quality,1,9),compression:M(e.compression,h.compression,0,9),scaling:typeof e.scaling=="boolean"?e.scaling:h.scaling,viewOnly:!!e.viewOnly,audio:!!e.audio}}function k(t){if(typeof t=="string"){let i=t.trim();return i?{program:i}:null}if(!t||typeof t!="object")return null;let e=t,n=String(e.program??"").trim();if(!n)return null;let r=String(e.workingDir??"").trim(),o=String(e.arguments??"").trim();return{program:n,...r?{workingDir:r}:{},...o?{arguments:o}:{}}}function F(t){let e=d(t.protocol),n=String(t.host??"").trim(),r=u(t.port,a[e]),o=k(t.remoteApp??t.program),i=t.ssh??{},c={enabled:!!i.enabled,port:u(i.port,a.ssh),username:(i.username??"").trim()},p={id:"",label:String(t.label??"").trim()||n,protocol:e,host:n,port:r,username:String(t.username??"").trim(),password:String(t.password??""),domain:String(t.domain??"").trim(),security:b(t.security),remoteApp:o,display:L(t.display),ssh:c,openSshOnConnect:!!t.openSshOnConnect};return p.id=String(t.id??"").trim()||O(p),p}function G(t){let{password:e,...n}=t;return n}function J(t){let e=`${t.host||"no address"}:${t.port}`,n=t.remoteApp?` \xB7 ${t.remoteApp.program}`:"";return`${t.protocol.toUpperCase()} \xB7 ${e}${n}`}var w="binary",f="airemote-cred.";function H(t){let e=new TextEncoder().encode(t),n="";for(let r of e)n+=String.fromCharCode(r);return btoa(n).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")}function v(t){let e=t.replaceAll("-","+").replaceAll("_","/").padEnd(Math.ceil(t.length/4)*4,"="),n=atob(e),r=new Uint8Array(n.length);for(let o=0;o<n.length;o++)r[o]=n.charCodeAt(o);return new TextDecoder().decode(r)}function j(t){let e=t.username||"",n=t.password||"";return!e&&!n?[w]:[w,f+H(JSON.stringify({u:e,p:n}))]}function R(t){return t?t.split(",").map(e=>e.trim()).filter(Boolean):[]}function q(t){for(let e of R(t))if(e.startsWith(f))try{let n=JSON.parse(v(e.slice(f.length)));if(!n||typeof n!="object")return null;let{u:r,p:o}=n;return{username:typeof r=="string"?r:"",password:typeof o=="string"?o:""}}catch{return null}return null}function K(t){return R(t).find(e=>!e.startsWith(f))??null}function I(t){if(typeof t=="string"&&t.trim())return{program:t.trim()};if(!t||typeof t!="object")return;let e=t,n=String(e.program??"").trim();if(n)return{program:n,...e.workingDir?{workingDir:String(e.workingDir)}:{},...e.arguments?{arguments:String(e.arguments)}:{}}}function D(t,e){let n=typeof e=="string"&&e.trim()?e.trim():void 0;if(t===!0)return{port:g,...n?{username:n}:{}};if(typeof t=="number"||typeof t=="string"&&/^\d+$/.test(t.trim())){let i=u(t,0);return i?{port:i,...n?{username:n}:{}}:void 0}if(!t||typeof t!="object")return;let r=t;if(r.enabled===!1)return;let o=typeof r.username=="string"&&r.username.trim()?r.username.trim():n;return{port:u(r.port,g),...o?{username:o}:{}}}function A(t,e,n,r,o){if(!t)return[];let i;try{i=JSON.parse(t)}catch(c){return console.warn(`Failed to parse ${o.toUpperCase()}_HOSTS JSON, ignoring it:`,c),[]}return!Array.isArray(i)||i.length===0?[]:i.map((c,p)=>{let s=c??{},l=d(s.protocol,e),m=l==="rdp"?I(s.remoteApp):void 0,S=D(s.ssh??s.sshPort,s.sshUser??s.sshUsername),x=String(s.host??"")||n,E=String(s.name??"").trim()||x;return{id:String(s.id??"")||`${o}-${p+1}`,name:E,host:x,port:u(s.port??r,a[l]),protocol:l,...s.domain?{domain:String(s.domain)}:{},...y(s.security)?{security:s.security}:{},...m?{remoteApp:m}:{},...S?{ssh:S}:{}}})}function Q(t){let e=t.defaultHost||"10.0.0.10",n=t.defaultPort||String(a.vnc),r=[...A(t.vncHostsJson,"vnc",e,n,"vnc-host"),...A(t.rdpHostsJson,"rdp",t.defaultRdpHost||e,t.defaultRdpPort||String(a.rdp),"rdp-host")];return r.length>0?r:[{id:"default-host",name:"Primary Host",host:e,port:u(n,a.vnc),protocol:"vnc"}]}function C(t){let e=t.trim().split(".");if(e.length!==4)return null;let n=0;for(let r of e){if(!/^\d{1,3}$/.test(r))return null;let o=Number.parseInt(r,10);if(o>255)return null;n=n<<8|o}return n>>>0}function N(t){if(!t)return[];let e=[];for(let n of t.split(",")){let r=n.trim();if(!r)continue;let o=r.split(".").findIndex(m=>m==="*"),[i,c]=o>=0?[r.replaceAll("*","0"),String(o*8)]:r.split("/"),p=C(i??"");if(p===null)continue;let s=c===void 0?32:Number.parseInt(c,10);if(!Number.isInteger(s)||s<0||s>32)continue;let l=s===0?0:4294967295<<32-s>>>0;e.push({base:(p&l)>>>0,mask:l})}return e}function tt(t,e){let n=C(t);return n===null?!1:N(e).some(({base:r,mask:o})=>(n&o)>>>0===r)}var nt={monitor:'<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',link:'<path d="M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 1 1 0 10h-2M8 12h8"/>',power:'<path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.8 0"/>',maximize:'<path d="M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3"/>',minimize:'<path d="M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3"/>',keyboard:'<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"/>',clipboard:'<rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>',check:'<path d="m5 13 4 4 10-10"/>',camera:'<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/>',eye:'<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z"/><circle cx="12" cy="12" r="3"/>',sliders:'<path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M2 14h4M10 8h4M18 16h4"/>',expand:'<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>',shield:'<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',chevron:'<path d="m6 9 6 6 6-6"/>',back:'<path d="m15 18-6-6 6-6"/>',zap:'<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>',cube:'<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m3.3 7 8.7 5 8.7-5M12 22V12"/>',logout:'<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/>',clock:'<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',activity:'<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',transfer:'<path d="m3 16 4 4 4-4M7 20V4M21 8l-4-4-4 4M17 4v16"/>',screen:'<rect x="2" y="5" width="20" height="13" rx="2"/><path d="M7 21h10"/>',command:'<path d="M15 6a3 3 0 1 1 3 3h-3zm0 0v12m0 0a3 3 0 1 0 3-3h-3zM9 6a3 3 0 1 0-3 3h3zm0 0v12m0 0a3 3 0 1 1-3-3h3z"/>',apps:'<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',terminal:'<path d="m4 17 6-6-6-6M12 19h8"/>',folder:'<path d="M3 7h6l2 2h10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V6a2 2 0 0 1 2-2h4l2 3"/>',close:'<path d="M18 6 6 18M6 6l12 12"/>',sparkles:'<path d="m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3z"/><path d="m19 15 .8 2.2 2.2.8-2.2.8L19 21l-.8-2.2-2.2-.8 2.2-.8z"/>',broom:'<path d="M10 10V4a2 2 0 0 1 4 0v6"/><path d="M6 10h12a2 2 0 0 1 2 2v2H4v-2a2 2 0 0 1 2-2Z"/><path d="m5 14-2 7h18l-2-7M9 18l-1 3M15 18l1 3"/>',panel:'<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>',menu:'<path d="M4 7h16M4 12h16M4 17h16"/>',plus:'<path d="M12 5v14M5 12h14"/>',star:'<path d="m12 3 2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.8 6.2 20.9l1.1-6.5L2.6 9.8l6.5-.9L12 3z"/>',trash:'<path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>'},rt="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f38020' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='3' width='20' height='14' rx='2'/%3E%3Cpath d='M8 21h8M12 17v4'/%3E%3C/svg%3E";var st="VNC_GATEWAY_READY_v1",it="VNC_GATEWAY_KEEPALIVE_v1",at=25e3,ct=123;function pt(t){return!t.established||!t.close?!1:t.close.code===1006?!0:t.close.code===1011&&/tcp|socket|network|host closed/i.test(t.close.reason)}function ut(t){let e=new TextEncoder;if(e.encode(t).byteLength<=123)return t;let n="";for(let r of t){if(e.encode(`${n}${r}\u2026`).byteLength>123)break;n+=r}return`${n}\u2026`}function lt(t){return t===1005||t===1006||t===1015?1011:t}async function mt(t){return typeof t=="string"?new TextEncoder().encode(t):t instanceof ArrayBuffer?new Uint8Array(t):t instanceof Blob?new Uint8Array(await t.arrayBuffer()):ArrayBuffer.isView(t)?new Uint8Array(t.buffer,t.byteOffset,t.byteLength):null}export{U as COMMAND_IDLE_MS,z as COMMAND_START_MS,h as DEFAULT_DISPLAY_SETTINGS,a as DEFAULT_PORTS,g as DEFAULT_SSH_PORT,rt as FAVICON_DATA_URI,it as GATEWAY_KEEPALIVE_SIGNAL,st as GATEWAY_READY_SIGNAL,nt as ICON_PATHS,at as KEEPALIVE_INTERVAL_MS,ct as MAX_CLOSE_REASON_BYTES,P as RDP_SECURITY_MODES,_ as REMOTE_PROTOCOLS,V as TRANSPORT_PROTOCOLS,w as WS_BINARY_SUBPROTOCOL,O as connectionId,J as describeConnection,Y as emptyConnection,tt as hostInAllowedSubnets,C as ipv4ToInt,y as isRdpSecurity,T as isRemoteProtocol,pt as isRetryableDisconnect,mt as messageToBytes,F as normalizeConnection,L as normalizeDisplaySettings,N as parseAllowedSubnets,Q as parseConfiguredHosts,q as readCredentialSubprotocol,K as selectSubprotocol,lt as sendableCloseCode,u as toPort,b as toRdpSecurity,d as toRemoteProtocol,ut as websocketCloseReason,j as websocketSubprotocols,G as withoutPassword};
@@ -1,3 +1,27 @@
1
+ /**
2
+ * The part of a WebSocket the protocol engines actually use.
3
+ *
4
+ * The engines were written against a browser WebSocket pointed at the gateway's
5
+ * relay, and the CLI hands them a TCP socket wearing the same face (see
6
+ * `cli/transport.ts`). Naming that face is what lets both be passed without the
7
+ * engines being told which one they got -- and without the seam being an `any`,
8
+ * which is the same thing said less carefully.
9
+ */
10
+ interface ByteTransport extends EventTarget {
11
+ readonly readyState: number;
12
+ binaryType: string;
13
+ /** The full range a browser WebSocket takes, so a real one satisfies this. */
14
+ send(data: string | ArrayBufferLike | ArrayBufferView | Blob): void;
15
+ close(code?: number, reason?: string): void;
16
+ }
17
+ /**
18
+ * A `CustomEvent` whose detail this code knows the shape of.
19
+ *
20
+ * The engines communicate by dispatching events, so their consumers spend a lot
21
+ * of time reaching into `event.detail`. Saying which shape is expected at the
22
+ * point of listening is what keeps that from being unchecked.
23
+ */
24
+ type DetailListener<T> = (event: CustomEvent<T>) => void;
1
25
  /**
2
26
  * The vocabulary both halves of the gateway share.
3
27
  *
@@ -110,12 +134,24 @@ declare function connectionId(input: Pick<ConnectionSettings, 'protocol' | 'host
110
134
  /** A blank connection, ready for the dialog to fill in. */
111
135
  declare function emptyConnection(protocol?: RemoteProtocol): ConnectionSettings;
112
136
  declare function normalizeDisplaySettings(value: unknown): DisplaySettings;
137
+ /**
138
+ * Anything a caller might have.
139
+ *
140
+ * Every field is `unknown` on purpose. This is the function that a saved card,
141
+ * a URL, a host-list entry and a localStorage blob all arrive at, and none of
142
+ * those had their types checked by anyone. Declaring them as already the right
143
+ * shape would not make them so -- it would only move the guesswork from here,
144
+ * where each field is coerced deliberately, to the callers, where it is not.
145
+ */
146
+ type ConnectionInput = {
147
+ [K in keyof ConnectionSettings]?: unknown;
148
+ } & Record<string, unknown>;
113
149
  /**
114
150
  * Build a complete, valid connection out of whatever a caller happens to have.
115
151
  * Partial input from a saved card, a host list entry, a URL or localStorage all
116
152
  * arrive here, so the dialog never has to reason about missing fields.
117
153
  */
118
- declare function normalizeConnection(input: Partial<ConnectionSettings> & Record<string, unknown>): ConnectionSettings;
154
+ declare function normalizeConnection(input: ConnectionInput): ConnectionSettings;
119
155
  /** What a connection is worth remembering by -- never the password. */
120
156
  type PersistedConnection = Omit<ConnectionSettings, 'password'>;
121
157
  declare function withoutPassword(settings: ConnectionSettings): PersistedConnection;
@@ -139,6 +175,11 @@ interface ScrollDelta {
139
175
  x: number;
140
176
  y: number;
141
177
  }
178
+ /**
179
+ * What the metrics rail instruments. A real WebSocket in the browser; in the
180
+ * CLI, a shim with the same surface over a TCP socket straight at the host.
181
+ */
182
+ type InstrumentableSocket = ByteTransport;
142
183
  /**
143
184
  * A live session. Both drivers expose this, so the AI pane's screen tool and
144
185
  * the toolbar never have to ask which protocol they are driving.
@@ -157,7 +198,7 @@ export interface RemoteSession extends EventTarget {
157
198
  /** RFB compression level. RDP ignores it. */
158
199
  compressionLevel?: number;
159
200
  /** The transport the metrics rail instruments. Can appear a tick after construction. */
160
- readonly socket?: WebSocket | null;
201
+ readonly socket?: InstrumentableSocket | null;
161
202
  readonly screenGeometry: ScreenGeometry;
162
203
  movePointer(x: number, y: number): boolean;
163
204
  clickPointer(x: number, y: number, button?: PointerButton): boolean;
@@ -245,6 +286,45 @@ export interface ProtocolDriver {
245
286
  /** Protocol-specific wording for a close, or null to fall back to the shared text. */
246
287
  describeDisconnect(context: DisconnectContext): string | null;
247
288
  }
289
+ /**
290
+ * Signing in with a key instead of a password.
291
+ *
292
+ * An identity is a public key blob, the algorithm name that goes with it, and
293
+ * something that can sign. What does the signing is deliberately behind a
294
+ * function: a key read off disk signs with Web Crypto, and a key held by
295
+ * ssh-agent signs by asking the agent -- the session cannot tell the two apart
296
+ * and does not need to.
297
+ *
298
+ * The parser here reads the format `ssh-keygen` writes by default, which no
299
+ * runtime can parse on its own: OpenSSH's own container rather than PKCS#8.
300
+ * Only unencrypted keys are read. A key with a passphrase would need bcrypt
301
+ * and a prompt, and the answer to a passphrase is ssh-agent, which is already
302
+ * holding the decrypted key.
303
+ */
304
+ /** One key that can sign for a user, however it happens to be held. */
305
+ interface SshIdentity {
306
+ /** The public key type, e.g. `ssh-ed25519`. */
307
+ readonly keyType: string;
308
+ /** The public key, in the wire format the host expects. */
309
+ readonly keyBlob: Uint8Array;
310
+ /** Where it came from, for logs and error messages. */
311
+ readonly comment: string;
312
+ /**
313
+ * Signature algorithms to offer, best first. RSA keys sign under several,
314
+ * and a host will say which of them it is willing to take.
315
+ */
316
+ readonly algorithms: readonly string[];
317
+ /** The full signature blob (algorithm name and signature) over `data`. */
318
+ sign(data: Uint8Array, algorithm: string): Promise<Uint8Array>;
319
+ }
320
+ /**
321
+ * Read a private key file.
322
+ *
323
+ * Returns one identity, or throws with the reason a human can act on -- which
324
+ * for the common failure is "this key has a passphrase, so let ssh-agent hold
325
+ * it" rather than a decoder error.
326
+ */
327
+ declare function parsePrivateKey(text: string, comment?: string): Promise<SshIdentity>;
248
328
  export declare const DEFAULT_PORT = 22;
249
329
  export declare const COMMAND_MAX_MS: number;
250
330
  /**
@@ -253,9 +333,151 @@ export declare const COMMAND_MAX_MS: number;
253
333
  * Each shell gets its own, because none of the three agree on how to print a
254
334
  * string, run a command and read back its exit status.
255
335
  */
256
- export declare function frameCommand(family: any, command: string, id: any): string;
336
+ export declare function frameCommand(family: string, command: string, id: string): string;
337
+ /** What runCommand resolves with. */
338
+ export interface CommandResult {
339
+ output: string;
340
+ exitStatus: number | null;
341
+ exitSignal?: string;
342
+ timedOut?: boolean;
343
+ started?: boolean;
344
+ reason?: string;
345
+ durationMs: number;
346
+ }
347
+ /** A tool command's output framing, hidden from the screen while it runs. */
348
+ interface DisplayFilter {
349
+ begin: string;
350
+ end: string;
351
+ stage: 'before' | 'inside' | 'closing';
352
+ pending: string;
353
+ }
354
+ /** What a caller can ask of an SSH session. */
355
+ export interface SshSessionOptions {
356
+ username?: string;
357
+ password?: string;
358
+ identities?: SshIdentity[] | (() => Promise<SshIdentity[]>);
359
+ preferredShell?: 'powershell';
360
+ /** Run a subsystem (e.g. 'sftp') rather than a shell. */
361
+ subsystem?: string;
362
+ columns?: number;
363
+ rows?: number;
364
+ hostLabel?: string;
365
+ log?: (step: string, detail?: unknown) => void;
366
+ verifyHost?: (info: {
367
+ fingerprint: string;
368
+ keyType: string;
369
+ algorithm: string;
370
+ }) => Promise<boolean> | boolean;
371
+ openTransport?: ((url: string) => ByteTransport) | null;
372
+ requestInput?: (prompt: {
373
+ prompt?: string;
374
+ echo?: boolean;
375
+ }) => Promise<string>;
376
+ }
377
+ /** What each SshSession event carries in its `detail`. */
378
+ export interface SshSessionEventDetail {
379
+ data: {
380
+ bytes: Uint8Array;
381
+ display?: Uint8Array;
382
+ isStderr: boolean;
383
+ };
384
+ ready: {
385
+ fingerprint?: string;
386
+ shellFamily: string;
387
+ };
388
+ status: {
389
+ phase: string;
390
+ message: string;
391
+ };
392
+ banner: {
393
+ text: string;
394
+ };
395
+ secure: {
396
+ fingerprint: string;
397
+ kexAlgorithm: string;
398
+ hostKeyAlgorithm: string;
399
+ cipher: string;
400
+ firstTime: boolean;
401
+ };
402
+ error: {
403
+ message: string;
404
+ };
405
+ close: {
406
+ message: string;
407
+ exitStatus?: number | null;
408
+ clean?: boolean;
409
+ code?: number;
410
+ };
411
+ command: {
412
+ phase: 'start' | 'end';
413
+ command: string;
414
+ exitStatus?: number | null;
415
+ timedOut?: boolean;
416
+ started?: boolean;
417
+ /** Why it was given up on: never acknowledged, silent, or simply too long. */
418
+ reason?: string;
419
+ durationMs?: number;
420
+ };
421
+ }
257
422
  export declare class SshSession extends EventTarget {
258
423
  #private;
424
+ /**
425
+ * Listen for one of this session's own events, typed by what it carries.
426
+ * The shapes are enumerated in `SshSessionEventDetail` just above.
427
+ */
428
+ addEventListener: {
429
+ <K extends keyof SshSessionEventDetail>(type: K, listener: (event: CustomEvent<SshSessionEventDetail[K]>) => void, options?: boolean | AddEventListenerOptions): void;
430
+ (type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
431
+ };
432
+ private readonly options;
433
+ private readonly username;
434
+ private password;
435
+ /**
436
+ * Keys to sign in with, if any. A function rather than a list is allowed
437
+ * because reading keys off a disk -- or asking an agent for them -- is work
438
+ * a session that never needs them should not do.
439
+ */
440
+ /** Replaced with the resolved list the first time it is read. */
441
+ private identities;
442
+ private readonly requestInput;
443
+ /** Whether there is anybody to ask; see the constructor. */
444
+ private readonly canAsk;
445
+ private columns;
446
+ private rows;
447
+ /** A subsystem to run instead of a shell, e.g. 'sftp'. */
448
+ private readonly subsystem;
449
+ private channelId;
450
+ private remoteChannelId;
451
+ private remoteWindow;
452
+ private remoteMaxPacket;
453
+ private localWindow;
454
+ private pendingWrites;
455
+ /** True while the interactive shell channel is usable. */
456
+ shellOpen: boolean;
457
+ private commandInFlight;
458
+ /** Set while a tool command runs, to keep its framing off the screen. */
459
+ displayFilter: DisplayFilter | null;
460
+ /** True only after the requested interactive shell is ready for commands. */
461
+ /** True once the interactive shell is actually usable, not merely authenticated. */
462
+ readySignaled: boolean;
463
+ private powerShellAttempted;
464
+ private powerShellTimer;
465
+ /** '', 'posix', 'cmd' or 'powershell'; learned from the prompt. */
466
+ /** 'powershell' when the host answered with one; '' until the shell is known. */
467
+ shellFamily: string;
468
+ private promptTail;
469
+ private exitStatus;
470
+ private exitSignal?;
471
+ private authMethods;
472
+ /** Which keys were offered and refused, for the message if nothing works. */
473
+ private triedKeys;
474
+ /** Set once the host has said no to the stored password. */
475
+ private passwordRejected;
476
+ private closed;
477
+ private waiters;
478
+ private readonly transport;
479
+ private fingerprint?;
480
+ private lastMessage?;
259
481
  /**
260
482
  * @param {string} url gateway WebSocket URL
261
483
  * @param {object} options
@@ -268,11 +490,11 @@ export declare class SshSession extends EventTarget {
268
490
  * @param {(info: object) => Promise<boolean>} [options.verifyHost]
269
491
  * @param {(prompt: object) => Promise<string>} [options.requestInput]
270
492
  */
271
- constructor(url: string, options?: {});
493
+ constructor(url: string, options?: SshSessionOptions);
272
494
  connect(): void;
273
495
  disconnect(): void;
274
496
  /** Send keystrokes, or anything else the terminal produces, to the shell. */
275
- write(data: Uint8Array): void;
497
+ write(data: Uint8Array | string): void;
276
498
  /**
277
499
  * Run one command in this shell and return what it printed.
278
500
  *
@@ -295,16 +517,24 @@ export declare class SshSession extends EventTarget {
295
517
  idleMs?: number;
296
518
  maxMs?: number;
297
519
  startMs?: number;
298
- }): Promise<unknown>;
520
+ }): Promise<CommandResult>;
299
521
  /** Tell the shell its window changed, so full-screen programs redraw. */
300
522
  resize(columns: number, rows: number): void;
301
523
  }
524
+ import { Terminal } from '@xterm/xterm';
302
525
  export declare class SshTerminal {
303
- constructor(container: any);
304
- get columns(): any;
305
- get rows(): any;
306
- onData(handler: any): void;
307
- onResize(handler: any): void;
526
+ private readonly container;
527
+ readonly terminal: Terminal;
528
+ private readonly fitAddon;
529
+ private readonly observer;
530
+ /** Where keystrokes go; readLine swaps it out while it owns the line. */
531
+ private dataHandler;
532
+ private fitting;
533
+ constructor(container: HTMLElement);
534
+ get columns(): number;
535
+ get rows(): number;
536
+ onData(handler: ((data: string) => void) | null): void;
537
+ onResize(handler: (cols: number, rows: number) => void): void;
308
538
  write(data: Uint8Array): void;
309
539
  /** Gateway-side notices, kept visually apart from what the host prints. */
310
540
  notice(text: string, tone?: string): void;
@@ -329,20 +559,20 @@ export declare class SshTerminal {
329
559
  fit(): void;
330
560
  /** The grid the host is being told about, for a status line or a log. */
331
561
  get geometry(): {
332
- columns: any;
333
- rows: any;
334
- fontSize: any;
562
+ columns: number;
563
+ rows: number;
564
+ fontSize: number;
335
565
  };
336
- get selection(): any;
566
+ get selection(): string;
337
567
  /**
338
568
  * Read one line from the user, for a password or a one-time code the host
339
569
  * asked for during authentication. The shell is not open yet, so the
340
570
  * terminal is free to be a prompt.
341
571
  */
342
572
  readLine({ prompt, echo }?: {
343
- echo?: boolean;
344
573
  prompt?: string;
345
- }): Promise<unknown>;
574
+ echo?: boolean;
575
+ }): Promise<string>;
346
576
  destroy(): void;
347
577
  }
348
578
  export declare const DRIVERS: Record<RemoteProtocol, ProtocolDriver>;