electrobun 1.17.3-beta.9 → 1.18.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/README.md CHANGED
@@ -14,16 +14,27 @@
14
14
  ## What is Electrobun?
15
15
 
16
16
  Electrobun aims to be a complete **solution-in-a-box** for building, updating, and shipping ultra fast, tiny, and cross-platform desktop applications written in Typescript.
17
- Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in <a href="https://ziglang.org/">zig</a>.
17
+ Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in Objc, C++, and several core parts written in <a href="https://ziglang.org/">zig</a>.
18
18
 
19
- Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrobun/</a> to see api documentation, guides, and more.
19
+ Visit <a href="https://docs.electrobunny.ai/electrobun/">https://docs.electrobunny.ai/electrobun/</a> to see api documentation, guides, and more.
20
+
21
+ You use it via npm.
22
+
23
+ Don't miss our:
24
+ - self-extracting bundles that use ZSTD compression for more compact distributables as small as 16MB
25
+ - zig optimized BSDIFF implementation that lets you ship tiny app updates as small as 4KB
26
+ - `bundleCEF` flag to bundle and pin Chromium for those that want that tradeoff of consistency over file size
27
+ - `bundleWGPU` that lets you use Bun Typescript -> WGPU to control a native GPU surface without a webview
28
+ - Our Three.js and Babylon.js adapters that work right in Bun
29
+ - Our `<electrobun-webview>` and `<electrobun-wpgu>` html elements that let you composit proper OOPIFs and native GPU surfaces into your UIs
30
+ - so much more.
20
31
 
21
32
  **Project Goals**
22
33
 
23
34
  - Write typescript for the main process and webviews without having to think about it.
24
35
  - Isolation between main and webview processes with fast, typed, easy to implement RPC between them.
25
- - Small self-extracting app bundles ~12MB (when using system webview, most of this is the bun runtime)
26
- - Even smaller app updates as small as 14KB (using bsdiff it only downloads tiny patches between versions)
36
+ - Small self-extracting app bundles ~14MB (when using system webview, most of this is the bun runtime)
37
+ - Even smaller app updates as small as 4KB (using bsdiff it only downloads tiny patches between versions)
27
38
  - Provide everything you need in one tightly integrated workflow to start writing code in 5 minutes and distribute in 10.
28
39
 
29
40
  ## Apps Built with Electrobun
@@ -42,8 +53,10 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
42
53
  - [Codex Agents Composer](https://github.com/MrLesk/codex-agents-composer) - desktop app for managing your Codex agents and their skills
43
54
  - [codex-devtools](https://github.com/gulivan/codex-devtools) - desktop inspector for Codex session data; browse conversations, search messages, and analyze agent activity
44
55
  - [Deskdown](https://github.com/guarana-studio/deskdown) - transform any web address into a desktop app in under 20 seconds
56
+ - [Dictate](https://github.com/siddhantparadox/dictate) - Windows dictation app with local and BYOK cloud transcription
45
57
  - [dev-3.0](https://github.com/h0x91b/dev-3.0) - helps you not get lost while managing multiple AI agents across projects
46
58
  - [DOOM](https://github.com/blackboardsh/electrobun-doom) - DOOM implemented in 2 ways: bun -> (c doom -> bundled wgpu) and (full ts port bun -> bundled wgpu)
59
+ - [dotlock](https://github.com/tsconfigdotjson/dotlock) - macOS desktop app for managing `.env` files across your projects
47
60
  - [electrobun-pdf](https://github.com/GijungKim/electrobun-pdf) - local-first PDF & DOCX editor for opening, annotating, and exporting documents without leaving your machine
48
61
  - [electrobun-rms](https://github.com/khanhthanhdev/electrobun-rms) - fast Electrobun desktop app template with React, Tailwind CSS, and Vite
49
62
  - [golb](https://github.com/chrisdadev13/golb) - desktop AI coding workspace built with React, Vite, and Tailwind
@@ -55,6 +68,7 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
55
68
  - [md-browse](https://github.com/needle-tools/md-browse) - a markdown-first browser that converts web pages to clean markdown
56
69
  - [Patchline](https://github.com/adwaithks/Patchline) - lightweight desktop Git client for reading patches and line diffs, then staging and committing changes
57
70
  - [peekachu](https://github.com/needle-tools/peekachu) - password manager for AIs; store secrets in your OS keychain and scrub output so AI assistants never see actual values
71
+ - [PiBun](https://github.com/khairold/pibun) - desktop GUI for the Pi coding agent with chat, terminal, git integration, and plugin system
58
72
  - [PLEXI](https://github.com/ianjamesburke/PLEXI) - a multi-dimensional terminal multiplexer for the agentic era
59
73
  - [Prometheus](https://github.com/opensourcectl/prometheus) - desktop utility toolbox for file cleanup, document manipulation, and image processing
60
74
  - [Quiver](https://ataraxy-labs.github.io/quiver/) - desktop app for GitHub PR reviews, merge conflict resolution, and AI commit messages
@@ -67,6 +81,7 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
67
81
  - [VibesOS](https://github.com/popmechanic/VibesOS) - A GUI for Claude Code that makes it easy to vibe code simple, un-hackable apps
68
82
  - [VoiceVault](https://github.com/PJH720/VoiceVault) - AI-powered voice recorder with transcription, summarization, and RAG search
69
83
  - [warren](https://github.com/Loa212/warren) - open-source, peer-to-peer terminal mesh for accessing your machines from any device without SSH keys or config files
84
+ - [whatsapp-reminder](https://github.com/FatahChan/whatsapp-reminder) - managed scheduled WhatsApp messages
70
85
 
71
86
  ### Video Demos
72
87
 
@@ -81,8 +96,11 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
81
96
  [![Star History Chart](https://api.star-history.com/svg?repos=blackboardsh/electrobun&type=date&legend=top-left&cache=3)](https://www.star-history.com/#blackboardsh/electrobun&type=date&legend=top-left)
82
97
 
83
98
  ## Contributing
99
+ Electrobun is one piece of a vision I'm building. I'm optimizing for focus and execution. Issues and PRs can be used to share ideas, but there should be no expectation that I will review, respond to, or merge them.
100
+
84
101
  Ways to get involved:
85
102
 
103
+ - Read the [Contribution guidelines](./CONTRIBUTING.md)
86
104
  - Follow us on X for updates <a href="https://twitter.com/BlackboardTech">@BlackboardTech</a> and <a href="https://twitter.com/YoavCodes">@YoavCodes</a> or on bluesky <a href="https://bsky.app/profile/yoav.codes">@yoav.codes</a>
87
105
  - Join the conversation on <a href="https://discord.gg/ueKE4tjaCE">Discord</a>
88
106
  - Create and participate in Github issues and discussions
@@ -155,3 +173,4 @@ All commands are run from the `/package` directory:
155
173
  | Windows 11+ | Official |
156
174
  | Ubuntu 22.04+ | Official |
157
175
  | Other Linux distros (gtk3, webkit2gtk-4.1) | Community |
176
+ | Raspberry Pi | Unofficial fork: [kortexa-ai/electrobun (linux-wpe)](https://github.com/kortexa-ai/electrobun/tree/kortexa/linux-wpe) — follow the author [@francip](https://x.com/francip/status/2050149256053539059?s=20) |
@@ -32,6 +32,16 @@ const platform = getPlatform();
32
32
  const arch = platform === 'win' ? 'x64' : getArch();
33
33
  const binExt = platform === 'win' ? '.exe' : '';
34
34
 
35
+ function getTarCommand() {
36
+ if (platform !== 'win') {
37
+ return 'tar';
38
+ }
39
+
40
+ // Git Bash tar can treat C:\... as a remote path. Force the built-in Windows tar.
41
+ const systemTar = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe');
42
+ return existsSync(systemTar) ? `"${systemTar}"` : 'tar';
43
+ }
44
+
35
45
  // Paths
36
46
  const electrobunDir = join(__dirname, '..');
37
47
  const cacheDir = join(electrobunDir, '.cache');
@@ -98,7 +108,7 @@ async function ensureCliBinary() {
98
108
  await downloadFile(tarballUrl, tarballPath);
99
109
 
100
110
  // Extract using system tar (available on macOS, Linux, and Windows 10+)
101
- execSync(`tar -xzf "${tarballPath}"`, { cwd: cacheDir, stdio: 'pipe' });
111
+ execSync(`${getTarCommand()} -xzf "${tarballPath}"`, { cwd: cacheDir, stdio: 'pipe' });
102
112
 
103
113
  // Clean up tarball
104
114
  unlinkSync(tarballPath);
@@ -56,6 +56,39 @@ export interface ElectrobunConfig {
56
56
  * ```
57
57
  */
58
58
  urlSchemes?: string[];
59
+
60
+ /**
61
+ * File type associations for the application.
62
+ * Registers document types so the OS can open files with your app
63
+ * (e.g., double-click in Finder, "Open With" menu, drag-to-dock).
64
+ *
65
+ * Platform support:
66
+ * - macOS: Fully supported. Generates CFBundleDocumentTypes in Info.plist.
67
+ * - Windows/Linux: Not yet supported.
68
+ *
69
+ * Files arrive as file:// URLs via the existing "open-url" event:
70
+ * ```typescript
71
+ * Electrobun.events.on("open-url", (e) => {
72
+ * if (e.data.url.startsWith("file://")) {
73
+ * console.log("Opened file:", e.data.url);
74
+ * }
75
+ * });
76
+ * ```
77
+ */
78
+ fileAssociations?: Array<{
79
+ /** File extensions without the leading dot (e.g., ["dotlock", "json"]) */
80
+ ext: string[];
81
+ /** Human-readable name for this file type (e.g., "DotLock Document") */
82
+ name: string;
83
+ /** The app's role for this file type. @default "Viewer" */
84
+ role?: "Editor" | "Viewer" | "Shell" | "None";
85
+ /**
86
+ * Path to an .icns file for this document type (macOS only).
87
+ * The file is automatically copied into the app bundle's Resources folder
88
+ * during the build. Only the filename (without path) is written to Info.plist.
89
+ */
90
+ icon?: string;
91
+ }>;
59
92
  };
60
93
 
61
94
  /**
@@ -278,7 +311,15 @@ export interface ElectrobunConfig {
278
311
  entitlements?: Record<string, boolean | string | string[]>;
279
312
 
280
313
  /**
281
- * Path to .iconset folder containing app icons
314
+ * Path to .iconset folder or .icon file (from Icon Composer)
315
+ * containing app icons.
316
+ *
317
+ * - `.iconset` folders are converted to .icns via iconutil
318
+ * (requires Command Line Tools)
319
+ * - `.icon` files are compiled via actool, producing Assets.car
320
+ * for Liquid Glass on macOS 26+ and a .icns fallback for older
321
+ * macOS versions (requires Xcode)
322
+ *
282
323
  * @default "icon.iconset"
283
324
  */
284
325
  icons?: string;
@@ -0,0 +1,105 @@
1
+ // Contract tests for the bun:ffi APIs that electrobun depends on.
2
+ //
3
+ // Bun bumps don't usually break electrobun, but when they do, the breakage
4
+ // almost always lives in this surface — JSCallback marshaling, FFIType
5
+ // encoding, dlopen behavior. These tests are a tripwire: if a new Bun release
6
+ // breaks any of them, we want to know before cutting an electrobun release,
7
+ // not after a user reports a crash.
8
+ //
9
+ // Skipped on Windows for now since the bun-check workflow runs on Linux and
10
+ // the system library paths differ. If we add a Windows runner later, switch
11
+ // the libc path resolution to include msvcrt/ucrtbase.
12
+
13
+ import { describe, expect, it } from "bun:test";
14
+ import {
15
+ CString,
16
+ FFIType,
17
+ JSCallback,
18
+ dlopen,
19
+ ptr,
20
+ toArrayBuffer,
21
+ type Pointer,
22
+ } from "bun:ffi";
23
+
24
+ const isUnix =
25
+ process.platform === "darwin" || process.platform === "linux";
26
+
27
+ const libcPath =
28
+ process.platform === "darwin" ? "libSystem.B.dylib" : "libc.so.6";
29
+
30
+ (isUnix ? describe : describe.skip)(
31
+ "bun:ffi contract used by electrobun",
32
+ () => {
33
+ it("dlopen + FFIType.cstring + FFIType.u64 (strlen)", () => {
34
+ const lib = dlopen(libcPath, {
35
+ strlen: {
36
+ args: [FFIType.cstring],
37
+ returns: FFIType.u64,
38
+ },
39
+ });
40
+
41
+ const len = lib.symbols.strlen(
42
+ new TextEncoder().encode("hello\0"),
43
+ );
44
+ expect(Number(len)).toBe(5);
45
+
46
+ lib.close();
47
+ });
48
+
49
+ it("ptr + toArrayBuffer round-trip", () => {
50
+ const src = new Uint8Array([1, 2, 3, 4, 5]);
51
+ const back = new Uint8Array(
52
+ toArrayBuffer(ptr(src), 0, src.byteLength),
53
+ );
54
+ expect(Array.from(back)).toEqual([1, 2, 3, 4, 5]);
55
+ });
56
+
57
+ it("CString reads null-terminated bytes", () => {
58
+ const buf = new Uint8Array([72, 105, 33, 0]); // "Hi!\0"
59
+ const s = new CString(ptr(buf));
60
+ expect(s.toString()).toBe("Hi!");
61
+ });
62
+
63
+ it("JSCallback: native invokes JS via function pointer (qsort)", () => {
64
+ const lib = dlopen(libcPath, {
65
+ qsort: {
66
+ args: [
67
+ FFIType.ptr,
68
+ FFIType.u64,
69
+ FFIType.u64,
70
+ FFIType.function,
71
+ ],
72
+ returns: FFIType.void,
73
+ },
74
+ });
75
+
76
+ let comparisonCount = 0;
77
+ const compare = new JSCallback(
78
+ (aPtr: Pointer, bPtr: Pointer) => {
79
+ comparisonCount++;
80
+ const a = new Int32Array(toArrayBuffer(aPtr, 0, 4))[0]!;
81
+ const b = new Int32Array(toArrayBuffer(bPtr, 0, 4))[0]!;
82
+ return a - b;
83
+ },
84
+ {
85
+ args: [FFIType.ptr, FFIType.ptr],
86
+ returns: FFIType.i32,
87
+ },
88
+ );
89
+
90
+ const arr = new Int32Array([5, 2, 8, 1, 3]);
91
+ lib.symbols.qsort(
92
+ ptr(arr),
93
+ BigInt(arr.length),
94
+ 4n,
95
+ compare.ptr,
96
+ );
97
+
98
+ expect(Array.from(arr)).toEqual([1, 2, 3, 5, 8]);
99
+ expect(comparisonCount).toBeGreaterThan(0);
100
+
101
+ compare.close();
102
+ lib.close();
103
+ });
104
+ },
105
+ );
@@ -74,7 +74,7 @@ const randomId = Math.random().toString(36).substring(7);
74
74
 
75
75
  export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
76
76
  id: number = nextWebviewId++;
77
- ptr!: Pointer;
77
+ ptr: Pointer | null = null;
78
78
  hostWebviewId?: number;
79
79
  windowId!: number;
80
80
  renderer!: "cef" | "native";
@@ -223,17 +223,12 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
223
223
  }
224
224
 
225
225
  loadURL(url: string) {
226
- console.log(`DEBUG: loadURL called for webview ${this.id}: ${url}`);
227
226
  this.url = url;
228
227
  native!.symbols.loadURLInWebView(this.ptr, toCString(this.url));
229
228
  }
230
229
 
231
230
  loadHTML(html: string) {
232
231
  this.html = html;
233
- console.log(
234
- `DEBUG: Setting HTML content for webview ${this.id}:`,
235
- html.substring(0, 50) + "...",
236
- );
237
232
 
238
233
  if (this.renderer === "cef") {
239
234
  // For CEF, store HTML content in native map and use scheme handler
@@ -361,8 +356,10 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
361
356
  unregisterHandler() {},
362
357
  });
363
358
  this.rpcHandler = undefined;
364
- this.ptr = null as any;
365
- native!.symbols.webviewRemove(ptr);
359
+
360
+ this.rpcHandler = undefined;
361
+ this.ptr = null;
362
+ native!.symbols.webviewRemove(ptr);
366
363
  }
367
364
 
368
365
  static getById(id: number) {
@@ -12,6 +12,11 @@ import { WGPUView } from "./WGPUView";
12
12
  const buildConfig = await BuildConfig.get();
13
13
 
14
14
  export type WindowOptionsType<T = undefined> = {
15
+ trafficLightOffset?: {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ activate?: boolean;
15
20
  title: string;
16
21
  frame: {
17
22
  x: number;
@@ -123,6 +128,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
123
128
  transparent: boolean = false;
124
129
  passthrough: boolean = false;
125
130
  hidden: boolean = false;
131
+ trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
126
132
  navigationRules: string | null = null;
127
133
  // Sandbox mode disables RPC and only allows event emission (for untrusted content)
128
134
  sandbox: boolean = false;
@@ -153,6 +159,10 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
153
159
  this.transparent = options.transparent ?? false;
154
160
  this.passthrough = options.passthrough ?? false;
155
161
  this.hidden = options.hidden ?? false;
162
+ this.trafficLightOffset = {
163
+ x: options.trafficLightOffset?.x ?? 0,
164
+ y: options.trafficLightOffset?.y ?? 0,
165
+ };
156
166
  this.navigationRules = options.navigationRules || null;
157
167
  this.sandbox = options.sandbox ?? false;
158
168
 
@@ -165,6 +175,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
165
175
  titleBarStyle,
166
176
  transparent,
167
177
  hidden,
178
+ activate,
168
179
  }: Partial<WindowOptionsType<T>>) {
169
180
  this.ptr = ffi.request.createWindow({
170
181
  id: this.id,
@@ -208,6 +219,8 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
208
219
  titleBarStyle: titleBarStyle || "default",
209
220
  transparent: transparent ?? false,
210
221
  hidden: hidden ?? false,
222
+ activate: activate ?? true,
223
+ trafficLightOffset: this.trafficLightOffset,
211
224
  }) as Pointer;
212
225
 
213
226
  BrowserWindowMap[this.id] = this;
@@ -263,12 +276,27 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
263
276
  return ffi.request.closeWindow({ winId: this.id });
264
277
  }
265
278
 
279
+ activate() {
280
+ return ffi.request.activateWindow({ winId: this.id });
281
+ }
282
+
266
283
  focus() {
267
- return ffi.request.focusWindow({ winId: this.id });
284
+ console.log(
285
+ "[electrobun] BrowserWindow.focus() is deprecated. Use window.activate() instead.",
286
+ );
287
+ return this.activate();
268
288
  }
269
289
 
270
290
  show() {
271
- return ffi.request.focusWindow({ winId: this.id });
291
+ return ffi.request.showWindow({ winId: this.id, activate: true });
292
+ }
293
+
294
+ showInactive() {
295
+ return ffi.request.showWindow({ winId: this.id, activate: false });
296
+ }
297
+
298
+ hide() {
299
+ return ffi.request.hideWindow({ winId: this.id });
272
300
  }
273
301
 
274
302
  minimize() {
@@ -325,6 +353,10 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
325
353
  return ffi.request.setWindowPosition({ winId: this.id, x, y });
326
354
  }
327
355
 
356
+ setWindowButtonPosition(x: number, y: number) {
357
+ return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
358
+ }
359
+
328
360
  setSize(width: number, height: number) {
329
361
  this.frame.width = width;
330
362
  this.frame.height = height;
@@ -6,6 +6,11 @@ import { getNextWindowId } from "./windowIds";
6
6
 
7
7
 
8
8
  export type GpuWindowOptionsType = {
9
+ trafficLightOffset?: {
10
+ x: number;
11
+ y: number;
12
+ };
13
+ activate?: boolean;
9
14
  title: string;
10
15
  frame: {
11
16
  x: number;
@@ -54,6 +59,7 @@ export class GpuWindow {
54
59
  title: string = "Electrobun";
55
60
  state: "creating" | "created" = "creating";
56
61
  transparent: boolean = false;
62
+ trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
57
63
  frame: {
58
64
  x: number;
59
65
  y: number;
@@ -73,6 +79,10 @@ export class GpuWindow {
73
79
  ? { ...defaultOptions.frame, ...options.frame }
74
80
  : { ...defaultOptions.frame };
75
81
  this.transparent = options.transparent ?? false;
82
+ this.trafficLightOffset = {
83
+ x: options.trafficLightOffset?.x ?? 0,
84
+ y: options.trafficLightOffset?.y ?? 0,
85
+ };
76
86
 
77
87
  this.init(options);
78
88
  }
@@ -81,6 +91,7 @@ export class GpuWindow {
81
91
  styleMask,
82
92
  titleBarStyle,
83
93
  transparent,
94
+ activate,
84
95
  }: Partial<GpuWindowOptionsType>) {
85
96
  this.ptr = ffi.request.createWindow({
86
97
  id: this.id,
@@ -123,6 +134,8 @@ export class GpuWindow {
123
134
  },
124
135
  titleBarStyle: titleBarStyle || "default",
125
136
  transparent: transparent ?? false,
137
+ activate: activate ?? true,
138
+ trafficLightOffset: this.trafficLightOffset,
126
139
  }) as Pointer;
127
140
 
128
141
  GpuWindowMap[this.id] = this;
@@ -160,12 +173,23 @@ export class GpuWindow {
160
173
  return ffi.request.closeWindow({ winId: this.id });
161
174
  }
162
175
 
176
+ activate() {
177
+ return ffi.request.activateWindow({ winId: this.id });
178
+ }
179
+
163
180
  focus() {
164
- return ffi.request.focusWindow({ winId: this.id });
181
+ console.log(
182
+ "[electrobun] GpuWindow.focus() is deprecated. Use window.activate() instead.",
183
+ );
184
+ return this.activate();
165
185
  }
166
186
 
167
187
  show() {
168
- return ffi.request.focusWindow({ winId: this.id });
188
+ return ffi.request.showWindow({ winId: this.id, activate: true });
189
+ }
190
+
191
+ showInactive() {
192
+ return ffi.request.showWindow({ winId: this.id, activate: false });
169
193
  }
170
194
 
171
195
  minimize() {
@@ -214,6 +238,10 @@ export class GpuWindow {
214
238
  return ffi.request.setWindowPosition({ winId: this.id, x, y });
215
239
  }
216
240
 
241
+ setWindowButtonPosition(x: number, y: number) {
242
+ return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
243
+ }
244
+
217
245
  setSize(width: number, height: number) {
218
246
  this.frame.width = width;
219
247
  this.frame.height = height;
@@ -1,5 +1,5 @@
1
1
  import { resolve } from "path";
2
2
 
3
- const RESOURCES_FOLDER = resolve("../Resources/");
3
+ export const RESOURCES_FOLDER = resolve("../Resources/");
4
4
 
5
5
  export const VIEWS_FOLDER = resolve(RESOURCES_FOLDER, "app/views");
@@ -179,7 +179,7 @@ const Updater = {
179
179
  // todo: allow switching channels, by default will check the current channel
180
180
  checkForUpdate: async () => {
181
181
  emitStatus("checking", "Checking for updates...");
182
- const localInfo = await Updater.getLocallocalInfo();
182
+ const localInfo = await Updater.getLocalInfo();
183
183
 
184
184
  if (localInfo.channel === "dev") {
185
185
  emitStatus("no-update", "Dev channel - updates disabled", {
@@ -283,7 +283,7 @@ const Updater = {
283
283
  await Updater.channelBucketUrl(); // Ensure localInfo is loaded
284
284
  const appFileName = localInfo.name;
285
285
 
286
- let currentHash = (await Updater.getLocallocalInfo()).hash;
286
+ let currentHash = (await Updater.getLocalInfo()).hash;
287
287
  let latestHash = (await Updater.checkForUpdate()).hash;
288
288
 
289
289
  const extractionFolder = join(appDataFolder, "self-extraction");
@@ -1074,14 +1074,14 @@ del "%~f0"
1074
1074
  },
1075
1075
 
1076
1076
  channelBucketUrl: async () => {
1077
- await Updater.getLocallocalInfo();
1077
+ await Updater.getLocalInfo();
1078
1078
  // With flat prefix-based naming, channelBucketUrl is just the baseUrl
1079
1079
  // Users can also use Updater.localInfo.baseUrl() directly
1080
1080
  return localInfo.baseUrl;
1081
1081
  },
1082
1082
 
1083
1083
  appDataFolder: async () => {
1084
- await Updater.getLocallocalInfo();
1084
+ await Updater.getLocalInfo();
1085
1085
  // Use identifier + channel for the app data folder
1086
1086
  // e.g., ~/Library/Application Support/sh.blackboard.myapp/canary/
1087
1087
  const appDataFolder = join(
@@ -1096,20 +1096,20 @@ del "%~f0"
1096
1096
  // TODO: consider moving this from "Updater.localInfo" to "BuildVars"
1097
1097
  localInfo: {
1098
1098
  version: async () => {
1099
- return (await Updater.getLocallocalInfo()).version;
1099
+ return (await Updater.getLocalInfo()).version;
1100
1100
  },
1101
1101
  hash: async () => {
1102
- return (await Updater.getLocallocalInfo()).hash;
1102
+ return (await Updater.getLocalInfo()).hash;
1103
1103
  },
1104
1104
  channel: async () => {
1105
- return (await Updater.getLocallocalInfo()).channel;
1105
+ return (await Updater.getLocalInfo()).channel;
1106
1106
  },
1107
1107
  baseUrl: async () => {
1108
- return (await Updater.getLocallocalInfo()).baseUrl;
1108
+ return (await Updater.getLocalInfo()).baseUrl;
1109
1109
  },
1110
1110
  },
1111
1111
 
1112
- getLocallocalInfo: async () => {
1112
+ getLocalInfo: async () => {
1113
1113
  if (localInfo) {
1114
1114
  return localInfo;
1115
1115
  }
@@ -1122,8 +1122,15 @@ del "%~f0"
1122
1122
  console.error("Failed to read version.json", error);
1123
1123
  localInfo = { identifier: "", channel: "", version: "", hash: "", baseUrl: "", name: "" };
1124
1124
  return localInfo;
1125
- }
1126
- },
1127
- };
1125
+ }
1126
+ },
1127
+ getLocallocalInfo: async () => {
1128
+ console.error(
1129
+ "[Electrobun] Updater.getLocallocalInfo() is deprecated. Use Updater.getLocalInfo() instead.",
1130
+ );
1131
+
1132
+ return Updater.getLocalInfo();
1133
+ },
1134
+ };
1128
1135
 
1129
1136
  export { Updater };
@@ -229,6 +229,7 @@ export {
229
229
  Screen,
230
230
  Session,
231
231
  WGPUBridge,
232
+
232
233
  BuildConfig,
233
234
  };
234
235
 
@@ -247,6 +248,7 @@ const Electrobun = {
247
248
  Screen,
248
249
  Session,
249
250
  WGPUBridge,
251
+
250
252
  BuildConfig,
251
253
  events: electobunEventEmmitter,
252
254
  PATHS,