native-app-wrapper
Tauri scaffold that packages a website or a bundled CLI as a native desktop + mobile app, configured from a single JSON profile
π€ Agent skill β npx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent --skill native-app-wrapper (what it covers)
A Tauri shell that turns one JSON profile into a native desktop (Windows/macOS/Linux) and mobile (Android/iOS) app. It packages one of two things:
- A website (
"mode": "remote") β a titled window that loads the site directly, plus the OS-level plumbing a plain webview doesn't get for free: a real app icon on every platform, a Google-OAuth-compatible login handoff, and a fullscreen toggle. - A command-line tool (
"mode": "local") β a bundled HTML frontend backed by the CLI itself, shipped alongside the app binary as a Tauri sidecar, so the app runs with no Node, Python, or other runtime installed on the user's machine. Seedocs/LOCAL_APPS.md.
packages/about-system-info/native is a working example of the second kind β the about-system
CLI shipped as an installable desktop app for all three desktop OSes.
What this is not
Not a place for app-specific UI or logic. In remote mode the wrapped site provides all of that; in
local mode the app's own dist/ and its sidecar do. This package's job stops at: open a window,
register the app's identity (icon, name, bundle id) with each OS, hand a browser-based OAuth login
back to that window, and expose the bundled CLI's output to the bundled frontend. Anything more
belongs in the app being wrapped.
Quick start
Scaffold a copy of the wrapper for your own app:
cd packages/native-app-wrapper
node bin/cli.js init ../../apps/my-app/native --profile-file ./my-app.json
cd ../../apps/my-app/native
npm install
npm run devinit copies the wrapper's sources into the target directory, gives that copy your profile as its
only identity, regenerates the Tauri config and Rust constants from it, renames the crate, and
draws a placeholder icon set so the copy builds immediately. It also leaves out what the profile
doesn't use: a desktop-only profile gets no android:build script, an offline CLI app gets no
guide to browser OAuth handoffs. Nothing links back to this package afterward β the copy is a
standalone app you can commit next to the thing it wraps, and updating it later means copying the
changed scripts across, not re-running init over your own dist/.
To work on this package itself instead, npm run dev here opens a window on the untouched
profiles/example.json (https://example.com), which is what the checked-in
src-tauri/tauri.conf.json is generated from.
Layout
native-app-wrapper/
βββ bin/cli.js # init / configure / icons
βββ profiles/ # one JSON file = one app identity (see profiles/README.md)
β βββ example.json # the template, and this package's own default
βββ scripts/
β βββ profile.mjs # loads + validates a profile; one definition of the schema
β βββ configure.mjs # profile -> tauri.conf.json, generated_config.rs, remote.json
β βββ build-sidecar.mjs # profile's sidecar.build -> src-tauri/binaries/<name>-<triple>
β βββ generate-icons.mjs # profile's iconSource -> full src-tauri/icons/ set (Tauri CLI)
β βββ generate-placeholder-icons.mjs # draws a buildable icon set with no artwork and no deps
β βββ lib/png.mjs # the PNG/ICO/ICNS writers that make the above possible
βββ src-tauri/
β βββ src/lib.rs # window setup, deep-link OAuth handoff, sidecar bridge, fullscreen toggle
β βββ capabilities/ # Tauri's permission grants (default.json + a generated remote.json)
β βββ icons/ # generated; checked in so a fresh clone builds
β βββ tauri.conf.json # generated by configure
βββ assets/icon-source.png # the 1024px master `iconSource` points at by default
βββ dist/ # the bundled frontend (a placeholder in remote mode; the app in local mode)
βββ docs/
βββ BUILDING.md # local dev/build commands and release builds
βββ LOCAL_APPS.md # packaging a CLI: the sidecar bridge and the bundled frontend
βββ OAUTH.md # why login needs a system browser + deep link, and how it works here
βββ MOBILE.md # Android/iOS host requirements and gen/android, gen/apple
βββ APP_STORES.md # Microsoft Store, Mac App Store, iOS App Store, Google PlayWhy generated config instead of a hand-edited tauri.conf.json
An app's identity shows up in five places that must agree: the Tauri config, the Rust deep-link
scheme, the Rust sidecar name, the remote capability's origin scope, and the icon set. Hand-editing
means five chances to drift. scripts/configure.mjs writes all of them from the profile, so the
profile is the only file anyone edits, and CI can assert the generated files still match it.
Why a window, not a bundled copy of a remote site
In remote mode, tauri.conf.json's main window points its url straight at the profile's site over
HTTPS β there's no local copy of the app's UI to keep in sync with the real site
(build.frontendDist still points at dist/, an unused placeholder Tauri's bundler requires to
exist). The tradeoff: the app needs network access to be useful, same as opening the site in a
browser tab would. Local mode is the opposite tradeoff β everything is bundled, nothing is fetched.
Last updated on