This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
SwiftStanServer is a non-sandboxed macOS app that hosts an HTTP server exposing SwiftStan commands over an OpenAPI interface. It is the execution tier: it owns cmdstan and the ~/Documents/StanCases filesystem, and clients (notably SwiftStanApp, and potentially SwiftStats) talk to it over HTTP. The HTTP layer is generated by Apple's Swift OpenAPI Generator with a Hummingbird transport. macOS 26+; Swift 6.
Three independent projects, coupled only by contracts:
SwiftStanLibrary— the distributable library athttps://github.com/SwiftProjectOrganization/SwiftStanLibrary. This is what the server depends on.SwiftStanServer(this) — linksSwiftStanLibraryand serves its public commands.SwiftStanApp(client) — has no build dependency on this project; talks to the server purely over HTTP, generating its client from a copy ofopenapi.yaml.
The full design rationale lives in ../SwiftStan/Docs/SwiftStanServer-Plan.md.
The repo includes the .xcodeproj with all source files, the OpenAPI spec, entitlements, and SPM dependencies already wired.
- Clone
https://github.com/SwiftProjectOrganization/SwiftStanServerand openSwiftStanServer.xcodeprojin Xcode. - Trust the build plugin when prompted (OpenAPIGenerator). The first build generates
APIProtocol/Typesfromopenapi.yaml. - Set your cmdstan path in the GUI or via the
$CMDSTANenvironment variable. - Build and run.
| Package | Product | Purpose |
|---|---|---|
https://github.com/SwiftProjectOrganization/SwiftStanLibrary |
SwiftStan |
Core Stan library |
https://github.com/apple/swift-openapi-generator ≥ 1.6.0 |
OpenAPIGenerator plugin |
Code generation at build time |
https://github.com/apple/swift-openapi-runtime ≥ 1.8.0 |
OpenAPIRuntime |
Runtime types |
https://github.com/hummingbird-project/swift-openapi-hummingbird ≥ 2.0.1 |
OpenAPIHummingbird |
Transport adapter |
https://github.com/hummingbird-project/hummingbird ≥ 2.5.0 |
Hummingbird |
HTTP server |
OpenAPI/openapi.yaml is the canonical spec (SwiftStanApp keeps a byte-identical copy). 14 POST operations plus GET /v1/health. dsl2stan is not included — SwiftStanLibrary dropped it because it requires recompiling the package source tree via swiftc (not viable in a distributed library; the in-process stancode covers alist→Stan without swiftc).
Every command returns 200 with a CommandResult { status, error, outputPath? }:
status— the human-readable status line (library tuple.0, or"Wrote <file>"for file ops).error— library tuple.1;""on success. Non-empty = logical failure at200. Real4xx/5xxreserved for transport faults.outputPath— set by file-translation ops; omitted for cmdstan ops.
Every request accepts an optional stanCases field (e.g. "StanCases", "SR2Cases", "ARM/Chapter3"). The server resolves it by appending to ~/Documents, with a path-traversal guard. When absent, falls back to $STAN_CASES or ~/Documents/StanCases. The resolved URL is passed directly to the library's caseRoot: parameter.
- Model listing (
models) → lists subdirectories of the resolved StanCases root. Request carries optionalstanCases. - cmdstan-backed (
compile, sample, optimize, pathfinder, laplace, generated_quantities, stansummary, ulam) → call library funcs returning(String, String). Requests carrymodel,arguments(verbatim cmdstankey=value), optionalcmdstanoverride,stanCases,verbose, plus per-op flags (compile: install/force;sample: install/nosummary + structured sampling params;ulam: force). - pure-Swift file translation (
csv2json, alist2dsl, stancode, stan2alist, runinfo) → call library funcs thatthrow/returnURL. Requests carrymodel(required),stanCases,verbose(+forceforstan2alist).
sample accepts first-class fields that are translated to cmdstan tokens by sampleTokens() before being merged with arguments: num_samples, num_warmup, thin, adapt_delta, max_treedepth, num_chains, seed. These map to the cmdstan hierarchical parser's expected key=value tokens under the sample subcommand.
StanAPIHandler.swift—struct StanAPIHandler: APIProtocol; one method per operation forwarding to the matchingSwiftStanfunction. Synchronous library calls (blockingProcess.run()) are wrapped inTask.detachedviaoffload(_:)so the event loop isn't starved.ServerController.swift—@Observable @MainActorclass owning the HummingbirdApplicationlifecycle (start()/stop()), binding to0.0.0.0:<port>(all interfaces, reachable over the local network).ServerSettings.swift— cmdstan path (UserDefaults"cmdstanPath"→$CMDSTAN→ hardcoded default), port (UserDefaults"serverPort", default 8080), StanCases root, and per-requestresolveStanCasesRoot(_:).RequestLog.swift—@Observable @MainActorbounded log (max 200 entries) of requests handled byStanAPIHandler; owned byServerController, read byContentView.SwiftStanServerApp.swift—@mainApp; starts the server on appear.ContentView.swift— Liquid Glass GUI: running indicator with hostname URL, port, cmdstan path, Start/Stop, and a scrollable recent-requests log.
Calls are synchronous (one blocking HTTP request per command — localhost, generous timeouts). cmdstan sampling/compiling can take minutes; the server keeps the response open until the library returns. If timeouts bite, raise Hummingbird's response/idle timeout in ServerController.
- macOS 26+; Swift 6. Non-sandboxed (shells out to cmdstan, writes
~/Documents/StanCases). Developer-ID / direct distribution. - Depends on
SwiftStanLibrary(https://github.com/SwiftProjectOrganization/SwiftStanLibrary). Do not add a dependency on the originalSwiftStanpackage (the one with the CLI) or onSwiftStanApp. - Required server-side env:
CMDSTAN(or set in the GUI / UserDefaults).STAN_CASESoptional (defaults to~/Documents/StanCases).SWIFTSTAN_PROJECT_ROOTis no longer needed (dsl2stanis not in the API). - Keep
openapi.yamlbyte-identical to the copy in SwiftStanApp (canonical spec, copied). - The server binds to
0.0.0.0(all interfaces). Clients on the same LAN can reach it — not just localhost.
- Build in Xcode (after first-time setup). Confirm the plugin emitted
APIProtocol/TypesandStanAPIHandlercompiles. - Run; the GUI shows "Running on http://<hostname>:8080".
curl http://127.0.0.1:8080/v1/health→ JSON with resolved cmdstan + StanCases paths. curl -XPOST http://127.0.0.1:8080/v1/stancode -H 'Content-Type: application/json' -d '{"model":"bernoulli"}'→{"status":"Wrote bernoulli.stan","error":"","outputPath":"..."}.curl -XPOST .../v1/compile -d '{"model":"bernoulli"}'then.../v1/sample→error:""andbernoulli.samples.csvappears under~/Documents/StanCases/bernoulli/Results/.curl -XPOST http://127.0.0.1:8080/v1/models -H 'Content-Type: application/json' -d '{}'→{"models":["bernoulli",...],"root":"...","error":""}.