electrobun 1.16.0 → 1.16.1-beta.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
@@ -27,9 +27,43 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
27
27
  - Provide everything you need in one tightly integrated workflow to start writing code in 5 minutes and distribute in 10.
28
28
 
29
29
  ## Apps Built with Electrobun
30
+ - [24agents](https://github.com/jhsu/24agents) - Hyperprompter
31
+ - [act-track-ai](https://github.com/IrdanGu/act-track-ai) - personal desktop productivity tracker
32
+ - [Agents Council](https://github.com/MrLesk/agents-council) - agent-to-agent MCP communication tool for feedback requests
33
+ - [ai-wrapped](https://github.com/gulivan/ai-wrapped) - Wrapped-style desktop dashboard for your AI coding agent activity
30
34
  - [Audio TTS](https://github.com/blackboardsh/audio-tts) - desktop text-to-speech app using Qwen3-TTS for voice design, cloning, and generation
35
+ - [aueio-player-desktop](https://github.com/tuomashatakka/aueio-player-desktop) - beautiful, minimal cross-platform audio player
36
+ - [bestdiff](https://github.com/tesmond/bestdiff) - a git diff checker with curved connectors
37
+ - [BuddyWriter](https://github.com/OxFrancesco/BuddyWriter) - BuddyWriter desktop and mobile apps
38
+ - [burns](https://github.com/l3wi/burns) - a Smithers manager
39
+ - [cbx-tool](https://github.com/jebin2/cbx-tool) - desktop app for reading and editing comic book archives (.cbz/.cbr)
31
40
  - [Co(lab)](https://blackboard.sh/colab/) - a hybrid web browser + code editor for deep work
41
+ - [codlogs](https://github.com/tobitege/codlogs) - search and export local Codex sessions via CLI or desktop app
42
+ - [Codex Agents Composer](https://github.com/MrLesk/codex-agents-composer) - desktop app for managing your Codex agents and their skills
43
+ - [codex-devtools](https://github.com/gulivan/codex-devtools) - desktop inspector for Codex session data; browse conversations, search messages, and analyze agent activity
44
+ - [Deskdown](https://github.com/guarana-studio/deskdown) - transform any web address into a desktop app in under 20 seconds
45
+ - [dev-3.0](https://github.com/h0x91b/dev-3.0) - helps you not get lost while managing multiple AI agents across projects
32
46
  - [DOOM](https://github.com/blackboardsh/electrobun-doom) - DOOM implemented in 2 ways: bun -> (c doom -> bundled wgpu) and (full ts port bun -> bundled wgpu)
47
+ - [electrobun-rms](https://github.com/khanhthanhdev/electrobun-rms) - fast Electrobun desktop app template with React, Tailwind CSS, and Vite
48
+ - [golb](https://github.com/chrisdadev13/golb) - desktop AI coding workspace built with React, Vite, and Tailwind
49
+ - [GOG Achievements GUI](https://github.com/timendum/gog-achievements-gui) - desktop app for managing GOG achievements
50
+ - [groov](https://github.com/laurenzcodes/groov) - desktop audio deck monitor
51
+ - [Guerilla Glass](https://github.com/okikeSolutions/guerillaglass) - open-source cross-platform creator studio for fast Record -> Edit -> Deliver workflows
52
+ - [Marginalia](https://github.com/lars-hoeijmans/Marginalia) - a simple note taking app
53
+ - [md-browse](https://github.com/needle-tools/md-browse) - a markdown-first browser that converts web pages to clean markdown
54
+ - [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
55
+ - [PLEXI](https://github.com/ianjamesburke/PLEXI) - a multi-dimensional terminal multiplexer for the agentic era
56
+ - [Prometheus](https://github.com/opensourcectl/prometheus) - desktop utility toolbox for file cleanup, document manipulation, and image processing
57
+ - [Quiver](https://ataraxy-labs.github.io/quiver/) - desktop app for GitHub PR reviews, merge conflict resolution, and AI commit messages
58
+ - [remotecode.io](https://github.com/samuelfaj/remotecode.io) - continue local AI coding sessions (Claude Code or Codex) from your mobile device
59
+ - [sirene](https://github.com/KevinBonnoron/sirene) - self-hosted multi-backend text-to-speech platform with voice cloning
60
+ - [StoryForge](https://github.com/vrrdnt/StoryForge) - desktop app for Vintage Story players to switch between game versions, modpacks, servers, and accounts
61
+ - [Tensamin Client](https://github.com/Tensamin/Client) - web, desktop, and mobile app for accessing Tensamin
62
+ - [tokenpass-desktop](https://github.com/b-open-io/tokenpass-desktop) - desktop app that runs the Sigma Identity stack locally for Bitcoin-backed authentication
63
+ - [typsmthng-desktop](https://github.com/aaditagrawal/typsmthng-desktop) - experimental desktop typing application
64
+ - [VibesOS](https://github.com/popmechanic/VibesOS) - A GUI for Claude Code that makes it easy to vibe code simple, un-hackable apps
65
+ - [VoiceVault](https://github.com/PJH720/VoiceVault) - AI-powered voice recorder with transcription, summarization, and RAG search
66
+ - [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
33
67
 
34
68
  ### Video Demos
35
69
 
@@ -41,12 +75,12 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
41
75
 
42
76
  ## Star History
43
77
 
44
- [![Star History Chart](https://api.star-history.com/svg?repos=blackboardsh/electrobun&type=date&legend=top-left&cache=2)](https://www.star-history.com/#blackboardsh/electrobun&type=date&legend=top-left)
78
+ [![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)
45
79
 
46
80
  ## Contributing
47
81
  Ways to get involved:
48
82
 
49
- - Follow us on X for updates <a href="https://twitter.com/BlackboardTech">@BlackboardTech</a> or <a href="https://bsky.app/profile/yoav.codes">@yoav.codes</a>
83
+ - 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>
50
84
  - Join the conversation on <a href="https://discord.gg/ueKE4tjaCE">Discord</a>
51
85
  - Create and participate in Github issues and discussions
52
86
  - Let me know what you're building with Electrobun
@@ -9,7 +9,11 @@ import {
9
9
  } from "../../shared/rpc.js";
10
10
  import { Updater } from "./Updater";
11
11
  import { BuildConfig } from "./BuildConfig";
12
- import { rpcPort, sendMessageToWebviewViaSocket } from "./Socket";
12
+ import {
13
+ rpcPort,
14
+ sendMessageToWebviewViaSocket,
15
+ removeSocketForWebview,
16
+ } from "./Socket";
13
17
  import { randomBytes } from "crypto";
14
18
  import { type Pointer } from "bun:ffi";
15
19
 
@@ -102,6 +106,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
102
106
  sandbox: boolean = false;
103
107
  startTransparent: boolean = false;
104
108
  startPassthrough: boolean = false;
109
+ isRemoved: boolean = false;
105
110
 
106
111
  constructor(options: Partial<BrowserViewOptions<T>> = defaultOptions) {
107
112
  // const rpc = options.rpc;
@@ -211,6 +216,9 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
211
216
  // so we have to chunk it
212
217
  // TODO: is this still needed after switching from named pipes
213
218
  executeJavascript(js: string) {
219
+ if (!this.ptr || this.isRemoved) {
220
+ return;
221
+ }
214
222
  ffi.request.evaluateJavascriptWithNoCompletion({ id: this.id, js });
215
223
  }
216
224
 
@@ -315,6 +323,9 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
315
323
 
316
324
  return {
317
325
  send(message: any) {
326
+ if (!that.ptr || that.isRemoved) {
327
+ return;
328
+ }
318
329
  const sentOverSocket = sendMessageToWebviewViaSocket(that.id, message);
319
330
 
320
331
  if (!sentOverSocket) {
@@ -327,14 +338,31 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
327
338
  }
328
339
  },
329
340
  registerHandler(handler: (msg: unknown) => void) {
341
+ if (that.isRemoved) {
342
+ return;
343
+ }
330
344
  that.rpcHandler = handler;
331
345
  },
332
346
  };
333
347
  };
334
348
 
335
349
  remove() {
336
- native.symbols.webviewRemove(this.ptr);
350
+ if (!this.ptr || this.isRemoved) {
351
+ return;
352
+ }
353
+ const ptr = this.ptr;
354
+ this.isRemoved = true;
355
+ // Drop JS-side references first so late callbacks cannot target a stale view.
337
356
  delete BrowserViewMap[this.id];
357
+ removeSocketForWebview(this.id);
358
+ this.rpc?.setTransport({
359
+ send() {},
360
+ registerHandler() {},
361
+ unregisterHandler() {},
362
+ });
363
+ this.rpcHandler = undefined;
364
+ this.ptr = null as any;
365
+ native.symbols.webviewRemove(ptr);
338
366
  }
339
367
 
340
368
  static getById(id: number) {
@@ -47,6 +47,14 @@ export const socketMap: {
47
47
  };
48
48
  } = {};
49
49
 
50
+ export const removeSocketForWebview = (webviewId: number) => {
51
+ const rpc = socketMap[webviewId];
52
+ if (!rpc) return;
53
+
54
+ rpc.socket = null;
55
+ delete socketMap[webviewId];
56
+ };
57
+
50
58
  const startRPCServer = () => {
51
59
  const startPort = 50000;
52
60
  const endPort = 65535;
@@ -2881,7 +2881,7 @@ export const internalRpcHandlers = {
2881
2881
  );
2882
2882
  return;
2883
2883
  }
2884
- native.symbols.webviewRemove(webview.ptr);
2884
+ webview.remove();
2885
2885
  },
2886
2886
  startWindowMove: (params: { id: number }) => {
2887
2887
  const windowPtr = getWindowPtr(params.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "1.16.0",
3
+ "version": "1.16.1-beta.0",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",
package/src/cli/index.ts CHANGED
@@ -482,35 +482,41 @@ async function ensureBunBinary(
482
482
  targetOS: "macos" | "win" | "linux",
483
483
  targetArch: "arm64" | "x64",
484
484
  bunVersion?: string,
485
+ bunnyBun?: string,
485
486
  ): Promise<string> {
486
- if (!bunVersion) {
487
+ const effectiveVersion = bunnyBun || bunVersion;
488
+ if (!effectiveVersion) {
487
489
  return getPlatformPaths(targetOS, targetArch).BUN_BINARY;
488
490
  }
489
491
 
490
492
  const binExt = targetOS === "win" ? ".exe" : "";
491
- const overrideDir = join(ELECTROBUN_CACHE_PATH, "bun-override", `${targetOS}-${targetArch}`);
493
+ const cacheSubdir = bunnyBun ? "bunny-bun-override" : "bun-override";
494
+ const overrideDir = join(ELECTROBUN_CACHE_PATH, cacheSubdir, `${targetOS}-${targetArch}`);
492
495
  const overrideBinary = join(overrideDir, `bun${binExt}`);
493
496
  const versionFile = join(overrideDir, ".bun-version");
494
497
 
495
498
  // Check if already downloaded with matching version
496
499
  if (existsSync(overrideBinary) && existsSync(versionFile)) {
497
500
  const cachedVersion = readFileSync(versionFile, "utf8").trim();
498
- if (cachedVersion === bunVersion) {
501
+ if (cachedVersion === effectiveVersion) {
499
502
  console.log(
500
- `Custom Bun ${bunVersion} already cached for ${targetOS}-${targetArch}`,
503
+ `${bunnyBun ? "Bunny" : "Custom"} Bun ${effectiveVersion} already cached for ${targetOS}-${targetArch}`,
501
504
  );
502
505
  return overrideBinary;
503
506
  }
504
- // Version mismatch - remove stale cache
505
507
  console.log(
506
- `Cached Bun version "${cachedVersion}" does not match requested "${bunVersion}", re-downloading...`,
508
+ `Cached Bun version "${cachedVersion}" does not match requested "${effectiveVersion}", re-downloading...`,
507
509
  );
508
510
  rmSync(overrideDir, { recursive: true, force: true });
509
511
  } else if (existsSync(overrideDir)) {
510
512
  rmSync(overrideDir, { recursive: true, force: true });
511
513
  }
512
514
 
513
- await downloadCustomBun(bunVersion, targetOS, targetArch);
515
+ if (bunnyBun) {
516
+ await downloadBunnyBun(bunnyBun, targetOS, targetArch);
517
+ } else {
518
+ await downloadCustomBun(effectiveVersion, targetOS, targetArch);
519
+ }
514
520
  return overrideBinary;
515
521
  }
516
522
 
@@ -664,6 +670,123 @@ async function downloadCustomBun(
664
670
  }
665
671
  }
666
672
 
673
+ /**
674
+ * Downloads Electrobunny's Bun fork from blackboardsh/bun GitHub releases.
675
+ * Release assets follow the same naming convention as oven-sh/bun:
676
+ * bun-darwin-aarch64.zip, bun-linux-x64.zip, etc.
677
+ */
678
+ async function downloadBunnyBun(
679
+ releaseTag: string,
680
+ platformOS: "macos" | "win" | "linux",
681
+ platformArch: "arm64" | "x64",
682
+ ) {
683
+ let assetName: string;
684
+ let dirName: string;
685
+
686
+ // Asset names match the CI artifact names from blackboardsh/bun
687
+ if (platformOS === "win") {
688
+ assetName = "bun-windows-x64.zip";
689
+ dirName = "bun-windows-x64";
690
+ } else if (platformOS === "macos") {
691
+ assetName = platformArch === "arm64" ? "bun-darwin-arm64.zip" : "bun-darwin-x64.zip";
692
+ dirName = platformArch === "arm64" ? "bun-darwin-arm64" : "bun-darwin-x64";
693
+ } else {
694
+ assetName = platformArch === "arm64" ? "bun-linux-arm64.zip" : "bun-linux-x64.zip";
695
+ dirName = platformArch === "arm64" ? "bun-linux-arm64" : "bun-linux-x64";
696
+ }
697
+
698
+ const binExt = platformOS === "win" ? ".exe" : "";
699
+ const overrideDir = join(ELECTROBUN_CACHE_PATH, "bunny-bun-override", `${platformOS}-${platformArch}`);
700
+ const overrideBinary = join(overrideDir, `bun${binExt}`);
701
+ const bunUrl = `https://github.com/blackboardsh/bun/releases/download/${releaseTag}/${assetName}`;
702
+
703
+ console.log(`Using Bunny Bun: ${releaseTag}`);
704
+ console.log(`Downloading from: ${bunUrl}`);
705
+
706
+ mkdirSync(overrideDir, { recursive: true });
707
+ const tempZipPath = join(overrideDir, "temp.zip");
708
+
709
+ try {
710
+ const response = await fetch(bunUrl);
711
+ if (!response.ok) {
712
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
713
+ }
714
+
715
+ const contentLength = response.headers.get("content-length");
716
+ const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
717
+ const fileStream = createWriteStream(tempZipPath);
718
+ let downloadedSize = 0;
719
+ let lastReportedPercent = -1;
720
+
721
+ if (response.body) {
722
+ const reader = response.body.getReader();
723
+ while (true) {
724
+ const { done, value } = await reader.read();
725
+ if (done) break;
726
+ const chunk = Buffer.from(value);
727
+ fileStream.write(chunk);
728
+ downloadedSize += chunk.length;
729
+ if (totalSize > 0) {
730
+ const percent = Math.round((downloadedSize / totalSize) * 100);
731
+ const percentTier = Math.floor(percent / 10) * 10;
732
+ if (percentTier > lastReportedPercent && percentTier <= 100) {
733
+ console.log(` Progress: ${percentTier}% (${Math.round(downloadedSize / 1024 / 1024)}MB/${Math.round(totalSize / 1024 / 1024)}MB)`);
734
+ lastReportedPercent = percentTier;
735
+ }
736
+ }
737
+ }
738
+ }
739
+
740
+ await new Promise((resolve, reject) => {
741
+ fileStream.end((error: any) => { if (error) reject(error); else resolve(void 0); });
742
+ });
743
+
744
+ console.log(`Download completed (${Math.round(downloadedSize / 1024 / 1024)}MB), extracting...`);
745
+
746
+ if (platformOS === "win") {
747
+ execSync(`powershell -command "Expand-Archive -Path '${tempZipPath}' -DestinationPath '${overrideDir}' -Force"`, { stdio: "inherit" });
748
+ } else {
749
+ execSync(`unzip -o ${escapePathForTerminal(tempZipPath)} -d ${escapePathForTerminal(overrideDir)}`, { stdio: "inherit" });
750
+ }
751
+
752
+ // Move binary from extracted subdirectory
753
+ const extractedBinary = join(overrideDir, dirName, `bun${binExt}`);
754
+ if (existsSync(extractedBinary)) {
755
+ renameSync(extractedBinary, overrideBinary);
756
+ } else {
757
+ throw new Error(`Bun binary not found after extraction at ${extractedBinary}`);
758
+ }
759
+
760
+ if (platformOS !== "win") {
761
+ execSync(`chmod +x ${escapePathForTerminal(overrideBinary)}`);
762
+ }
763
+
764
+ // Also extract ICU data if present
765
+ const extractedDir = join(overrideDir, dirName);
766
+ if (existsSync(extractedDir)) {
767
+ for (const file of readdirSync(extractedDir)) {
768
+ if (file.endsWith(".dat")) {
769
+ renameSync(join(extractedDir, file), join(overrideDir, file));
770
+ }
771
+ }
772
+ }
773
+
774
+ writeFileSync(join(overrideDir, ".bun-version"), releaseTag);
775
+
776
+ if (existsSync(tempZipPath)) unlinkSync(tempZipPath);
777
+ if (existsSync(extractedDir)) rmSync(extractedDir, { recursive: true, force: true });
778
+
779
+ console.log(`Bunny Bun ${releaseTag} for ${platformOS}-${platformArch} set up successfully`);
780
+ } catch (error: any) {
781
+ if (existsSync(overrideDir)) {
782
+ try { rmSync(overrideDir, { recursive: true, force: true }); } catch {}
783
+ }
784
+ console.error(`Failed to set up Bunny Bun ${releaseTag} for ${platformOS}-${platformArch}:`, error.message);
785
+ console.error(`\nVerify the release tag exists at: https://github.com/blackboardsh/bun/releases`);
786
+ process.exit(1);
787
+ }
788
+ }
789
+
667
790
  async function ensureCEFDependencies(
668
791
  targetOS?: "macos" | "win" | "linux",
669
792
  targetArch?: "arm64" | "x64",
@@ -1357,6 +1480,7 @@ const defaultConfig = {
1357
1480
  cefVersion: undefined as string | undefined, // Override CEF version: "CEF_VERSION+chromium-CHROMIUM_VERSION"
1358
1481
  wgpuVersion: undefined as string | undefined, // Override Dawn (WebGPU) version: "0.2.3" or "v0.2.3-beta.0"
1359
1482
  bunVersion: undefined as string | undefined, // Override Bun runtime version: "1.4.2"
1483
+ bunnyBun: undefined as string | undefined, // Use Electrobunny's Bun fork: "bunny-bun-abc1234" (release tag from blackboardsh/bun)
1360
1484
  locales: undefined as string[] | "*" | undefined, // ICU locales subset (Linux/Windows)
1361
1485
  mac: {
1362
1486
  codesign: false,
@@ -2191,6 +2315,7 @@ Categories=Utility;Application;
2191
2315
  currentTarget.os,
2192
2316
  currentTarget.arch,
2193
2317
  config.build.bunVersion,
2318
+ config.build.bunnyBun,
2194
2319
  );
2195
2320
  // Note: .bin/bun binary in node_modules is a symlink to the versioned one in another place
2196
2321
  // in node_modules, so we have to dereference here to get the actual binary in the bundle.
@@ -3113,6 +3238,7 @@ Categories=Utility;Application;
3113
3238
  ? { cefVersion: config.build?.cefVersion ?? DEFAULT_CEF_VERSION_STRING }
3114
3239
  : {}),
3115
3240
  bunVersion: config.build?.bunVersion ?? BUN_VERSION,
3241
+ ...(config.build?.bunnyBun ? { bunnyBun: config.build.bunnyBun } : {}),
3116
3242
  };
3117
3243
 
3118
3244
  // Include chromiumFlags only if the developer defined them