code2app
v0.1.24
Published
Tauri scaffold that packages a website or a bundled CLI as a native desktop + mobile app, configured from a single JSON profile
Maintainers
Readme
🤖 Agent skill — npx skills@latest add https://github.com/OpenSourceAGI/dev-tools-starter-agent --skill native-app-wrapper (what it covers)
native-app-wrapper
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.
