A self-contained developer / scientific / AI workstation for iPad and Mac. Python 3.14, Jupyter notebooks, C, C++, Fortran, pdflatex, and local LLMs — all running on-device, no internet required.
iPad / iPadOS / Mac Catalyst
┌──────────────────────────────────────────────────────────┐
│ │
│ Monaco editor ─── IntelliSense + signature help │
│ ▲ ▲ │
│ │ │ │
│ │ ┌─────┴───────┐ │
│ │ │ vim mode │ ⌘P ⇧⌘, │
│ │ └─────────────┘ │
│ ▼ │
│ Notebook editor ─── per-cell Run + persistent kernel │
│ │ │
│ ▼ │
│ Visual debugger ─── step / inspect / call stack │
│ │ │
│ ▼ │
│ Python / C / C++ / Fortran / pdflatex │
│ │ │
│ ▼ │
│ SwiftTerm terminal ◄──► PTY ◄──► CPython REPL │
│ │ │
│ ▼ │
│ Local LLM chat + RAG + image gen │
│ (bundled GGUF · OpenAI · Anthropic · Ollama remote) │
│ │
└──────────────────────────────────────────────────────────┘
Built on python-ios-lib — the Python 3.14 runtime + 30+ native iOS Python libraries project. Every library listed below has its own reference documentation in that repo; links go directly to each library's main doc.
The in-editor AI assistant now runs on Apple's on-device foundation models (AFM 3, iOS 26+ — private, free, zero downloads) or any downloaded GGUF, and finally behaves like a real chat:
- Model pill — one picker for Apple Intelligence (on-device), Apple Cloud Pro (iOS 27), the built-in GGUF catalog (Qwen 3.5 / Gemma 4 / Llama 3.2 / …), any
.ggufon disk, or the Models tab. Selecting Apple also drives the terminalaiREPL. - Mode pill — Coding vs Chat — Coding sees the open file and answers with runnable patches (the classic behavior). Chat is a plain conversation on a selectable system-prompt persona (Default / Coder / Tutor / Writer / Translator / Analyst / Creative / Concise, or a custom prompt you write) — your editor code is never sent in Chat mode.
- Conversation memory — every turn is remembered and replayed within the model's context budget, so "make it shorter" / "why did that fail?" work. Auto-run corrections replace the failed attempt in memory, so the assistant remembers the code that worked. A ✎ New-chat button wipes transcript + memory.
- UI polish — adaptive panel width (280–360 pt), a compact ⚡ auto-run toggle, and a welcome card with tappable mode-aware starter prompts instead of a blank panel. Long-press the AI Assist chip for a quick model/mode menu without opening the panel.
The Libraries tab now opens with an interactive ring chart of the entire app's disk footprint — every library ≥ 10 MB is its own tappable slice (bpy 425 MB, LaTeX 277 MB, torch 123 MB, …down to faiss 10 MB), with Python runtime, the LaTeX distribution, offline wheels, and app core as honest buckets so the total matches the real install size. Tap a slice to see its share and filter the list to it.
Every package card also shows its true on-disk size (native frameworks correctly attributed — torch counts its 99 MB libtorch_python, bpy its 66 MB USD) plus an orange NATIVE badge on cross-compiled packages; the header reads "N packages · X GB · M native"; and a sort menu offers Largest first. Package detail pages show "N MB on disk".
Two new shell commands measure the device live (nothing is a lookup table):
cpu-z— single-core (Python loop, zlib, memory copy) and multi-core (zlib across all cores) scores on the official CPU-Z bench scale (M1 ≈ 580 single / 3400 multi), plus AMX matmul GFLOPS and SHA-256 as info lines. Directly comparable with Windows/Linux CPU-Z screenshots.gpu-z— Metal device info straight fromMTLCreateSystemDefaultDevice(family, Metal level, unified-memory budget) + fp16/fp32 matmul GFLOPS through the torch Metal bridge, scored on the Geekbench 6 Compute scale (M1 ≈ 32k; an M4 posts ~53–56k).-vshows bridge diagnostics.
Fixing gpu-z's initial 0-GFLOPS reading also uncovered and fixed a real bug: the torch→numpy conversion layer was ~20,000× too slow, silently forcing the entire torch Metal bridge onto the CPU. With the fix, torchmetal.enable() genuinely GPU-accelerates torch.matmul (M4: 1400+ GFLOPS fp16) for all user code — see python-ios-lib for that side (which also gained parquet-enabled pyarrow and a Metal-backed moderngl).
Every number is measured on your device at the moment you run the command — there is no spec-sheet lookup, no model-name table. The scores are then mapped onto two well-known external scales so you can compare across platforms. They are labeled est. because we cannot run CPUID's or Geekbench's proprietary workloads; instead our own workloads are calibrated against published Apple-silicon anchor scores, and validated against real results (an iPad M4 maps to ≈ 820 single on our scale; official CPU-Z posts the M4 at ≈ 800–830).
cpu-z — workloads
| # | Benchmark | What it measures | How | One-M1-core reference |
|---|---|---|---|---|
| 1 | Python loop | interpreter + integer ALU, one core | 3,000,000 iterations of acc += i*3 + (i>>2) ^ (i&7) → Mops/s |
9.0 Mops/s |
| 2 | zlib compress (1 thread) | C-speed CPU throughput, build-independent | 4 MB high-entropy blob (random.Random(0xC0DE).randbytes, deterministic so every device compresses identical data), level 6 → MB/s |
50 MB/s |
| 3 | Memory copy (1 thread) | memory subsystem | 64 MB numpy buffer copied 4×, read+write bytes counted → GB/s | 13 GB/s |
| 4 | zlib × N threads | true multi-core scaling | same blob compressed concurrently in cpu_count threads — zlib releases the GIL, so N threads genuinely occupy N cores → aggregate MB/s |
(scored against the same 50 MB/s single-core ref) |
cpu-z — formulas
single_ratio = mean( python/9.0 , zlib_1T/50 , copy_1T/13 ) # vs ONE M1 core
single_score = 580 × single_ratio # CPU-Z v17: M1 ≈ 580
multi_ratio = zlib_NT / 50 # aggregate vs one M1 core
multi_score = 680 × multi_ratio # calibrated: M1 (8-core, ≈5× scaling) ≈ 3400
Both scores share one scale (everything is normalized to a single M1 core), which is why multi reads ~3–6× single on a healthy chip — the same relationship Geekbench and CPU-Z show. Multi is scored from the zlib run alone because CPU-Z's multi test scales near-linearly with cores; mixing in bus-bound work would hide core count.
cpu-z — shown but deliberately NOT scored
- SHA-256 — on Apple silicon this is fixed-function crypto: it measures whether the Python build links a hardware-accelerated hashlib (≈ 3,000 MB/s) or falls back to software (≈ 500 MB/s) — a 6–7× swing that says nothing about the CPU. The line is labeled
HW crypto/software pathinstead. - Matmul fp32 (AMX) — Apple's matrix coprocessor via Accelerate. It is neither "a core" nor "all cores", so it gets an info line rather than skewing either score.
- Memory copy × 4 threads — the memory bus is shared; parallel copies don't scale with cores, so including it would drag the multi score down arbitrarily.
gpu-z — workloads and formula
The info block is read directly from Metal (MTLCreateSystemDefaultDevice via ctypes: device name, MTLGPUFamily, Metal version, unified memory, recommendedMaxWorkingSetSize). The benchmark then runs real torch.matmul calls routed to the GPU by the torch Metal bridge (MPSMatrixMultiplication underneath):
- Probe-grow sizing — square matmuls at k = 512 → 1024 → 2048; growth stops early if a single call exceeds 0.35 s, then up to 4 repetitions inside a 0.6 s budget. GFLOPS = 2k³·reps / elapsed.
- Every call is materialized — one element of the result is read back after each matmul, so the timing includes the CPU↔GPU copies both ways. This is intentional: the score reflects what Python code actually obtains, not a marketing TFLOPS figure (an M4 posts ≈ 950 GFLOPS fp32 here against 4.4 TFLOPS theoretical — the copies are the tax).
gpu_ratio = mean( fp16_GFLOPS/800 , fp32_GFLOPS/500 ) # refs = measured M1-class MPS incl. copies
gpu_score = 34000 × gpu_ratio # Geekbench 6 Compute: M1 ≈ 32k
Calibration check: a measured M4 ratio of ≈ 1.6 maps to ≈ 53–56k — real Geekbench 6 Metal posts the M4 at ≈ 57k (within ~5 %). Attention (SDPA) is displayed as an info line but not scored: the bridge executes it as a matmul → softmax → matmul chain with a copy at every hop, so its throughput measures bridge dispatch overhead rather than GPU speed. TechPowerUp's GPU-Z has no benchmark at all (it is an info/sensor tool), which is why the cross-platform anchor is Geekbench 6 Compute — the number Windows/Linux GPUs are compared with (OpenCL/Vulkan there, Metal on Apple).
Run-to-run variance of ±10 % is normal for both commands (thermals, background load); cpu-z -q runs a shorter, noisier pass, and gpu-z -i prints the info block without loading torch.
The in-editor AI chat was rebuilt around a streaming, conversational UI (open it from the editor toolbar):
- Collapsible "thinking" — a reasoning model's
<think>output folds into a pill you can expand; the final answer renders beneath it. - Compiled LaTeX / math —
$…$and$$…$$in replies render as native vector glyphs via SwiftMath, inline in the chat bubble (no web view). - Code cards — fenced code blocks become titled cards with copy + an inline Run button.
- File attachments — a 📎 in the composer attaches PDFs / text / source files (shown as chips above the input) for the model to read.
- Run Python from chat — with Auto-run AI code enabled, Python the assistant emits is executed automatically and its output (text, charts, tables) flows straight into the preview pane.
Autocomplete now indexes every importable module (not a hardcoded shortlist) and lazily fetches each module's members / attributes on demand from the live Python daemon — so typing a module name and . lists its real API surface, with kind-aware icons and signature help.
Tap any .ipynb file in the file browser → opens directly in the editor box as a cell-stacked notebook (not raw JSON in Monaco). Each cell has its own toolbar (Run ▶, move ↑/↓, insert +, delete 🗑, code↔markdown toggle 🅼/ƒ). Per-cell Run uses a persistent in-process Python kernel so variables defined in cell 1 are reachable from cell 2.
- Output capture per cell: stdout, stderr, last-expression result, matplotlib PNG (auto-savefig), HTML (pandas DataFrames, plotly), red error tracebacks
- Markdown cells render with GFM + KaTeX inline math, tap to switch to edit mode
- Toolbar at top: ▶ Run All, ↻ Restart Kernel, 💾 Save (writes back to
.ipynbJSON in nbformat v4) - No modal popup — the notebook lives inside
editorContainer, replacing Monaco for the duration of.ipynbediting. Opening a.pyswaps Monaco back inline.
debug-gui script.py in the terminal → a floating toolbar appears at the top of the editor with:
- ▶ Continue · ⏭ Step Over · ⤓ Step Into · ⤴ Step Out · ⏹ Stop
- 🔍 Variable inspector panel (slides in from right) — live
f_locals+f_globalstree, repr-clipped to 200 chars per value - Call stack — top 3 frames shown beneath the variables panel
- Current-line arrow in Monaco's gutter (golden, auto-scrolls into view)
- Persisted breakpoints (
~/.codebench/breakpoints/<file>.bps) honoured at launch - Headless fallback — if the UI isn't compiled in (older build), the debugger auto-continues after 3 s so scripts don't hang
The AI subsystem can now dispatch to any of four backends, switched per-request or per-session:
| Provider | Configure | Notes |
|---|---|---|
| Bundled GGUF | Models tab → tap a .gguf |
Default, runs through llama.cpp |
| OpenAI | ⇧⌘, → Settings → paste API key |
Streams chat/completions SSE |
| Anthropic | ⇧⌘, → Settings → paste API key |
Streams Messages API with system separation |
| OpenAI-compat | ⇧⌘, → set base URL (e.g. http://192.168.1.10:11434/v1) |
Ollama / vLLM / llama.cpp server |
API keys are stored in Keychain (kSecAttrAccessibleWhenUnlockedThisDeviceOnly). Ctrl-C in the terminal cancels in-flight HTTP requests + local generations symmetrically.
plt.show() / display(df) / Image.show() / fig.show() from any script — Python or notebook cell — renders inline in the output panel with append semantics. No more "saves to disk, you have to open the file." Backed by a Python-side codebench_inline module that monkey-patches the show/display hooks and ships PNG / HTML artifacts to Swift via inline_*.json signal files.
Toggle in ⇧⌘, settings. Implements a ~80% subset of vim:
- Modes: NORMAL · INSERT · VISUAL · V-LINE (status pill bottom-right)
- Motion:
h j k l·w b e·0 $ ^·gg G·{ } % - Insert:
i a I A o O - Edit:
x X r·dd yy cc·d{motion} y{motion} c{motion}·p P·u <C-r>·J·.(last-change repeat) - Search:
/ ? n N - Visual:
v Vthend y c x - Cmdline:
:w :q :wq :x(:wtriggers Monaco save) - Counts:
3dd,5j,10G, etc.
Persists via localStorage.setItem('codebench.vim.enabled', '1') so it survives editor reloads.
⌘P— VS Code-style fuzzy file picker. Recent files at the top with "recent · " subtitle, full workspace walk underneath (capped at 5 000 files). Arrow keys + Enter to open.⇧⌘,— unified settings sheet: AI provider, API key, model name, temperature, max_tokens, vim mode toggle, inline-completion toggle.
Right-click / long-press any file in the file browser → "Quick Look" appears at the top of the context menu for:
.csv/.tsv→ scrollable HTML grid (sticky header, row numbers, alt-row striping; capped at 5 000 × 100 cells).json→ pretty-printed + regex syntax highlighting.yaml/.yml/.toml→ monospace pretty-print.png/.jpg/.heic/.webp/.bmp/.tiff/.gif→ pinch-zoomable image with dimensions caption.npy/.npz→ numpy table view (loaded in-process via the bundled Python; 1-D up to 1000 rows, 2-D up to 500×50)
A persistent-namespace Python REPL — variables survive across invocations. Rich displayhook for pandas DataFrames (HTML table inline). Meta-commands :q :clear :show :save FILE. Use repl --keep to preserve the previous session's namespace.
Idle wake-ups dropped from ~117/sec to ~1.5/sec by moving three always-on timers to kqueue file-system event sources (DispatchSourceFileSystemObject) with a 2 s safety-net fallback:
| Engine | Old polling | New idle | Speedup |
|---|---|---|---|
PywebviewBridge |
10 ms (100 Hz) | event-driven | 200× |
LaTeXEngine |
100 ms | event-driven | 20× |
AIEngine |
150 ms | event-driven | 13× |
Plus: IntelliSenseEngine 3 s blocking-loop → DispatchSource event watch (no held threads), MonacoEditorView.loadFileBatched collapses 3 JS round trips into 1 (~100 ms saved per file open), C/C++/Fortran Monaco providers lazy-register only on first non-Python file (saves ~210 symbol parses on pure-Python sessions). BackgroundExecutionGuard .sync → .async to remove a Swift-Concurrency deadlock vector.
On every launch, three test files auto-seed into your Workspace (respecting the .codebench_deleted tombstone so deletes stick):
codebench_features_test.py— automated 24-test feature suite for everything abovecodebench_features_test.ipynb— sample notebook exercising every output typecodebench_debug_target.py— small target to step through underdebug-gui
User-edited copies are detected by file-head SHA comparison and never overwritten on subsequent launches.
-
Reliable inline preview for shell-launched python —
python foo.pyfrom the integrated terminal now displays the resulting matplotlib/plotly chart in the preview pane the same way the Run button does. Previously flaky for several reasons, all fixed:- PTY scanner missed
[plot saved] /…/[manim rendered] /…markers prefixed with ANSI escape codes (CSI clear-line, prompt redraws). ThehasPrefixcheck is now preceded bystripAnsiEscapesand also handles the[manim rendered]marker. WKWebView.loadFileURL(allowingReadAccessTo:)flaked on macOS Catalyst — the access grant to the sandboxedWebContentprocess raced the actual load (WebProcessProxy::hasAssumedReadAccessToURL: no access). The HTML branch ofshowImageOutputnow reads the file (up to 20 MB) and usesloadHTMLString(html, baseURL: parentDir), sidestepping the sandbox grant entirely.- A blank-HTML preload meant to drop a previous manim video DOM was causing back-to-back async loads for HTML→HTML transitions; the blank's cancellation (
NSURLErrorCancelled -999) intermittently propagated into the chart load. Skipped when the next content is also HTML — the new HTML replaces the DOM atomically. - The dir-watcher's
DispatchSource.makeFileSystemObjectSource(.write)fires on inode create (file size 0) but doesn't re-fire on subsequent content writes into an existing entry. Heavy plotly HTML can take 60–80 s to serialize on iOS Python. A newpollForChartCompletionpolls every 0.5 s for up to 120 s once a too-small file is seen, then routes throughtryShowChartwhensize ≥ 4 KB. flush=Trueadded to the embedded_offlinai_*_showprint sites so the marker line definitely reaches the PTY.- Diagnostic
NSLoglines added across the chain: filter Xcode console on[chart-watch]to trace dir-event → poll → load.
- PTY scanner missed
-
Inherited from python-ios-lib: matplotlib shim no longer crashes user scripts on chained attribute access (
ax.xaxis.line.set_color(...)etc.), and full plotly styling — titles, axis ranges, backgrounds — now applies correctly (was being silently aborted by a__figure__sentinel leak). See python-ios-lib's recent changes.
| Capability | How |
|---|---|
| Monaco code editor (real VS-Code editor) | WKWebView-hosted Python IntelliSense — indexes every importable module with lazy member/attribute completion, ~70-entry signature DB, hover docs, auto-resolve from a Python daemon for numpy / scipy / sklearn / matplotlib / sympy. Vim mode (NORMAL/INSERT/VISUAL/V-LINE), inline AI completion (ghost text, 350 ms debounced) |
| Jupyter notebook editor | Cell-stacked .ipynb editor embedded inline in the editor box. Per-cell Run + persistent in-process kernel, output capture (stdout/stderr/PNG/HTML/error), markdown cells with GFM + KaTeX, save back to nbformat v4 |
| Visual debugger | Floating toolbar (Continue / Step Over / Step Into / Step Out / Stop) + variable inspector + call stack + golden current-line arrow in gutter. Driven by a Pdb subclass over signal files. Persisted breakpoints |
| Integrated terminal | SwiftTerm backed by a PTY master/slave pair piping into the embedded CPython REPL |
| pdflatex on-device | busytex WASM (pdftex 1.40.25 + xetex + luatex + bibtex8 + xdvipdfmx) running in a hidden WKWebView with TeX Live 2023 packages preloaded into MEMFS. A custom 23 MB overlay adds pgf / tikz / beamer / hyperref / mathtools / microtype / cleveref / fancyhdr / bbm / CJKutf8 / fontspec / ctex and ls-R index |
| Local + remote LLM chat | llama.cpp for GGUF models + ExecuTorch + OpenAI / Anthropic / OpenAI-compat (Ollama, vLLM) HTTP streaming. ChatGPT-style UI: streaming, collapsible thinking, compiled LaTeX, code cards, 📎 file attachments, optional auto-run of AI-generated Python into the preview. Conversation export. API keys in Keychain. Provider switchable via ⇧⌘, |
| RAG engine | In-process vector store for RAG over user-imported docs |
| Image generation | Offline image models via ExecuTorch |
| Inline rich output | plt.show() / display(df) / Image.show() etc. render in the output panel with append semantics — figures, DataFrames, plotly all flow through |
Quick Open (⌘P) |
VS Code-style fuzzy file picker (recent + workspace walk) |
| Quick Look (long-press file) | Inline grid view for CSV/JSON/YAML/TOML/NPY/PNG/JPG without opening Monaco |
| File browser + tabs | iOS document browser with multiple concurrent workspaces |
| Auto-save | Debounced ~600 ms after keystroke, plus on run / tab-switch / view-disappear / app-backgrounding |
| Tombstone system | Files deleted via UI are recorded in <Workspace>/.codebench_deleted so starter-script seeders don't resurrect them on next launch |
| Auto-seeded test bundle | Workspace/Tests/ gets 3 test files on launch (respects tombstone + user-edit detection) |
| Language | Runtime | Main doc |
|---|---|---|
| Python 3.14 | BeeWare-embedded CPython | python-ios-lib README |
Jupyter .ipynb |
Same CPython, cell-stacked editor with persistent in-process kernel | (built-in; tap any .ipynb in the file browser) |
| C | Pure-Swift tree-walking interpreter (3.4k LOC, 48 operators, structs, pointers, preprocessor) | c-interpreter.md · interpreters.md |
| C++ | Pure-Swift tree-walking interpreter (4.2k LOC, classes, STL, templates, inheritance) | cpp-interpreter.md · interpreters.md |
| Fortran | ofort tree-walking interpreter in C (Beliavsky, MIT) via a Swift bridge — modules, allocatable arrays, 45+ intrinsics | fortran-interpreter.md · fortran-runtime.md |
All four languages share the same Monaco editor + IntelliSense pipeline and auto-save. Python additionally has the dedicated notebook editor for .ipynb files (cell-stacked UI in place of Monaco).
Every library below is bundled natively on-device. Click the library name to jump to its reference doc in python-ios-lib.
| Library | Type | Doc |
|---|---|---|
| NumPy 2.3.5 | Native iOS (arm64) | docs/libs/numpy.md · docs/numpy.md |
| SciPy 1.15.0 | Pure Python shim | docs/libs/scipy.md · docs/scipy-ios.md |
| SymPy 1.14.0 | Pure Python | docs/sympy.md · docs/libs/sympy.md |
| mpmath 1.4.1 | Pure Python | docs/mpmath.md |
| Library | Type | Doc |
|---|---|---|
| PyTorch 2.1.2 (patched) | Native iOS (arm64) — full import torch, tensors, autograd, nn, optim, JIT, FFT, distributions. Accelerate-backed linalg |
docs/libs/pytorch.md |
| transformers 4.41.2 | Pure Python — HuggingFace BERT / GPT-2 / T5 / BART, train + generate on-device | docs/libs/transformers.md |
| tokenizers 0.19.1 | Native iOS (Rust) — first public iOS build, real BPE/WordPiece/Unigram trainers, PyO3 bindings | docs/libs/tokenizers.md |
| scikit-learn | Pure NumPy (12k+ LOC, 40 modules, 38 metrics) | docs/sklearn.md · docs/libs/sklearn.md |
| Library | Type | Doc |
|---|---|---|
| matplotlib | Native iOS | docs/matplotlib.md · docs/libs/matplotlib.md |
| manim | Pure Python | docs/manim.md · docs/libs/manim.md |
| Pillow | Native iOS (libjpeg-turbo + zlib) | docs/pillow.md |
| PyAV / FFmpeg | Native iOS (libavcodec, libavformat, libavfilter, …) | docs/av-pyav.md · docs/libs/media.md |
| plotly | Pure Python | docs/plotly.md |
| svgelements | Pure Python | docs/svgelements.md |
| pydub | Pure Python (ffmpeg-backed) | docs/pydub.md |
| Library | Type | Doc |
|---|---|---|
| networkx | Pure Python | docs/networkx.md |
| beautifulsoup4 | Pure Python | docs/beautifulsoup.md |
| click | Pure Python | docs/click.md |
| jsonschema | Pure Python | docs/jsonschema.md |
| PyYAML | Pure Python | docs/pyyaml.md |
| pygments | Pure Python | docs/pygments.md |
| rich | Pure Python | docs/rich.md |
| tqdm | Pure Python | docs/tqdm.md |
| Minor libs (requests, dateutil, psutil, watchdog, screeninfo, soupsieve, safetensors, regex, typing_extensions, …) | Mixed | docs/minor-libs.md |
Full top-level reference: python-ios-lib/docs/README.md.
CodeBench's pdflatex / latex / tex shell commands route to busytex WASM running in a hidden WKWebView. The TeX Live 2023 data packages (texlive-basic + ubuntu-texlive-{latex-base, latex-recommended, fonts-recommended, latex-extra, science}, ~230 MB compressed) preload into MEMFS on first run. A 23 MB overlay data package bundles extra packages pdflatex commonly needs:
- PGF / TikZ / beamer + themes + translator
- hyperref + dependencies (kvsetkeys, pdfescape, hycolor, …)
- mathtools, microtype, cleveref, fancyhdr, geometry, setspace, bbm (+ bbm-macros)
- CJK + CJKutf8 (CJK family)
- amscls / amsthm / mathrsfs / booktabs / float / enumitem
- A kpathsea
ls-Rindex so lookups are O(1)
On top of the pipeline, the Swift-side BusytexEngine adds:
- Sibling-file collection: scans
\includegraphics,\includepdf,\input,\include,\bibliographyreferences, bundles real files + synthesizes 1×1 placeholder PNG/PDF for missing ones so compiles don't abort on a missing figure. - Unicode sanitizer: replaces codepoints pdflatex can't render (CJK, symbols past U+0200) with
[?]so stray Unicode doesn't fatal-error mid-compile. - Auto-lmodern injection: when the doc uses
\usepackage[T1]{fontenc}without a T1-capable font family, auto-adds\usepackage{lmodern}so pdftex doesn't try to spawnmktexpk(which needsfork(), unavailable in WASM). - Live progress streaming: every pdftex stdout line streams to the terminal as it compiles, plus a Swift-side 3 s heartbeat so you know the engine is alive during long runs.
Full pdflatex doc: media.md#offlinai_latex--local-latex-engine.
The AI subsystem (chat, ai builtin, inline code-completion) routes to one of four backends, switchable per-session via ⇧⌘, settings or per-request via the "provider" field in ai_request.json:
| Backend | Use case |
|---|---|
| Bundled GGUF (default) | Fully offline. Load a .gguf from the Models tab. Runs through llama.cpp / Metal |
| OpenAI | Streaming chat/completions SSE. Just paste an API key — defaults to https://api.openai.com/v1 |
| Anthropic | Streaming Messages API. Handles system separation automatically |
| OpenAI-compat | Any endpoint that speaks chat/completions SSE — Ollama, vLLM, llama.cpp server, LM Studio |
API keys live in Keychain (kSecAttrAccessibleWhenUnlockedThisDeviceOnly), one slot per provider. Ctrl-C in the terminal cancels in-flight HTTP requests and local generations symmetrically. Inline AI code-completion (ghost text, 350 ms debounced) uses whichever provider is configured.
The chat surface is a ChatGPT-style conversational UI: streaming replies, a collapsible "thinking" pill for reasoning models, inline-compiled LaTeX/math (SwiftMath), copyable code cards with a Run button, 📎 file attachments (PDF / text / source), and — when Auto-run AI code is enabled — automatic execution of the Python the assistant writes, with its output (text, charts, tables) routed into the preview pane.
- GGUF models via
llama.cppintegrated as an XCFramework. Load any Llama / Mistral / Qwen / Phi model, chat with streaming tokens. - ExecuTorch backends for Apple-Core-ML / XNNPACK / kernel-optimized inference of PyTorch models.
- PyTorch → Metal GPU bridge (
CodeBench/MetalMatmulBridge.swift) — exposes one@_cdeclC entry point backed byMPSMatrixMultiplication. Python's_torch_metal_bridge.py(in python-ios-lib) reaches it viadlopen(NULL)+dlsymand monkey-patchestorch.matmul/mm/bmm/addmm/F.linear/F.scaled_dot_product_attentionon every Python startup. Real on-device transformer training in fp32/fp16/bf16 with 2–10× speedup over CPU. Linker flags inOTHER_LDFLAGS(-Wl,-exported_symbol,_cb_metal_*) keep the symbol export-visible through Apple's archive / TestFlight strip pass. - LoRA fine-tuning via llama.cpp's Metal backward kernels (separate path from the PyTorch bridge) — see
CodeBench/LlamaFinetuner.swift. Trains a LoRA adapter on a GGUF base model in-place. - RAG: in-process sentence-embedding + vector store over user-imported text / PDF / markdown.
- Image generation via ExecuTorch-runnable diffusion-family models.
All models live in the app sandbox; no tokens leave the device.
The CodeBench shell IS a Python REPL — typing Python executes directly. On top of it there are builtins for POSIX-y operations iOS doesn't give you:
- File/system:
ls,cd,pwd,mkdir,rm,cp,mv,cat,head,tail,grep,find,file,touch,df,du,ncdu,top - Languages:
python/python3(with-V/-c/-m/ full flag handling),cc/gcc/clang,c++/g++/clang++,gfortran/f77/f90/f95 - LaTeX:
pdflatex,latex,tex,pdftex,latex-diagnose - Markdown / notebook viewers:
md/markdown(renders.mdto the preview pane via markdown-it-py + KaTeX),nb/ipynb/notebook(read-only.ipynbpreview — for the full cell-stacked editor, tap the file in the file browser) - AI:
ai(chat with the configured provider — bundled GGUF or remote) - Debugger:
debug(classic pdb in the terminal),debug-gui(floating toolbar + variable inspector in the editor) - REPL:
repl(persistent-namespace Python with rich displayhook;repl --keeppreserves last session's namespace) - Test runners:
test-libs/test_libs(smoke-test every bundled library) - VCS:
git clone(via zipball fetch — real Git protocol isn't available sandboxed; HuggingFacehf://URLs supported viasnapshot_download) - Package mgmt:
pip(install to the per-workspace site-packages dir; smart-skips already-bundled deps)
python --help, python -V, python -c "print(1+1)", python -m pip install … all behave like real CPython.
| Shortcut | Action |
|---|---|
⌘P |
Quick Open file picker (fuzzy match) |
⇧⌘, |
Settings sheet (AI provider, vim mode, inline completion) |
⌘/ |
Keyboard-shortcuts help |
⌘A (in terminal) |
Select all terminal output |
⌘C (in terminal) |
Copy selection |
⌘V |
Paste (Monaco + terminal) |
| Ctrl-C (in terminal) | Interrupt running script / cancel AI generation |
| Ctrl-D (in terminal) | EOF / exit current REPL |
This repo contains the CodeBench-specific pieces (Swift source, Xcode project, resources, busytex data packages via LFS, build scripts). It depends on python-ios-lib for the runtime layer (Python.xcframework, llama.xcframework, ExecuTorch frameworks, app_packages, Monaco folder, SwiftTerm SPM).
git clone https://github.com/yu314-coder/CodeBench
cd CodeBench
./scripts/setup.sh # clones python-ios-lib + symlinks it in
open CodeBench.xcodeprojsetup.sh clones python-ios-lib into _vendor/python-ios-lib/ and creates symlinks at the workspace root (Frameworks/, app_packages/, Monaco/, Sources/, Package.swift) pointing into the vendored runtime. Both repos keep independent .git histories — pulling updates on either side is just git pull in the corresponding directory. Re-run ./scripts/setup.sh --update to also fast-forward the runtime.
Git LFS auto-pulls the busytex data packages (~244 MB) on clone via the configured filters. If your git client skipped them, run git lfs pull inside CodeBench/Resources/Busytex/ before building.
Some setups (Windows-with-WSL, sandboxed CI) don't follow symlinks well. In that case, lay the two repos out side-by-side as one merged workspace:
git clone https://github.com/yu314-coder/python-ios-lib
cd python-ios-lib
git clone https://github.com/yu314-coder/CodeBench _codebench
cp -R _codebench/CodeBench _codebench/CodeBench.xcodeproj _codebench/Info.plist .
cp -R _codebench/scripts/* scripts/ 2>/dev/null || trueEither way, Xcode opens CodeBench.xcodeproj and finds Frameworks/, app_packages/, Monaco/, Sources/, and Package.swift at the workspace root.
Xcode scheme: CodeBench. Run on a real device, on TestFlight, or via My Mac (Designed for iPad). The first build takes a few minutes; subsequent builds are incremental.
llama.xcframework is prebuilt and shipped in python-ios-lib/Frameworks/, so the app links against it directly. Only rebuild from llama.cpp source if you need a different upstream version or build flags:
mkdir -p third_party
git clone https://github.com/ggerganov/llama.cpp third_party/llama.cpp
# Then run python-ios-lib/build-xcframework.sh + finish-ios-only.sh
# and replace python-ios-lib/Frameworks/llama.xcframework with the result.The finished CodeBench.app is ~1 GB:
- 791 MB
Frameworks/(Python, llama, ExecuTorch xcframeworks — from python-ios-lib) - 484 MB
app_packages/(bundled Python site-packages — from python-ios-lib) - 254 MB
CodeBench/(Swift source + Resources, of which ~230 MB is the LaTeX data packages — this repo, via LFS)
Verified against gh api /repos/yu314-coder/{repo}/contents/ as of this commit.
| This repo (CodeBench) | python-ios-lib | Upstream only — re-clone if rebuilding |
|---|---|---|
CodeBench/ — Swift source + Resources (busytex data via LFS) |
Frameworks/ — Python / llama / ExecuTorch / LaTeX xcframeworks |
third_party/llama.cpp/ → ggerganov/llama.cpp (~7.6 GB, only needed if rebuilding llama.xcframework) |
CodeBench.xcodeproj/ |
app_packages/ — bundled Python site-packages |
|
Info.plist |
Monaco/ — Monaco editor WebView bundle |
|
scripts/ — CodeBench-specific build helpers: |
Sources/ + Package.swift — SwiftTerm SPM integration |
|
• fetch_busytex_assets.sh (LFS-fallback downloader) |
docs/ — per-library reference docs (linked from this README) |
|
• fetch_ios_wheels.py, unpack_wheels.sh, check_wheels.py |
fix_ffmpeg_paths.sh — install_name_tool rewrite for av.*.framework |
|
• fix_ffmpeg_paths.sh (mirror) |
C-lib build dirs: cairo/, cpp/, ffmpeg/, fortran/, gcc/, harfbuzz/, pango/, skia-pathops/ |
|
• test_all_libs.py (smoke test for all 30+ libs) |
Python-pkg build dirs: numpy_ios/, pandas_ios/, pillow_ios/, psutil_ios/, audioop/, av/, matplotlib/, scipy/, sklearn/, manimpango/, mapbox_earcut/ |
Files / dirs not in either repo (local-only build scratch, never checked in): third_party/, DerivedData/, build/, .venv/, xcuserdata/. Generated on-demand by the build scripts in python-ios-lib.
Monaco editor: lives in python-ios-lib (14 MB — Monaco/ top-level folder reference). CodeBench's CodeBench.xcodeproj references it at the workspace root, so after you complete the step-2 clone recipe above, the Xcode project finds it.
CodeBench stands on:
- python-ios-lib — the CPython 3.14 runtime and 30+ iOS-ported Python libraries that make the app work
- BeeWare — the
Python.xcframeworkembedding technique - SwiftTerm — xterm-compatible terminal emulator
- Monaco Editor — VS Code's editor in WKWebView
- busytex — TeX Live 2023 compiled to WASM
- llama.cpp / ExecuTorch — local LLM inference
- SwiftMath — native CoreText math rendering for
$…$inline expressions
See LICENSE. Individual dependencies retain their original licenses — consult each project's repo (linked above).