跳转到主要内容

WRITING

Open-Sourcing Two Native macOS Apps: Ask Claude and HydroMac

July 14, 20266 min readTianli Zeng
Open SourcemacOSSwiftUIRustIndie Dev
Open-Sourcing Two Native macOS Apps: Ask Claude and HydroMac

My desktop runs a small fleet of native macOS apps I built for myself — one for asking AI, one for water-engineering calculations, one for document processing, one for watching positions. All small SwiftUI tools, all in daily use for a long time. This week I cleaned up two of them and open-sourced them: Ask Claude and HydroMac (水利工具箱), with a third, DocTools, along for the ride. This post covers what they are, the architecture they share, and the part of open-sourcing that actually took work — scrubbing the data.

Ask Claude: a native chat window for Claude subscribers

Repo: github.com/zengtianli/ask-claude

The pain point is specific: I have a Claude subscription (Pro/Max), and asking questions through the Claude Code CLI in a terminal works fine — but I always wanted a proper window at hand. Meanwhile almost every Claude client out there wants an API key, which is a separate, token-metered bill your subscription quota can't pay.

Ask Claude never touches the API: it spawns the claude binary you already have logged in (claude -p --output-format stream-json --include-partial-messages) as a subprocess and parses the streaming JSON line by line — session_id means "connected", the model name means "opus is thinking", each text_delta renders incrementally, and multi-turn memory rides on the CLI's own --resume mechanism. Which means:

  • Zero API key, zero extra billing. If claude runs in your terminal, this app runs — on the same subscription quota.
  • No chat history of its own. Sessions are managed entirely by the Claude Code CLI, exactly as if you were using the terminal.
  • Pure SwiftUI, one small binary. No Electron, no web view, no background daemon. The release zip is 209KB.

Requires macOS 15+ (Apple Silicon) and an installed, logged-in Claude Code CLI. Defaults to Opus; one defaults write switches the model.

HydroMac: a SwiftUI shell over a Rust compute core

Repo: github.com/zengtianli/hydro-mac

This one is my day job. Eight water-engineering calculators in one .app: annual water-resources report queries, river pollutant-carrying capacity (1-D decay model), water-efficiency assessment (AHP + CRITIC combined weighting), joint reservoir dispatch, plain-river multi-district water balance, daily irrigation demand, a full district rainfall pipeline, and geocoding with coordinate-system conversion.

Architecturally it's the same idea as Ask Claude in a second shape:

One shell, two tiers: the shell is always native SwiftUI; the backend is either an external CLI or an embedded Rust compute core
One shell, two backends: Ask Claude spawns the claude CLI already on your system; HydroMac ships its Rust compute core as a CLI embedded in the .app. Both talk to their backend the same way — spawn a process, pass JSON over stdin/stdout. There is not a single line of computation in the frontend.

The shell is always native SwiftUI (compiled straight with swiftc — there isn't even an xcodeproj); the compute core is pure Rust (hydro-cli), driven via Foundation.Process with a JSON envelope {ok, data, error} over stdin/stdout. The binary is embedded in .app/Contents/Resources, so the single .app is self-contained and fully offline — which matters in this industry, because field survey sites often have no network.

Numerical reliability is life-or-death for tools like this: the repo ships 20 golden numerical regression cases (any change to the calculation math must pass all of them) plus 64 cargo unit tests.

The real work of open-sourcing: scrubbing the data

Flipping a private repo to public takes one click — but doing that would have been an incident, not a release. HydroMac's sample data and golden cases came from real engineering work: real reservoir names, real hydrological districts, real companies' water-intake ledgers — present in git history since the very first commit. That leaves exactly one correct path: a fresh clean repo plus fully synthetic data.

The effort was far bigger than I estimated:

Sanitization pipeline: 550 entity strings inventoried, 381 mappings, all golden cases regenerated, adversarial verification caught 3 leaks, zero residue in the end
Sanitization is not find-and-replace: first a machine inventory of 550 unique entity strings, then 381 consistent mappings (compound place names derived morpheme by morpheme), then regenerating all 20 golden cases and verifying field by field that the math was untouched — and finally an independent adversarial review that cloned the public repo from scratch just to find fault. It found 3 leaks.

The most counterintuitive leak deserves its own paragraph: pinyin initials. The district output codes were literally the pinyin abbreviations of real place names — every Chinese name had been scrubbed, while the abbreviations sat untouched in constant tables, the parser, and the golden cases. In the same family: .pyc caches embedding the build machine's absolute paths (actually caught inside another app's release zip), regional abbreviations in CSV headers, names embedded in xlsx cells. The lesson in one sentence: sanitization must sweep the non-Chinese forms — pinyin abbreviations, coordinates, binary strings, cache files. None of them can be waved through on intuition.

How small can native apps be

All three apps are ad-hoc signed with no paid developer certificate, and the release zip sizes are the direct dividend of a native stack:

Release sizes on a log axis: Ask Claude 0.21MB, DocTools 0.43MB, HydroMac 2.7MB, versus 100MB+ for an empty Electron app
The gap on a log axis: the largest of the three, HydroMac, is 2.7MB including its entire Rust compute core — 1/37 of an empty Electron shell; the smallest, Ask Claude, is more than two orders of magnitude smaller. Small isn't showing off — faster download, faster launch, smaller memory footprint, and the user feels every one of them.

The third repo that came along is DocTools: a batch document toolbox (clean, convert, merge, split, preview), SwiftUI shell + Python engine, the same process-plus-JSON contract, 429KB release zip.

Take them

All three repos are MIT-licensed, with arm64 zips attached to the Releases page. Since they're ad-hoc signed, clear the quarantine flag once before first launch:

xattr -cr "/Applications/Ask Claude.app"

or right-click → Open, then allow it under System Settings → Privacy & Security. If you'd rather not trust prebuilt binaries, each repo builds from source with a single ./build.sh.

Issues and PRs welcome.