goleo

module
v0.12.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT

README ΒΆ

Goleo

Build cross-platform desktop and mobile apps with a Go backend and a web UI.

Write your app logic in Go. Build your interface with any web framework β€” Vue, React, Svelte, or plain HTML. Ship one codebase to Windows, macOS, Linux, Android, iOS, and the web (PWA).

Goleo is in the Tauri / Wails class β€” a compiled backend driving the OS's native webview β€” but the backend is Go, and a single project targets desktop and mobile and PWA.

app.Bridge().Handle("greet", func(ctx context.Context, args json.RawMessage) (any, error) {
    var p struct{ Name string `json:"name"` }
    _ = json.Unmarshal(args, &p)
    return "Hello, " + p.Name + "!", nil
})
import { invoke } from '@goleo/bridge'
const msg = await invoke<string>('greet', { name: 'World' })   // β†’ "Hello, World!"

πŸ“– Developer Guide

Full step-by-step docs live in docs/guide/ β€” installation, project setup, building, packaging (icons + metadata + installers), deploying & auto-update, wiring up your app, RPC, native menus, system tray, and mobile (device dev + sideloading).

Why Goleo

  • πŸ–₯️ One codebase, six targets β€” Windows, macOS, Linux, Android, iOS, PWA.
  • 🐹 Real Go on the backend β€” full concurrency, the standard library, and the Go ecosystem for your app logic and system access.
  • 🎨 Any web frontend β€” Vue (default template), React, Svelte, vanilla β€” anything Vite builds.
  • πŸ”Œ Typed bridge β€” call Go from JS with invoke(), push events from Go with Emit(); generate TypeScript types for every command.
  • πŸ“¦ Small single binary β€” the frontend is embedded via //go:embed; no bundled Chromium (uses the OS webview).
  • πŸš€ Ships itself β€” installers, code signing/notarization, and a signed auto-updater built into the CLI.
  • πŸͺŸ Native desktop integration β€” multi-window, system tray, single-instance, launch-on-login, deep links, and a signal-based lifecycle.
  • πŸ”’ Secure by design β€” an opt-in runtime capability ACL and a hardened loopback bridge.
  • πŸ“± Device features β€” clipboard, filesystem, dialogs, share, camera, geolocation, battery, sensors, notifications, and more β€” native where it counts, graceful browser fallbacks elsewhere.

Quick start

# Install the CLI (npm, or `go install github.com/daforester/goleo/cli/goleo@latest`)
npm install -g @goleo/cli

# Scaffold a project (Vue + Vite + @goleo/bridge). Prompts for a template;
# add --demo for the full host-feature showcase. `npx @goleo/cli new` works too.
goleo new my-app
cd my-app
cd frontend && npm install && cd ..

# Live development (Go backend + Vite HMR)
goleo dev

# Build a single binary for the current platform
goleo build

Then edit backend/app/app.go (startup + feature wiring), backend/commands/ (your Go commands), and frontend/src/ (your UI). That's it.


Project layout

my-app/
β”œβ”€β”€ goleo.json              # project + bundle/publish config
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ app/app.go          # the one file you edit: config, command + feature registration
β”‚   β”œβ”€β”€ commands/           # your backend commands
β”‚   β”œβ”€β”€ main.go             # generated (desktop entry point) β€” do not edit
β”‚   └── gomobile/           # generated (mobile entry points) β€” do not edit
└── frontend/               # your web app (Vite; Vue by default, swappable)
    └── src/main.ts         # inits the bridge

main.go and the gomobile/ glue are regenerated on every build β€” all your logic lives in backend/app/app.go and backend/commands/.


The bridge β€” calling Go from your UI

Go β€” register a command:

app.Bridge().Handle("saveNote", func(ctx context.Context, args json.RawMessage) (any, error) {
    var note struct{ Title, Body string }
    if err := json.Unmarshal(args, &note); err != nil {
        return nil, err
    }
    // ...persist it...
    return map[string]string{"id": "note-1"}, nil
})

// Push an event to the frontend at any time:
app.Emit("sync:done", map[string]any{"count": 42})

TypeScript β€” invoke it and listen for events:

import { invoke, on } from '@goleo/bridge'

const { id } = await invoke<{ id: string }>('saveNote', { title: 'Hi', body: '...' })
const unsubscribe = on('sync:done', (data) => console.log('synced', data))

Run goleo generate types to produce frontend/src/goleo.d.ts with a fully typed invoke() for every built-in command.


Desktop capabilities

Configure in runtime.Config and call the bridge helpers from @goleo/bridge.

Multi-window, tray, and lifecycle:

a = runtime.New(runtime.Config{
    Title:            "My App",
    InProcessWindows: true,               // extra windows in-process (Windows: own message loop;
                                          // macOS/Linux: share the primary's run loop)
    SingleInstance:   true,               // a second launch focuses the running one
    URLScheme:        "myapp",            // register myapp:// deep links
    Background:       true,               // headless controller (window(s) on demand)
    Tray: &runtime.TrayConfig{            // optional system tray
        Tooltip: "My App",
        Items: []runtime.TrayItem{
            {Label: "Open", OnClick: func() { a.OpenWindow(runtime.WindowOptions{Path: "/"}) }},
            {Label: "Quit", OnClick: func() { a.Quit() }},
        },
    },
})
import { openWindow, quitApp, getInitialURL, onDeepLink,
         enableAutostart, checkForUpdate, applyUpdate } from '@goleo/bridge'

await openWindow({ path: '/settings', width: 600, height: 400 })
onDeepLink((url) => route(url))               // myapp:// links while running
await enableAutostart()                       // launch on login
if ((await checkForUpdate()).available) await applyUpdate()
Capability Config / API
Additional windows App.OpenWindow Β· openWindow/closeWindow/listWindows Β· WindowOptions.ExitOnClose
System tray Config.Tray + Config.Background
Single instance Config.SingleInstance β†’ app:secondInstance event
Launch on login enableAutostart / disableAutostart / isAutostartEnabled
Deep links Config.URLScheme β†’ getInitialURL + onDeepLink
Graceful quit App.Quit() Β· quitApp()
Auto-update runtime.RegisterUpdater + checkForUpdate/applyUpdate

Device features & storage

Register the ones you use in backend/app/app.go; the CLI auto-detects them for mobile builds. Each has a TS wrapper with a browser fallback, so PWA/dev degrade gracefully.

runtime.RegisterDesktopFeatures(a.Bridge())  // clipboard, dialogs, filesystem
runtime.RegisterStore(a.Bridge())            // persistent key/value store
runtime.RegisterShare(a.Bridge())            // native share sheet
runtime.RegisterClipboard(a.Bridge())
runtime.RegisterCamera(a.Bridge())           // + geolocation, battery, sensors, vibration, nfc, ble…
import { storeSet, storeGet, share, clipboardReadText } from '@goleo/bridge'
await storeSet('theme', 'dark')
const theme = await storeGet<string>('theme')
await share({ title: 'Goleo', url: 'https://example.com' })

Available: clipboard Β· dialogs Β· filesystem Β· key/value store Β· share Β· camera Β· geolocation Β· battery Β· sensors Β· vibration Β· wake-lock Β· NFC Β· Bluetooth (BLE) Β· notifications Β· background Β· push.


Security β€” capability ACL

Bridge access is permissive by default; set a Policy to switch to deny-by-default on a per-method basis (Tauri-style, enforced centrally on every invoke):

app.SetPolicy(&runtime.Policy{
    Allow:   []string{"goleo:store*", "greet"},   // exact or "prefix*"; core info commands always allowed
    FSRoots: []string{"/home/me/app-data"},       // widen the filesystem scope
})

Filesystem access is confined by default. The fs plugin can reach the app's own data directory, anything in Policy.FSRoots, and any path the user picked in a native file dialog this session β€” nothing else. Writes and deletes outside that scope are refused; out-of-scope reads currently log a deprecation warning and will become errors. Set Config.FSScope = runtime.FSScopeUnrestricted (or GOLEO_FS_UNRESTRICTED=1) for a tool that genuinely needs the whole disk. System locations are refused for writes in every mode.

HTTPHosts and ShellPrograms are reserved β€” goleo has no http or shell plugin yet, so they gate nothing today.

The loopback bridge is also hardened in production (loopback-only bind, origin allow-list, per-launch token). goleo:openURL only opens http/https/mailto/tel plus your own Config.URLScheme.


Distribution & auto-update

The CLI takes you from build to a self-updating installer:

goleo build --bundle             # native installer: NSIS .exe Β· .dmg Β· .deb/.rpm
goleo build --bundle --publish   # + write an ed25519-signed update manifest
goleo generate updater-key       # keypair for signing updates

Code signing/notarization are env-driven (GOLEO_WIN_CERT, GOLEO_MAC_IDENTITY, GOLEO_APPLE_ID, …) so secrets stay out of the repo and CI can inject them. The in-app updater verifies the signed manifest before applying an update.


CLI reference

Command What it does
goleo new <name> Scaffold a new project
goleo dev Dev mode β€” Go backend + Vite HMR
goleo dev pwa Frontend-only PWA dev (no Go backend)
goleo build [target] Build for current/windows/linux/darwin/android/ios/pwa
goleo build --bundle Also produce a native installer
goleo build --publish Also write the signed update manifest
goleo emulate android Build + run on a connected Android emulator/device
goleo generate types Generate goleo.d.ts typed bridge bindings
goleo generate updater-key Generate an ed25519 update-signing keypair

Platform support

Desktop app Mobile app PWA Native webview
Windows βœ… β€” βœ… WebView2 (cgo-free)
macOS βœ… β€” βœ… WKWebView (cgo-free)
Linux βœ… β€” βœ… WebKitGTK (cgo-free)
Android β€” βœ… βœ… system WebView (gomobile)
iOS β€” βœ… βœ… WKWebView (gomobile)

All three desktop builds are fully cgo-free and cross-compile from one machine β€” the glaze binding (WKWebView / WebKitGTK / WebView2 via purego) drives the OS webview with no C toolchain in the loop. Mobile builds use gomobile; iOS requires macOS + Xcode.


How it works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   invoke / events    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Web UI (OS webview)    β”‚ ◄──────────────────► β”‚  Go backend + Bridge   β”‚
β”‚  @goleo/bridge          β”‚  WebSocket Β· HTTP    β”‚  your commands + feats β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  Β· native bind       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Dev: Vite serves the UI with HMR and proxies to the Go backend.
  • Production: the frontend is embedded in the Go binary (//go:embed) and served from a hardened loopback server; the OS webview loads it.
  • Mobile: a gomobile .aar/.xcframework (an intermediate; Gradle turns the AAR into the app.apk/app.aab you ship) runs the same Go backend inside the platform's native WebView.

Deeper docs: AGENTS.md (architecture), docs/roadmap.md (masterplan + status), SPIKES.md (feasibility findings), docs/comparison.md (vs Tauri v2 & Wails v3).


Status

Feature-complete and shipping-ready on all targets via the paths above. The desktop webview is now cgo-free on all three OSes (macOS / Linux / Windows via glaze); see the roadmap for what's next.

License

MIT

Directories ΒΆ

Path Synopsis
cli
cmd
goleo command
autostart
Package autostart registers/unregisters an app to launch on login.
Package autostart registers/unregisters an app to launch on login.
deeplink
Package deeplink registers a custom URL scheme (myapp://) so links launch or wake the app.
Package deeplink registers a custom URL scheme (myapp://) so links launch or wake the app.
fs
microphone
Package microphone exposes the microphone's PERMISSION state, not audio capture.
Package microphone exposes the microphone's PERMISSION state, not audio capture.
nfc
singleinstance
Package singleinstance enforces a single running instance of an app and forwards a later launch's args to the primary (e.g.
Package singleinstance enforces a single running instance of an app and forwards a later launch's args to the primary (e.g.
store
Package store is a simple persistent key/value store backed by a JSON file in the app data directory.
Package store is a simple persistent key/value store backed by a JSON file in the app data directory.
updater
Package updater is a desktop auto-update client.
Package updater is a desktop auto-update client.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL