Build native desktop apps that a person, your program and an agent can all drive.
JavaScript · Python · Shell · any language over a pipemacOS · Windows · Linux
Every control has a name, and it is the same name for all four: what a screen reader reads out, what your script writes to, what a pipe asks for, what an agent presses.
294 KBthe whole framework, macOS
git clone https://github.com/eddime/spark-native
cd spark-native && ./spark new myapp
./spark dev myapp/index.html
Addressable
means this.
Every control has a name, and that name is the same one for all four of them. Not four APIs bolted together, one tree, reached four ways.
One name. Four callers.
Which means the same line, whatever you write it in, no bindings, no FFI, nothing generated.
out.textContent = "Hello, " + who.value;
app["out"].text("Hello, " + app["who"].value)
spark_set out textContent "Hello, $(spark_get who value)"
And
the name is what gets checked: spark cross asks all three systems
what a keyboard can reach, by name. The first run found twenty-five of
twenty-six Windows controls unreachable without a mouse.
Which makes the fourth caller free.
$ spark mcp notes.html
spark: 5 tool(s) from Notes — press_save, fill_what, read_screen …
The buttons are the tools, under the names you gave them. Nothing to write: the app already answers every question MCP asks.
What that buys you → Your window as an MCP server → The second question →
One file.
Three toolkits.
The same markup becomes a real NSButton, a real
Win32 button and a real GtkButton.
Measured once, in shared C, and only then handed to each system to build, so none of the three does the arithmetic, and none of them can get it different.



swipe →
Every screenshot came out of
spark cross, which asks each system whether every widget landed
where the layout put it.
The machine already
has all of it.
Your computer has shipped a window system, a widget set, a text engine and a JavaScript engine for thirty years. It draws every other app you own.
Then a desktop app arrives carrying its own, a second browser, a second engine, eighty-five megabytes of them, to draw a button the machine could already draw. And once it is yours, so is keeping it patched.
- a browser engine
- a JavaScript runtime
- a renderer, a font stack, a compositor
- every security fix in all of them, forever
- a parser
- a layout engine
- three thin hosts, one per system
- that is the entire list
Spark asks the machine instead.
What it weighs.
0.35 MBone file, nothing beside it
146 mswindow up, scripts run
20 MBphysical footprint
Measured on this machine, and none of it is cleverness: there is no renderer and no engine to carry, because the widgets and the JavaScript are already on the system. Against what, and how →
Is this for you?
- a small tool that must stay small
- the same look on three systems, checked rather than hoped for
- an app whose every control must be reachable by name, and checked that way rather than hoped about
- a window in front of a CLI, a build or a model you already have
- something a test or an agent has to drive
- you want the web platform, CSS, arbitrary HTML, anything off npm
- heavy custom rendering: a video editor, a 3D scene
- your design is a web design, pixel for pixel
- you need mobile, or a screen reader that can operate the app: names and events are published on all three, values and field contents are not yet
Not on either list: needing a widget Spark does not have. That is a shared library and one function, a plugin, not a fork.