Tuboshu: turning websites into separate desktop apps with Electron
Transform Your Favorite Websites into Seamless Desktop Experiences✨! 把常用的网站集合到一个桌面程序里。
At a glance
- What is it?
- Tuboshu is an Electron application that wraps websites as standalone desktop windows, with several accounts per site, a global hotkey to show and hide them, custom window chrome and injected JavaScript. It suits people who want one tool instead of a browser full of tabs, and it is a single developer project with a licence field that disagrees with its manifest.
- Who is it for?
- Adopt Tuboshu if you keep several web services open all day and want them as separate OS windows with their own accounts and a single hotkey to summon them, because that is exactly the workflow the project is built around. Do not adopt it for sites that require a native app, for offline use, or for a build you have to sign and distribute internally, since the packaging configuration targets public installers.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 64 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One window per website, managed by an Electron shell
Tuboshu is a desktop application that turns websites into desktop applications. The repository description puts it in Chinese, saying it collects the websites you use into a single desktop program, and the README headline promises seamless desktop experiences.
The mechanism is Electron. A site is added to a list, and each entry gets its own window rather than a tab, so the result looks like a set of small native applications instead of a browser with many open pages. The project is built with electron at 37.10.3, electron-vite for the build, and electron-builder for packaging, and the application code lives in an app directory with a gui directory for the interface and a resource directory for icons and other assets.
The stated audiences are specific in a useful way. Developers are described as quickly creating a debugging web container, designers as turning Figma, Canva or Poxso into a dedicated workbench, operations staff as running several accounts of a site side by side, data analysts as keeping Notion or Airtable resident, and ordinary users as building a personal centre of web applications.
That last set of examples tells you the tool is aimed at people who keep a handful of browser-based services open as their working day, and the screenshots in the README are grouped by that kind of site, with collections of AI sites, video sites, design sites and social sites.
Several accounts per site, and what isolation means here
The feature list in the README calls out multiple account support separately: a site can be opened several times, with account isolation, running independently without interfering with each other.
In an Electron application, running the same site twice means two BrowserWindow instances pointed at the same origin, and the thing that makes that work in practice is storage partition. If both windows shared a session partition, logging into one account would log the other out, which would make the feature useless. The isolation claim therefore implies that each instance keeps its own cookies and its own storage, and that is the part of the design worth checking for yourself if you depend on it, since the README states the behaviour without describing the mechanism.
The privacy line sits next to it. The README says configuration data is stored locally and no cloud synchronisation is required. The package dependencies support that reading: the single runtime dependency is sql.js, which is SQLite compiled to WebAssembly, and the build configuration unpacks node_modules/sql.js/dist/sql-wasm.wasm from the asar archive so the database file can be read at runtime.
So the persistence layer is a local database rather than a settings file written to the user profile, and there is no account or sync service anywhere in the dependency list.
The global hotkey and the window-level customisation
Two customisation features carry most of the daily use.
The first is the global hotkey, described as fast show and hide with a default of Ctrl or Cmd plus H. A global shortcut registered with the operating system means the windows can be summoned or dismissed without focusing the application first, which is what turns a collection of web wrappers into something that behaves like a workspace. The key is configurable rather than fixed, since the same section of the feature list also lists custom hotkeys among the customisation options.
The second is per-site customisation: window size, icon, hotkeys and JavaScript injection, with the promise of tailoring the page to your own. JavaScript injection is the most capable of the four and the one with the most consequences, because code injected into a page runs with the page's privileges. The build configuration supports that story, since the extraResources section copies a plugin directory from ./resource/plugin into the packaged application as a plugin folder, which is a place to ship such code.
Neither of these features is documented in detail in the README, which is a limitation rather than a criticism. The customisation surface is announced in a bullet list and the specifics live in the interface and in the plugin directory, so anyone planning to extend it should read the source.
Packaging: asar, an unpacked WebAssembly file, and per platform installers
The build section of the package manifest is the most detailed part of the repository and it explains a lot about how the application is assembled.
The application is packed into an asar archive, with one exception: node_modules/sql.js/dist/sql-wasm.wasm is listed in asarUnpack, which is the standard way to let a native or WebAssembly file be loaded from disk. The compression setting is maximum, and removePackageScripts is enabled so that install scripts do not run when the packaged application is unpacked on a user's machine.
The mac target produces a dmg with an icns icon and a product category of public.app-category.productivity, and the artifact name template includes the product name, version, mac and architecture. The Windows target is an NSIS installer with a ico icon and its own artifact name template for win and architecture.
"mac": {
"category": "public.app-category.productivity",
"target": "dmg",
"icon": "resource/build/mac_icon.icns",
"identity": null,
"artifactName": "${productName}-Setup-${version}-mac-${arch}.${ext}"
},The appId is com.tuboshu.tuboshu, and an afterPack script runs at the end of packaging, which is where the plugin directory is copied into the output.
The build files list also excludes the source directories from the package, including the app folder, the github folder, the settings folder under gui, and the README, so the shipped application is a build of the interface rather than the repository.
The development loop with electron-vite and pnpm
The scripts section shows the day to day commands, and there is one for each phase.
"scripts": {
"start:debug": "electron --enable-logging --log-file=electron.log --trace-warnings .",
"start": "electron-vite build && electron .",
"pack": "electron-builder --dir",
"build": "electron-builder"
},start:debug launches Electron directly with logging enabled, writes the log to electron.log and turns on trace warnings, which is what you want when something in the renderer is failing. start does a full electron-vite build and then launches the app. pack is an unpacked build with electron-builder, useful for checking the packaged layout without producing an installer, and build produces the real thing.
The project uses pnpm, with a pnpm-lock.yaml and a pnpm-workspace.yaml at the root, and a .node-version file pinning the Node version used for development. The vite configuration lives in electron.vite.config.js, and the main entry point of the built application is out/main.js.
The development dependencies are minimal by design: electron, electron-builder and electron-vite, and nothing else, while sql.js is the only runtime dependency. There is no test framework in the manifest and no test directory at the root of the repository, which is worth knowing if you plan to contribute a change to something as fiddly as the injection mechanism.
Two inconsistencies in the metadata, and what they tell you
The repository metadata and the manifest do not agree with each other, and the version numbering is odd too.
The first is the licence. The package manifest declares a licence of ISC and names the author as deepshit2025 with an email address, while the licence field recorded for the repository is unknown. For a project that is described as completely open source and free, that is a small but real ambiguity. Nothing in the repository root listing suggests a LICENSE file, so the text of the ISC licence as declared in the manifest is the only statement of terms available, and a fork should confirm that with the author.
The second is the version. The manifest version is v2.2.2, and the tagged releases are 2.2.2 from 2026-07-04, 2.2.1 and 2.2.0 both from 2026-06-21. Yet the repository root contains a file named 更新说明_v3.0.0.txt, which is the release notes for version 3.0.0, sitting in a tree whose manifest still says 2.2.2.
That file suggests a 3.0.0 is being prepared or has been prepared and the manifest has not been bumped, or that the notes were written ahead of the release. Either way, the last push to the repository was on 2026-08-20, which is after the 2.2.2 release, so the working tree contains changes that are not in any tag.
Taken together, the two inconsistencies say the project is moving faster than its release metadata, which is normal for a single developer but is something to confirm before you build a workflow on top of it.
Editorial conclusion
Adopt Tuboshu if you keep several web services open all day and want them as separate OS windows with their own accounts and a single hotkey to summon them, because that is exactly the workflow the project is built around. Do not adopt it for sites that require a native app, for offline use, or for a build you have to sign and distribute internally, since the packaging configuration targets public installers. Before you fork it, settle the licence question, because the repository licence is recorded as unknown while the package manifest says ISC.
Frequently asked questions
What is Tuboshu built with?
Tuboshu is an Electron application built with electron at 37.10.3, electron-vite for the build and electron-builder for packaging. The only runtime dependency is sql.js, and the main entry point of the built app is out/main.js.
Does Tuboshu support running the same site with several accounts?
Yes. The feature list states that a site can be opened multiple times with account isolation, running independently without interfering with each other. Configuration data is stored locally and no cloud synchronisation is needed.
How do I build a distributable version of Tuboshu?
The manifest defines a build script that runs electron-builder, a pack script that runs electron-builder with --dir for an unpacked build, and a start script that runs electron-vite build and then electron. The mac target is a dmg and the Windows target is an NSIS installer.
What licence is Tuboshu released under?
The package manifest declares the licence as ISC and names the author as deepshit2025. The repository licence field is recorded as unknown, and no separate LICENSE file appears in the repository root listing, so the manifest declaration is the only statement of terms available.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/deepshit2025-tuboshu)Was this useful?