ai-remote 0.3.0 → 0.4.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,140 @@
1
+ /**
2
+ * A native window for the session — no browser, no bundled engine.
3
+ *
4
+ * The window and the webview inside it come from the operating system: WebView2
5
+ * on Windows, WebKit on macOS, WebKitGTK on Linux, reached through tao/wry. So
6
+ * the session gets a real application window with the platform's own title bar,
7
+ * its own dock or taskbar entry, and the green button on macOS meaning
8
+ * fullscreen — for about 4 MB rather than the 150 MB a second browser costs.
9
+ *
10
+ * It runs in a process of its own rather than inside the session, for the same
11
+ * reason the viewer is an attachment: a window has an event loop that wants the
12
+ * main thread, and closing one must not take a session with it.
13
+ */
14
+
15
+ import { spawn } from 'node:child_process';
16
+ import { createRequire } from 'node:module';
17
+
18
+ /** Whether the native window layer is installed for this platform. */
19
+ export function nativeWindowAvailable(): boolean {
20
+ try {
21
+ createRequire(import.meta.url).resolve('@webviewjs/webview');
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ export interface WindowOptions {
29
+ url: string;
30
+ title: string;
31
+ /** The remote desktop's size; the window is sized to show it at 100%. */
32
+ width: number;
33
+ height: number;
34
+ fullscreen?: boolean;
35
+ /** Open in the default browser instead of a window of its own. */
36
+ tab?: boolean;
37
+ }
38
+
39
+ export interface WindowHandle {
40
+ kind: 'window' | 'tab' | 'none';
41
+ pid?: number;
42
+ detail: string;
43
+ }
44
+
45
+ /**
46
+ * Open the viewer, and say what actually happened rather than throwing. A
47
+ * window that will not open is a worse session, not a failed one, and the URL
48
+ * has been printed either way.
49
+ */
50
+ export function openWindow(options: WindowOptions): WindowHandle {
51
+ if (!options.tab && nativeWindowAvailable()) {
52
+ const args = [
53
+ process.argv[1],
54
+ '__window',
55
+ options.url,
56
+ '--title', options.title,
57
+ '--width', String(options.width),
58
+ '--height', String(options.height),
59
+ ];
60
+ if (options.fullscreen) args.push('--fullscreen');
61
+
62
+ try {
63
+ const child = spawn(process.execPath, args, { stdio: 'ignore', detached: true });
64
+ child.unref();
65
+ return { kind: 'window', pid: child.pid, detail: 'native window' };
66
+ } catch (error) {
67
+ return { kind: 'none', detail: error instanceof Error ? error.message : String(error) };
68
+ }
69
+ }
70
+
71
+ const command = process.platform === 'darwin' ? 'open'
72
+ : process.platform === 'win32' ? 'cmd'
73
+ : 'xdg-open';
74
+ const args = process.platform === 'win32' ? ['/c', 'start', '', options.url] : [options.url];
75
+
76
+ try {
77
+ spawn(command, args, { stdio: 'ignore', detached: true }).unref();
78
+ return {
79
+ kind: 'tab',
80
+ detail: options.tab ? 'asked for a browser tab' : 'the native window layer is not installed',
81
+ };
82
+ } catch (error) {
83
+ return { kind: 'none', detail: error instanceof Error ? error.message : String(error) };
84
+ }
85
+ }
86
+
87
+ /** Run the window. This is the `__window` process, and it does not return. */
88
+ export async function runWindow(argv: string[]): Promise<void> {
89
+ const positional: string[] = [];
90
+ const flags: Record<string, string | boolean> = {};
91
+ for (let i = 0; i < argv.length; i++) {
92
+ const token = argv[i];
93
+ if (!token.startsWith('--')) { positional.push(token); continue; }
94
+ const name = token.slice(2);
95
+ if (name === 'fullscreen') flags[name] = true;
96
+ else flags[name] = argv[++i] ?? '';
97
+ }
98
+
99
+ const url = positional[0];
100
+ if (!url) throw new Error('__window needs a URL');
101
+
102
+ const { Application } = await import('@webviewjs/webview');
103
+ const application = new Application();
104
+
105
+ // `logical: true` matters: without it these are device pixels, so a window
106
+ // asked for at 1280 opens at 640 on any Retina display.
107
+ const window = application.createBrowserWindow({
108
+ title: String(flags.title || 'ai-remote'),
109
+ width: Number(flags.width) || 1280,
110
+ height: Number(flags.height) || 800,
111
+ logical: true,
112
+ resizable: true,
113
+ maximizable: true,
114
+ minimizable: true,
115
+ focused: true,
116
+
117
+ // One bar, not two. The page draws the title and the controls, and the
118
+ // system titlebar becomes a transparent strip that the content runs
119
+ // underneath -- so the traffic lights sit on the page's own toolbar.
120
+ //
121
+ // Transparent rather than hidden on purpose: this binding exposes no way to
122
+ // start a window drag from the page, so removing the titlebar outright
123
+ // would leave a window nothing could move.
124
+ macosTitlebarTransparent: true,
125
+ macosFullsizeContentView: true,
126
+ macosTitleHidden: true,
127
+ // The page handles clicks in the bar, so this is what leaves the window
128
+ // draggable by the parts of it that are not a button.
129
+ macosMovableByWindowBackground: true,
130
+ });
131
+
132
+ window.createWebview({ url, enableDevtools: false });
133
+
134
+ if (flags.fullscreen) {
135
+ // After the window has a screen to fill.
136
+ setTimeout(() => { try { window.setFullscreen(); } catch { /* not fatal */ } }, 400);
137
+ }
138
+
139
+ await application.whenReady();
140
+ }