A native SwiftUI shell for Mac, built over a pinned Craft Anything engine. This is stage 1 of the plan in
../craft-anything-swift-review.md. The app starts our fork's craft-anything-cli mcp as a child process and
drives it the same way any MCP client would. No engine code is ported yet.
Tools/engine/pin-engine.sh # build the pinned engine into Engine/bin (needs the ../craft-anything clone at the pin)
swift build
.build/debug/CraftAnythingNative
Tools/make-app.sh # or: a development app bundle with all three programs, at .build/CraftAnythingNative.appswift test runs the engine-connection tests. When Engine/bin holds an engine, the tests also start it, list
its tools, call some of them, and check the approval round trip. They use temp folders for its settings and
projects.
To capture a window without the person's data, run with --screenshot <file.png> [--section new|tools|<project folder>].
That draws the window into a PNG and quits. The engine's settings, projects and inbox go to a fresh temp folder.
Pushing a v<version> tag that matches VERSION runs .github/workflows/release.yml, which runs
Tools/release.sh and publishes the GitHub Release. The release script:
- builds the pinned engine (from the private fork, through a read-only deploy key,
ENGINE_DEPLOY_KEY) - runs
swift test - bundles the app, the tool server, the WebKit plugin and the engine
- signs everything with Developer ID
- smoke-tests the bundle with the build folders hidden: the bundled engine serves the WebKit browser set through the bundled plugin, and the app draws its window
- notarizes and staples the app, then the DMG
- publishes the DMG, its
.sha256andDocs/releases/<version>.mdas the notes
The signing and notary secrets are organization secrets on xocialize-code, the same ones Gaea uses. Running the workflow by hand (Actions › release › Run workflow) builds, checks and notarizes any ref without publishing.
The script runs the same way on this Mac: Tools/release.sh signs with the keychain's Developer ID, notarizes with
the mlx-notary profile when it exists, and leaves everything in dist/. PUBLISH=yes also publishes. Its header
lists the other settings, such as SIGN_IDENTITY=- for an ad hoc test build.
-
CraftEngineKitis the engine connection:MCPClientis an actor that speaks newline-delimited JSON-RPC to the engine over stdio. Requests can overlap. Each request has a timeout and sendsnotifications/cancelledwhen it expires. The client drains stderr and keeps its last lines for error messages. If the engine stops, every waiting call fails with what it printed.Inboxmirrors the engine's inbox folder. When the engine needs consent for an action (a build, Play), it posts the request there and waits, and the app writes the answer back as a whole file.EngineToolandToolParameterread the tools' JSON schemas, andToolResultreads the engine's{"result"}/{"error"}replies.ToolServerstands in front of the engine for the builders it starts. Every message passes through unchanged, except calls to aNativeTool, which are answered in Swift under the engine's own tool id.MCPServerservesNativeTools on their own, as an engine plugin.
-
CraftWebKitis the engine's website browser set served with WebKit:web_site_open,web_site_capture,web_page_states_captureandweb_site_close, under the engine's ids and arguments. They run the engine's own page scripts (PageScripts.swift, generated from the pin byTools/engine/sync-scripts.sh) and write the same files:screenshot-<width>.png,noise-<width>.png,capture.json,README.md,page.html,states.jsonandstate-<n>.png. Clean room comes from the call's_meta, as the engine sends it. The page runs in a private (non-persistent) WKWebView with light appearance and classic scrollbars, in a window nobody sees, at one pixel per CSS pixel. WebKit's window-occlusion check is turned off, so the page believes it's on screen (that switch isn't public API; without it the window is on screen but fully transparent, and the page is flagged if it still thinks it's hidden). Load errors are read from inside the page, so a failed request shows only when the browser reports it there. -
craft-anything-native-toolsis the plugin the engine sends those tools to. In the engine'sconfig.toml(craft-anything-native-tools --print-configprints the entry with its own path):[[plugins.mcp]] id = "mac_app" command = "/…/CraftAnythingNative.app/Contents/MacOS/craft-anything-native-tools" replaces = ["web_site_open", "web_site_capture", "web_page_states_capture", "web_site_close"]
Every engine process reads it, so Study, the checks and the builders all picture websites with WebKit. Each project keeps the browser that pictured its original (
browserin itscraft.json, set by Study; the project screen shows it), and pictures of its build follow that record, so projects studied with Chromium stay on Chromium after WebKit is turned on. A project pictured with a plugin that's since been removed is refused, not switched; studying it again records the browser set up then. The app doesn't add the entry for you yet. Each call is logged to~/Library/Logs/CraftAnythingNative/native-tools.log.Tools/parity/webkit-parity.pycaptures the same pages both ways and reports the differences. -
craft-anything-tool-serveris the program builders get as their tool server. The app setsCRAFT_ANYTHING_TOOL_SERVER(a patch in our fork) andCRAFT_ANYTHING_ENGINEin the engine's environment.mcp …is served throughToolServer; anything else (builder-notes) runs the engine itself. Each call is logged to~/Library/Logs/CraftAnythingNative/tool-server.log. -
CraftAnythingNativeis the app:- New Build: the request, the original (none, a website, or an app or file), the stack, and the number of rounds.
- Projects: status, the conversation, questions to answer, checks, and the latest screenshot. From here you can send a note or a follow-up, stop the build, play the result, or show it in Finder.
- A tool browser with a form for each tool, built from its schema.
- A sheet for approvals posted to the app's inbox.
- Clean room or Deep: a switch in the toolbar, in Settings and in New Build. They all set the engine's saved
deep_mode, so every engine process it starts follows it. The engine lists 107 tools in Clean room and 321 in Deep, and doesn't announce the change, so the app lists the tools again after switching. Turning Deep on asks first. Settings shows whether Ghidra is installed and can get it (engines_install). When Clean room refuses something (details.reason = "clean_room"), the Tools form and the project screen offer to turn on Deep mode. - New Build's Game assets switch (
generate_assets), for game stacks and the builder's choice. - Settings › Game assets: who makes a game's pictures and which model. The choices are Gaea on this Mac (free and
keyless, the default) or one of fal.ai's models. Settings also shows whether Gaea is answering, lists the picture
models it offers (MES
GET /operations), and holds the fal.ai key that 3D models still need.Tools/gaea/picture-check.pymakes one picture through the pinned engine and a running Gaea, the way a builder does, in a throwaway project.
Tools/engine/pin.env names the engine commit this shell drives: the native branch of our fork (kept
private, at xocialize/craft-anything), based on upstream main at fc93bd0 (craft-anything 0.4.0). It adds:
- the SwiftUI stack
- the
CRAFT_ANYTHING_TOOL_SERVERsetting - plugins that
replacebuilt-in tools (with the call's_meta) - the website page scripts as files
- each project's record of the browser that pictured it
- Gaea as a keyless asset provider
- It's the default for game pictures, sent over MES on loopback.
[assets] gaea_urlandgaea_modelchange the address and the model. - 3D models stay on fal.ai.
- It's the default for game pictures, sent over MES on loopback.
We don't send changes upstream. To take upstream's changes, we rebase those commits onto upstream main and run
the engine's checks. After that, move the pin, run pin-engine.sh and run the tests again. Engine/bin/pin.txt records the binary's commit, version and sha256, and the toolbar shows that
commit.
This shell is MIT (see LICENSE). The engine it drives is storytold/craft-anything, MIT OR Apache-2.0. This
shell uses none of its brand assets. Sources/CraftWebKit/PageScripts.swift holds a copy of the engine's
website page scripts, under the engine's license, as its header says.
Port the pieces that are better native, behind the same tool ids, as NativeTools the engine sends to
craft-anything-native-tools:
- website capture (WKWebView): done, and each project keeps the browser that studied it
- window capture (ScreenCaptureKit)
- driving Claude Code, and the inbox
- project files, checked byte for byte against the pinned engine