ai-remote 0.5.1 → 0.6.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.
@@ -0,0 +1,176 @@
1
+ export type ComputerPoint = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ /** The wire's 0/1/2, or the name for it. */
6
+ export type ComputerButton = 0 | 1 | 2 | 'left' | 'middle' | 'right' | 'l' | 'm' | 'r';
7
+ /** A modifier chord held for the length of one action, e.g. `ShiftLeft`. */
8
+ type Modified = {
9
+ key?: string;
10
+ };
11
+ export type ComputerAction = ({
12
+ type: 'click';
13
+ button?: ComputerButton;
14
+ /** ChatGPT's spelling of `button`. */
15
+ mouse_button?: ComputerButton;
16
+ count?: number;
17
+ /** ChatGPT's spelling of `count`. */
18
+ click_count?: number;
19
+ intervalMs?: number;
20
+ /** Milliseconds to hold the button down for each click. */
21
+ duration?: number;
22
+ } & ComputerPoint & Modified) | ({
23
+ type: 'move';
24
+ } & ComputerPoint & Modified) | ({
25
+ type: 'scroll';
26
+ /** Wheel notches, positive right and down. */
27
+ dx?: number;
28
+ dy?: number;
29
+ /** ChatGPT's spelling: a direction and a distance in pixels. */
30
+ direction?: 'up' | 'down' | 'left' | 'right' | 'u' | 'd' | 'l' | 'r';
31
+ pixels?: number;
32
+ } & ComputerPoint & Modified) | {
33
+ type: 'type';
34
+ text: string;
35
+ chunkSize?: number;
36
+ intervalMs?: number;
37
+ } | {
38
+ type: 'key';
39
+ chord?: string;
40
+ /** ChatGPT's spelling of `chord`. */
41
+ key?: string;
42
+ /** Milliseconds to hold the chord before releasing it. */
43
+ duration?: number;
44
+ } | {
45
+ type: 'wait';
46
+ ms: number;
47
+ } | ({
48
+ type: 'drag';
49
+ path: ComputerPoint[];
50
+ button?: ComputerButton;
51
+ intervalMs?: number;
52
+ } & Modified)
53
+ /**
54
+ * A drag held open across calls, which is ChatGPT's `drag_handle()`.
55
+ *
56
+ * `drag` is one gesture with a known shape. These three are for the drag
57
+ * whose ending depends on what the drag itself reveals: press, take a
58
+ * screenshot, decide, and only then release. The button stays down on the
59
+ * host between calls, so something has to end it -- the session lets go if it
60
+ * closes first, but nothing else will.
61
+ */
62
+ | ({
63
+ type: 'drag-start';
64
+ button?: ComputerButton;
65
+ } & ComputerPoint) | ({
66
+ type: 'drag-move';
67
+ } & ComputerPoint) | {
68
+ type: 'drag-end';
69
+ };
70
+ export interface BatchOptions {
71
+ /**
72
+ * Wait for the drawn cursor to reach each target before the input goes out.
73
+ *
74
+ * Only means anything while a viewer is attached; there is no cursor to watch
75
+ * otherwise and nothing to wait for. It makes a batch slower on purpose, in
76
+ * exchange for a click that a person watching sees land.
77
+ */
78
+ animate?: boolean;
79
+ /**
80
+ * A floor between consecutive actions, ChatGPT's `post_action_sleep_ms`.
81
+ *
82
+ * Theirs is 100ms and always on. This defaults to none: a batch exists to
83
+ * spend one model turn on many inputs, and `dispatchMs` is a published
84
+ * measurement. Ask for it when the far side needs the breathing room.
85
+ */
86
+ postActionSleepMs?: number;
87
+ /**
88
+ * Take the keyboard and pointer from the viewer for the length of this batch.
89
+ *
90
+ * A remote desktop has one pointer. An agent does not get its own -- neither
91
+ * protocol has a second one to give it -- so a hand resting on a trackpad is
92
+ * moving the same cursor the agent is aiming, and a click that lands where
93
+ * the hand left it is worse than one that does not land at all. This holds
94
+ * the input for the batch and gives it back afterwards.
95
+ *
96
+ * Not a lock. The control button in the window takes it back at once, and
97
+ * `interference` in the next result says that it happened.
98
+ */
99
+ exclusive?: boolean;
100
+ }
101
+ export interface ObservationOptions {
102
+ region?: {
103
+ x: number;
104
+ y: number;
105
+ width: number;
106
+ height: number;
107
+ };
108
+ maxEdge?: number;
109
+ }
110
+ export interface ComputerScreenshot {
111
+ base64: string;
112
+ width: number;
113
+ height: number;
114
+ desktopWidth: number;
115
+ desktopHeight: number;
116
+ viewport?: {
117
+ x: number;
118
+ y: number;
119
+ width: number;
120
+ height: number;
121
+ };
122
+ generation: number;
123
+ changed: boolean;
124
+ quiet: boolean;
125
+ painted: boolean;
126
+ waitedMs: number;
127
+ }
128
+ export interface ComputerBatchResult {
129
+ actions: number;
130
+ clicks: number;
131
+ /** Input dispatch and requested pacing; not proof of application completion. */
132
+ dispatchMs: number;
133
+ /** A `drag-start` is still holding a button down on the host. */
134
+ dragging: boolean;
135
+ /**
136
+ * Input events that arrived from a viewer while this batch ran.
137
+ *
138
+ * Zero is the ordinary answer. Anything else is somebody using the machine at
139
+ * the same time, and their pointer is the same pointer: treat the actions in
140
+ * this batch as having possibly landed somewhere else, look before carrying
141
+ * on, and consider `exclusive` or `takeInput()` if it keeps happening.
142
+ * Refused input counts, so this stays a useful signal after the input has
143
+ * been taken away.
144
+ */
145
+ interference: number;
146
+ /** Whether the people watching may drive right now. */
147
+ humanInput: boolean;
148
+ }
149
+ /** Map a point in a returned image back to full-desktop coordinates. */
150
+ export declare function desktopPoint(image: ComputerScreenshot, point: ComputerPoint): ComputerPoint;
151
+ export declare class RemoteComputer {
152
+ readonly session: string;
153
+ constructor(session: string);
154
+ private call;
155
+ screenshot(options?: ObservationOptions): Promise<ComputerScreenshot>;
156
+ /** One IPC call, no reconnect or model invocation between actions. */
157
+ act(actions: ComputerAction[], options?: ObservationOptions & BatchOptions): Promise<ComputerBatchResult & ComputerScreenshot>;
158
+ /**
159
+ * Take the keyboard and pointer from every window, until given back.
160
+ *
161
+ * For an agent that has seen `interference` and decided to work alone.
162
+ * `releaseInput()` undoes it, and so does the control button in the window --
163
+ * which is the point: the person at the machine is never locked out of it.
164
+ */
165
+ takeInput(): Promise<{
166
+ humanInput: boolean;
167
+ viewers: number;
168
+ }>;
169
+ /** Give the keyboard and pointer back to whoever is watching. */
170
+ releaseInput(): Promise<{
171
+ humanInput: boolean;
172
+ viewers: number;
173
+ }>;
174
+ /** Explicitly skip observation for a known sequence; inspect when needed. */
175
+ dispatch(actions: ComputerAction[], options?: BatchOptions): Promise<ComputerBatchResult>;
176
+ }
@@ -0,0 +1,3 @@
1
+ import g from"node:net";import{StringDecoder as f}from"node:string_decoder";function w(r,e){let t=new f("utf8"),n="";r.on("data",u=>{for(n+=t.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=(r,e)=>{r.write(`${JSON.stringify(e)}
3
+ `)};function p(r,e,t={},n=12e4){return new Promise((u,s)=>{let o=g.connect(r),a=!1,m=(i,b)=>{a||(a=!0,clearTimeout(l),o.destroy(),i?s(i):u(b))},l=setTimeout(()=>m(new Error(`The session did not answer "${e}" in time.`)),n);o.on("connect",()=>x(o,{id:1,op:e,args:t})),o.on("error",i=>{m(Object.assign(i,{notRunning:i.code==="ENOENT"||i.code==="ECONNREFUSED"}))}),w(o,i=>m(null,i))})}import{homedir as y}from"node:os";import{join as c}from"node:path";var k=c(y(),".ai-remote"),C=c(k,"run");var h=r=>c(C,`${r}.sock`);function N(r,e){if(!Number.isFinite(e.x)||!Number.isFinite(e.y)||e.x<0||e.y<0||e.x>=r.width||e.y>=r.height)throw new Error("Point must be inside the screenshot.");let t=r.viewport??{x:0,y:0,width:r.desktopWidth,height:r.desktopHeight};return{x:Math.min(t.x+t.width-1,Math.floor(t.x+e.x*t.width/r.width)),y:Math.min(t.y+t.height-1,Math.floor(t.y+e.y*t.height/r.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,t){let n=await p(h(this.session),e,t);if(!n.ok)throw new Error(n.error??"Remote operation failed.");return n.result}screenshot(e={}){return this.call("shot",{...e})}act(e,t={}){return this.call("batch",{...t,actions:e})}takeInput(){return this.call("input",{on:!1})}releaseInput(){return this.call("input",{on:!0})}dispatch(e,t={}){return this.call("batch",{...t,actions:e,observe:!1})}};export{d as RemoteComputer,N 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;
@@ -143,10 +179,7 @@ interface ScrollDelta {
143
179
  * What the metrics rail instruments. A real WebSocket in the browser; in the
144
180
  * CLI, a shim with the same surface over a TCP socket straight at the host.
145
181
  */
146
- interface InstrumentableSocket {
147
- readonly readyState: number;
148
- addEventListener(type: string, listener: (event: any) => void): void;
149
- }
182
+ type InstrumentableSocket = ByteTransport;
150
183
  /**
151
184
  * A live session. Both drivers expose this, so the AI pane's screen tool and
152
185
  * the toolbar never have to ask which protocol they are driving.
@@ -330,12 +363,72 @@ export interface SshSessionOptions {
330
363
  rows?: number;
331
364
  hostLabel?: string;
332
365
  log?: (step: string, detail?: unknown) => void;
333
- verifyHost?: (info: any) => Promise<boolean> | boolean;
334
- openTransport?: ((url: string) => any) | null;
335
- requestInput?: (prompt: any) => Promise<string>;
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
+ };
336
421
  }
337
422
  export declare class SshSession extends EventTarget {
338
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
+ };
339
432
  private readonly options;
340
433
  private readonly username;
341
434
  private password;
@@ -359,16 +452,19 @@ export declare class SshSession extends EventTarget {
359
452
  private remoteMaxPacket;
360
453
  private localWindow;
361
454
  private pendingWrites;
362
- private shellOpen;
455
+ /** True while the interactive shell channel is usable. */
456
+ shellOpen: boolean;
363
457
  private commandInFlight;
364
458
  /** Set while a tool command runs, to keep its framing off the screen. */
365
459
  displayFilter: DisplayFilter | null;
366
460
  /** True only after the requested interactive shell is ready for commands. */
367
- private readySignaled;
461
+ /** True once the interactive shell is actually usable, not merely authenticated. */
462
+ readySignaled: boolean;
368
463
  private powerShellAttempted;
369
464
  private powerShellTimer;
370
465
  /** '', 'posix', 'cmd' or 'powershell'; learned from the prompt. */
371
- private shellFamily;
466
+ /** 'powershell' when the host answered with one; '' until the shell is known. */
467
+ shellFamily: string;
372
468
  private promptTail;
373
469
  private exitStatus;
374
470
  private exitSignal?;
@@ -421,7 +517,7 @@ export declare class SshSession extends EventTarget {
421
517
  idleMs?: number;
422
518
  maxMs?: number;
423
519
  startMs?: number;
424
- }): Promise<unknown>;
520
+ }): Promise<CommandResult>;
425
521
  /** Tell the shell its window changed, so full-screen programs redraw. */
426
522
  resize(columns: number, rows: number): void;
427
523
  }