ai-remote 0.5.1 → 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.
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
  }