Build powerful, cross-platform desktop and web applications using Python 3.14+ (NoGIL) and web technologies
Project description
⚡ Forge Framework
Forge the future. Ship with Python.
Build lightweight, cross-platform desktop applications using Python as the backend and any web technology (HTML/CSS/JS, React, Vue, Svelte) as the frontend — connected via a seamless IPC bridge.
✨ Features
- Native OS WebView — Uses WKWebView (macOS), WebView2 (Windows), WebKitGTK (Linux) — NO Chromium bundling
- Tiny App Size — Final binaries under 20MB for basic apps
- Simple IPC Bridge — Call Python functions from JavaScript seamlessly
- Typed State Injection —
def handler(db: Database)auto-receives managed instances (Tauri DX) - Deny-First Security — Scoped filesystem/shell/URL permissions with glob patterns
- Circuit Breaker — Auto-disables commands that fail repeatedly, prevents cascading failures
- Crash Reports — Structured JSON crash reports with system context for post-mortem analysis
- Beautiful CLI — Scaffold, develop, build, doctor, and plugin management
- Hot Reload — Watch for file changes during development, auto-restart on config changes
- Multiple Frontends — Plain HTML, React, Vue, or Svelte templates
- NoGIL Ready — Designed for Python 3.14+ free-threaded mode with thread-safe primitives
🚀 Quick Start
Installation
pip install forge-framework
Or install the Node-facing packages:
npm install @forgedesk/api
npm install -D @forgedesk/cli
npm install -D @forgedesk/vite-plugin vite
npm create forge-app@latest my-app
The npm wrappers bootstrap the Python runtime by delegating to forge-framework.
Create a New Project
# Create a new project with the plain template
forge create my-app --template plain
# Or use React, Vue, or Svelte
forge create my-app --template react
forge create my-app --template vue
forge create my-app --template svelte
Or scaffold from npm:
npm create forge-app@latest my-app -- --template react
If your npm is pointed to a mirror that has not synced the latest package yet, use:
npx --registry=https://registry.npmjs.org @forgedesk/create-forge-app@latest my-app -- --template react
Generated projects retain template contract metadata in forge.toml so CLI diagnostics can detect incompatible or stale template outputs.
Security Policy
Forge now supports basic IPC command policy controls in forge.toml:
[security]
allowed_commands = ["greet", "version", "plugin_hello"]
denied_commands = ["delete"]
expose_command_introspection = false
allowed_origins = ["https://app.example.com"]
[security.window_scopes]
main = ["filesystem", "dialogs"]
settings = ["clipboard"]
This allows production apps to reduce exposed backend surface area instead of shipping every registered command to every frontend. Forge now also forwards frontend origin and window label metadata on every IPC call, so the bridge can reject untrusted origins and enforce per-window capability scopes.
Plugin Foundation
Forge now includes a Python plugin loading foundation:
[plugins]
enabled = true
modules = ["my_app_plugins.analytics"]
paths = ["plugins"]
Each plugin can expose a register(app) function and optional manifest metadata.
Desktop package outputs now also emit forge-plugins.json, which records discovered plugin manifests and version compatibility checks for release automation.
📦 Package Distribution
Forge now includes first-party package scaffolding for dual ecosystem distribution:
- packages/api/README.md —
@forgedesk/apityped JS bindings - packages/cli/README.md —
@forgedesk/cliNode wrapper around the Python CLI - packages/vite-plugin/README.md —
@forgedesk/vite-pluginfrontend integration for Vite - packages/create-forge-app/README.md —
@forgedesk/create-forge-appnpm scaffolder
This is the first step toward a Tauri-style install flow where frontend teams can start with npm while backend/runtime users can keep using pip.
Forge can now be prepared for publication to both registries:
- PyPI package:
forge-framework - npm packages:
@forgedesk/api,@forgedesk/cli,@forgedesk/vite-plugin, and@forgedesk/create-forge-app
Release quality is enforced in CI with version-alignment checks, release-branch gating, release-manifest verification, and installer smoke tests before publishing.
Release automation is defined in:
- .github/workflows/publish-python.yml
- .github/workflows/publish-npm.yml
- .github/workflows/release-matrix.yml
- .github/workflows/signing-validation.yml
- RELEASE.md
Development
cd my-app
forge dev
Notes:
forge devnow launches the app in a managed subprocess.- With hot reload enabled, Forge watches project files and restarts the Python app when backend or frontend source files change.
- If
[dev].dev_server_commandand[dev].dev_server_urlare configured, Forge starts the frontend dev server, waits for it to become ready, and points the native runtime at that URL. - Use
forge dev --no-watchto disable restart-on-change behavior.
Build
forge build
Environment Validation
forge doctor
forge doctor --output json
Package metadata and installer descriptors without a release manifest:
forge package --result-format json
Run signing and verification against an existing forge-package.json:
forge sign --result-format json
For CI or release automation, use machine-readable build results:
forge build --result-format json
To generate a release manifest with artifact digests for pipelines:
forge release --result-format json
Use forge doctor --output json before publishing to validate environment readiness, version alignment, and project health.
📖 Documentation
Project Structure
my-app/
├── forge.toml # App configuration
├── src/
│ ├── main.py # Python backend
│ └── frontend/ # Web frontend
│ ├── index.html
│ ├── main.js
│ └── style.css
├── assets/
│ └── icon.png
└── dist/ # Build output
forge.toml Configuration
[app]
name = "My App"
version = "1.0.0"
description = "My first Forge app"
authors = ["Your Name"]
[window]
title = "My App"
width = 1200
height = 800
resizable = true
fullscreen = false
[build]
entry = "src/main.py"
icon = "assets/icon.png"
output_dir = "dist"
single_binary = true
[dev]
frontend_dir = "src/frontend"
hot_reload = true
port = 5173
dev_server_command = "npm run dev"
dev_server_url = "http://127.0.0.1:5173"
dev_server_timeout = 20
[permissions]
filesystem = true
clipboard = true
dialogs = true
notifications = true
system_tray = false
updater = false
[protocol]
schemes = ["forge"]
[packaging]
app_id = "dev.forge.myapp"
product_name = "My App"
formats = ["dir", "appimage"]
category = "Utility"
[signing]
enabled = false
adapter = "auto"
identity = "Developer ID Application: Example Corp"
verify_command = "codesign --verify --deep"
notarize = false
notarize_command = "xcrun notarytool submit dist/MyApp.dmg --wait"
timestamp_url = "https://timestamp.example.com"
[updater]
enabled = false
endpoint = "https://updates.example.com/manifest.json"
channel = "stable"
check_on_startup = false
allow_downgrade = false
require_signature = true
staging_dir = ".forge-updater"
install_dir = "dist/current"
Exposing Python to JavaScript
# src/main.py
from forge import ForgeApp
app = ForgeApp()
@app.command
def greet(name: str) -> str:
return f"Hello, {name}! From Python 🐍"
@app.command
def get_system_info() -> dict:
import platform
return {
"os": platform.system(),
"python": platform.python_version(),
}
@app.command
async def read_file(path: str) -> str:
with open(path, "r") as f:
return f.read()
app.run()
Calling Python from JavaScript
import forge, { invoke, on } from "@forgedesk/api";
// Call a Python command
const result = await invoke("greet", { name: "Alice" });
console.log(result); // "Hello, Alice! From Python 🐍"
// Get system info
const info = await invoke("get_system_info");
console.log(info.os); // "Windows" / "Darwin" / "Linux"
// Use built-in APIs
await forge.clipboard.write("Hello, World!");
const text = await forge.clipboard.read();
// Application menu model
await forge.menu.set([
{
id: "file",
label: "File",
submenu: [
{ id: "file.open", label: "Open" },
{ type: "separator" },
{ id: "file.pin", label: "Pin", type: "checkbox", checked: true }
]
}
]);
await forge.menu.trigger("file.open");
// System tray model
await forge.tray.setMenu([
{ label: "Open", action: "open" },
{ separator: true },
{ label: "Pin", action: "pin", checkable: true, checked: true }
]);
await forge.tray.trigger("open", { source: "frontend" });
// Desktop notifications
await forge.notifications.notify("Forge", "Background sync complete", {
timeout: 4
});
// Deep link dispatch into the running app
await forge.deepLink.open("forge://notes/open?id=123");
// File operations
const content = await forge.fs.read("notes/my-note.txt");
await forge.fs.write("notes/my-note.txt", content);
// Dialogs
const path = await forge.dialog.open({
title: "Open File",
filters: [{ name: "Text", extensions: ["txt"] }]
});
// Window controls (native multiwindow on desktop, managed popup fallback on web)
const currentWindow = await forge.window.current();
const allWindows = await forge.window.list();
await forge.window.create({ label: "settings", route: "/settings", width: 900, height: 640 });
await forge.window.setTitle("Forge Notes");
await forge.window.setPosition(120, 80);
await forge.window.setSize(1280, 800);
await forge.window.setFullscreen(false);
await forge.window.focus();
const state = await forge.window.state();
console.log(state.width, state.height);
console.log(await forge.window.position());
console.log(await forge.window.isVisible());
// Updater metadata and manifest checks
const updaterConfig = await forge.updater.config();
const update = await forge.updater.check();
console.log(updaterConfig.channel, update.update_available);
// Signed updater flow
const verification = await forge.updater.verify();
if (verification.verified && update.update_available) {
const result = await forge.updater.update({
installDir: "dist/current"
});
console.log(result.apply.install_dir);
}
Event System
# Python emitting events
import time, threading
@app.command
def start_progress():
def run():
for i in range(101):
app.emit("progress_update", {"value": i})
time.sleep(0.05)
threading.Thread(target=run).start()
// JavaScript listening for events
on("progress_update", (data) => {
document.getElementById("progress").value = data.value;
});
on("window:resized", (data) => {
console.log("Window resized:", data.label, data.width, data.height);
});
🛠️ CLI Commands
| Command | Description |
|---|---|
forge create <name> |
Scaffold a new project |
forge dev |
Start development mode with hot reload |
forge build |
Build a production binary |
forge package |
Build desktop artifacts and emit package/install metadata |
forge sign |
Sign and verify an existing package manifest |
forge release |
Build and generate a release manifest |
forge info |
Display system and project info |
forge doctor |
Validate environment with remediation hints |
forge plugin-add <name> |
Install and register a plugin |
python -m forge |
Alternative CLI entry point |
CLI Diagnostics
-
forge info --output json- Machine-readable environment and project details -
forge doctor- Human-readable prerequisite validation with fix suggestions -
forge doctor --output json- CI-friendly doctor results with exit code0on success and1on blocking issues -
forge build --result-format json- CI-friendly build results including target, selected builder, produced artifacts, and validation errors -
forge package --result-format json- Package manifests, installer descriptors, and generated installer artifacts -
forge sign --result-format json- Signing, verification, and notarization status for an existing build output -
forge release --result-format json- End-to-end build plus release manifest generation -
forge doctorandforge infoalso surface plugin/security config through project payloads.
📦 Built-in APIs
File System (forge.fs)
read(path)- Read file contentswrite(path, content)- Write to fileexists(path)- Check if path existslist(path)- List directory contentsdelete(path)- Delete file/directorymkdir(path)- Create directory
Dialogs (forge.dialog)
open(options)- Open file dialogsave(options)- Save file dialogmessage(title, body, type)- Message dialog
Clipboard (forge.clipboard)
read()- Read clipboard textwrite(text)- Write to clipboard
System (forge.app)
version()- Get app versionplatform()- Get platform nameinfo()- Get system infoexit()- Exit application
Menu (app.menu / window.__forge__.menu)
- On Linux desktop builds, Forge now projects this model into a native menu bar and routes native selections back through
menu:select. set(items)- Replace the active menu treeget()- Read the current menu model snapshotclear()- Remove all menu itemsenable(id)/disable(id)- Toggle item enabled statecheck(id)/uncheck(id)- Toggle item checked statetrigger(id, payload?)- Emit a framework menu selection event
Tray (app.tray / window.__forge__.tray)
- Enable the
system_traypermission inforge.tomlbefore use. setIcon(path)/set_icon(path)- Configure the tray icon assetsetMenu(items)/set_menu(items)- Replace tray menu itemsshow()/hide()- Toggle tray visibilityisVisible()/is_visible()- Read visibility statestate()- Inspect tray backend and menu statetrigger(action, payload?)- Emit a framework tray selection event
Notifications (app.notifications / window.__forge__.notifications)
- Enable the
notificationspermission inforge.tomlbefore use. notify(title, body, ...)- Send a desktop notification with the best available backendstate()- Inspect notification backend availability and the last sent notificationhistory(limit?)- Read recent notification delivery metadata
Deep Links (app.deep_links / window.__forge__.deepLink)
- Configure
[protocol].schemesinforge.tomlfor accepted custom URL schemes. open(url)- Validate and dispatch a deep link into the running applicationstate()- Inspect configured schemes and recent deep link historyprotocols()- Read configured protocol handler schemes
Window (app.window / window.__forge__.window)
setTitle(title)/set_title(title)- Update the window titlesetPosition(x, y)/set_position(x, y)- Move the windowsetSize(width, height)/set_size(width, height)- Resize the windowsetFullscreen(enabled)/set_fullscreen(enabled)- Toggle fullscreenstate()- Read the cached window state snapshotposition()- Read the cached window positionisVisible()- Check visibility stateisFocused()- Check focus state
Runtime (app.runtime / window.__forge__.runtime)
health()- Lightweight runtime health snapshotdiagnostics()- Full diagnostics payload including updater, tray, notifications, and deep-link statecommands()- Registered command manifestprotocol()- Protocol compatibility detailsplugins()- Loaded plugin summary and manifest metadatasecurity()- Effective IPC command allow/deny policylastCrash()/last_crash()- Latest captured crash snapshot, if anystate()- Runtime navigation/devtools state snapshotlogs(limit)- Recent structured runtime logsnavigate(url)/reload()/back()/forward()- Native runtime navigation controlsopenDevtools()/closeDevtools()/toggleDevtools()- Native devtools controlsexportSupportBundle(destination)- Export a support bundle zip with diagnostics, logs, config, registry, and crash snapshot
Updater (app.updater / window.__forge__.updater)
currentVersion()/current_version()- Current app version used for update checkschannels()- Supported release channelsconfig()- Effective updater configuration snapshotmanifestSchema()/manifest_schema()- Release manifest schema descriptorgenerateManifest(options)/generate_manifest(...)- Generate updater manifest metadatacheck(manifestUrl?, currentVersion?)/check(...)- Evaluate a local or remote release manifestverify(manifestUrl?, publicKey?)/verify(...)- Verify an Ed25519-signed release manifestdownload(options)/download(...)- Download the selected artifact and verify its checksumapply(options)/apply(...)- Extract and apply a downloaded artifact with backup/rollback supportupdate(options)/update(...)- End-to-end check, verify, download, and apply flow
Updater notes:
- Sign manifests with Ed25519 and store the public key in
[updater].public_key. - Sign the canonical manifest JSON with
release.signatureomitted. - Artifacts are checksum-verified before apply.
- Archive applies create a backup and roll back automatically on failure.
Packaging/signing notes:
forge build --result-format jsonnow includes protocol-handler, packaging, and signing contract metadata.forge package --result-format jsonemitsforge-package.json,forge-protocols.json, andforge-plugins.jsonwithout requiring a release manifest.forge sign --result-format jsonconsumes an existingforge-package.jsonand runs sign/verify/notarize adapters independently from the build step.forge release --result-format jsonwritesforge-release.jsonwith artifact digests, package metadata, signing status, and notarization status.- Set
[packaging].app_idfor desktop builds that use custom protocols or signing. - Configure
[signing]metadata early; Forge surfaces contract warnings during build validation and supports default platform adapters whenidentityis set. - Desktop builds now emit
forge-package.jsonandforge-protocols.jsoninto the build output directory. - Desktop builds also emit format-specific installer descriptors based on
[packaging].formats. - Requested installer formats now fail hard when the required packaging toolchain is missing. Forge no longer emits placeholder
.dmg,.exe,.AppImage, or.flatpakfiles. - On Linux, desktop builds with
formats = ["deb"]now also emit a real.debinstaller artifact into the build output directory. - On macOS, desktop builds with
formats = ["app"]emit an.appbundle scaffold, andformats = ["dmg"]can produce a.dmgthroughhdiutil. - On Windows, desktop builds with
formats = ["msi"]emit a WiX source and build an.msiwhenwixlor WiX toolchain binaries are available, whileformats = ["nsis"]can produce an NSIS installer throughmakensis. - On Linux, desktop builds with
formats = ["appimage"]can produce an AppImage throughappimagetool, andformats = ["flatpak"]can produce a Flatpak bundle throughflatpak-builder+flatpak build-bundle. - On Linux, builds with configured custom protocols also emit a
.desktopregistration descriptor forx-scheme-handler/...integration. - When
[signing].sign_commandor[signing].verify_commandare configured, Forge runs them duringforge buildwithFORGE_BUILD_OUTPUT_DIR,FORGE_BUILD_ARTIFACTS, andFORGE_PACKAGE_MANIFESTexported. - Without custom commands, Forge uses default signing adapters where available: GPG on Linux,
codesignon macOS, andsigntoolon Windows.[signing].adaptercan force a concrete adapter. If[signing].notarizeis enabled, Forge can also runnotarize_commandorxcrun notarytoolon macOS. - Generated frontend templates now default to
@forgedesk/apiplus@forgedesk/vite-plugin, so modern frontend stacks can stay npm-first while still targeting the Forge runtime. - The repository now includes release-hardening workflows in .github/workflows/release-matrix.yml and .github/workflows/signing-validation.yml plus smoke/signing helpers under scripts/ci.
🎯 Examples
Hello Forge
A minimal demo app validating IPC functionality:
cd examples/hello_forge
forge dev
Forge Notes
A note-taking app demonstrating the file system API:
cd examples/forge_notes
forge dev
🔧 Development
Setting Up
# Clone the repository
git clone https://github.com/forge-framework/forge.git
cd forge
# Install in development mode
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black forge/
ruff check forge/
📄 License
MIT License — see LICENSE for details.
🙏 Acknowledgments
Forge is inspired by:
- Tauri — The Rust-based desktop framework
- pywebview — Python webview library
- Electron — Cross-platform desktop apps
Forge the future. Ship with Python. 🚀
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file forgedesk-2.0.2.tar.gz.
File metadata
- Download URL: forgedesk-2.0.2.tar.gz
- Upload date:
- Size: 615.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92b51ba0a05b1a8517da1fad8e8bb736d2161a5758b883a802e92531f27a3132
|
|
| MD5 |
4bea4f84f548dee73983db083d0f6ffe
|
|
| BLAKE2b-256 |
6e85e9830e9b7084d15cd9c07fe39228177d96cce23c92cff5ec769f14794f58
|
Provenance
The following attestation bundles were made for forgedesk-2.0.2.tar.gz:
Publisher:
publish-python.yml on swadhinbiswas/ForgeDesk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
forgedesk-2.0.2.tar.gz -
Subject digest:
92b51ba0a05b1a8517da1fad8e8bb736d2161a5758b883a802e92531f27a3132 - Sigstore transparency entry: 1270603480
- Sigstore integration time:
-
Permalink:
swadhinbiswas/ForgeDesk@aeb052da3e181a3400585c1756e4f1cfc23c8611 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/swadhinbiswas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@aeb052da3e181a3400585c1756e4f1cfc23c8611 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file forgedesk-2.0.2-cp314-cp314-manylinux_2_39_x86_64.whl.
File metadata
- Download URL: forgedesk-2.0.2-cp314-cp314-manylinux_2_39_x86_64.whl
- Upload date:
- Size: 81.4 MB
- Tags: CPython 3.14, manylinux: glibc 2.39+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09f7a9d48e9a0f3905ffda5f164e7f19643e6dd6bd141dfc0c0d611d0e8c2737
|
|
| MD5 |
8161e7d90f7a2c712585d78762206d93
|
|
| BLAKE2b-256 |
fc4bc584e024d627efc4b37bd19385daa71f795e97b44367ec36b8d82c29a305
|
Provenance
The following attestation bundles were made for forgedesk-2.0.2-cp314-cp314-manylinux_2_39_x86_64.whl:
Publisher:
publish-python.yml on swadhinbiswas/ForgeDesk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
forgedesk-2.0.2-cp314-cp314-manylinux_2_39_x86_64.whl -
Subject digest:
09f7a9d48e9a0f3905ffda5f164e7f19643e6dd6bd141dfc0c0d611d0e8c2737 - Sigstore transparency entry: 1270603511
- Sigstore integration time:
-
Permalink:
swadhinbiswas/ForgeDesk@aeb052da3e181a3400585c1756e4f1cfc23c8611 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/swadhinbiswas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@aeb052da3e181a3400585c1756e4f1cfc23c8611 -
Trigger Event:
workflow_dispatch
-
Statement type: