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.
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 8c3532d. It adds the SwiftUI stack, the
CRAFT_ANYTHING_TOOL_SERVER setting, plugins that replace built-in tools (with the call's _meta), and the
website page scripts as files. We don't send changes upstream. Instead, we review what upstream adds
since the base and cherry-pick what's worth having. 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