Skip to content
tmagriPublic

About

A native macOS editor for Typst.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

TypstEdit

TypstEdit Icon

A lightweight, native macOS editor for Typst.

Download • Features • AI Assistant • Build


TypstEdit is a native SwiftUI application designed to make writing Typst documents on macOS seamless and efficient. It combines a robust text editor with a live preview, leveraging the speed of the native Typst CLI.

Screenshot of TypstEdit Screenshot of TypstEdit

✨ Features

Core Editing

  • ⚡️ Save Preview: Real-time compilation and preview of your document as you save the document.
  • 🎨 Advanced Syntax Highlighting: Refined native syntax highlighting for Typst with accurate keyword, function, and markup styling.
  • 🧑‍💻 Code & Data Files: A Typst project usually holds more than .typ files. TypstEdit opens .py, .json, .xml, .yml, .toml, .html, .css, .js/.ts, .sh, C/C++/C#, Java, Swift, Go, Rust, Ruby, PHP, SQL, and the rest of the bundled tree-sitter set as editable text with full syntax highlighting — plus plain text (.txt, .log, .csv, .bib, .tex) with none. These files have no Typst ribbon: the editing surface is the plain code editor, and the Typst-only formatting tools (bold, lists, headings, PDF preview) stay out of the way. Comment toggling follows the language (# in Python, -- in SQL, <!-- --> in XML), indentation switches to four spaces, and the status bar names the active syntax.
  • 🐍 Python Terminal: A .py file shows a real terminal where a document's PDF preview would be. It is your login shell, started in the file's folder, so python3, pip, pytest, a venv and any other command work as in Terminal.app. Run (status bar, pane header, or Editor → Run Python File, ⌘⌥R) saves the file and types python3 -- '<file>' into the shell. The pane header also has Interrupt (Ctrl-C), Clear and Restart. Copy, paste, Select All and ⌘F act on the terminal while it has focus. The shell keeps running and keeps its scrollback when you switch to another file or change the layout. It stops when you close the window.
  • 🔍 Find and Replace: Powerful search functionality with editor scrolling to results.
  • 🔢 Line Numbers: Helpful ruler for code navigation.
  • 📝 Rich Text Formatting: Support for bold, italic, underline, strikethrough, highlight, subscript, and superscript.
  • 📋 List Support: Bullet lists, numbered lists, and description lists with one-click insertion.
  • 💬 Code Blocks & Quotes: Inline code, code blocks, and block quotes with syntax support.
  • 🔄 Markdown Auto-Conversion: Paste Markdown directly into your .typ files and it will automatically convert to native Typst syntax! (Note: Highly complex Markdown syntax may occasionally fail to convert perfectly, so please review and correct the output after pasting).

Visual Editors

  • 🧮 Visual Equation Editor: Interactive equation editor with live LaTeX preview for mathematical expressions.
  • 📈 Calculator in Documents: Plot Bessel/Airy/erf/… functions, tabulate CAS expressions and inline exact results while the document compiles — see Calculator Inside Your Documents.
  • 🖥️ Calculator Mode: TI-89 style CAS home screen (solve, factor, d, ∫, Σ, matrices, exact/approx, ans recall, plot(…) charts) powered by the bundled Giac engine — right-click any result or chart to insert it into your document as Typst math, #plot code, sampled points or an image, save sessions as .tcalc files, browse a verified function catalogue, and let AI Chat evaluate math with evaluate_math. See Docs/calculator.md.
  • 🖼️ Figure Editor: Visual interface for inserting and editing figures with captions and references.
  • 🔧 Foundation Item Picker: Drill-down navigation for Typst foundation items (arrays, strings, datetime, calc, etc.) with searchable methods and properties.
  • 🎯 Contextual Editing: Smart "Edit at Cursor" feature that detects and opens appropriate editors for tables, links, equations, and more.

Project & File Management

  • 🚀 Project Templates: Start quickly with built-in templates for Articles, Reports, Presentations, and Resumes.
  • 📁 Project Sidebar: Easily navigate through your project files.
  • 🌿 Git Source Control: The panel below the project files shows changes in Tree mode by default, similar to VS Code. It supports diffs, staging, amend, undo, sync, branches, tags, stashes, and merge conflicts. Pressing Commit with nothing staged offers to stage all changes first, with an option to always do that; the ✨ button in the commit box writes a commit message with AI from the staged changes — or all changes when nothing is staged; the text lands in the box for review and nothing is committed on its own. Right-click any file or folder to add it to .gitignore (tracked paths are untracked from the index, keeping the files on disk). You can reword commits that are not pushed from the commit graph. Open files reload after a pull or checkout. See Git source control.
  • 📄 PDF Import: Convert a PDF to Typst on your Mac with no model downloads. The import finds reading order across columns, headings (from bookmarks when the PDF has them), lists, ruled tables with merged cells, code samples, figures with captions and display formulas. It removes running headers. Scanned PDFs are read with on-device OCR, and Force OCR ignores a bad text layer. Optional AI converts formulas and complex tables. You can also save the result as Markdown and JSON. See PDF import.
  • 📥 LyX Import: Convert LyX documents to Typst format with automatic font, page size, and heading detection.
  • 📤 Export Options: Export to PDF, PNG, SVG, Markdown, or Standard Typst (a portable .typ with calculator values inlined).
  • 💾 Project Backup: One-click project backup functionality.

Smart Snippets & Insertion

  • 🧩 Smart Snippets: Quickly insert complex elements like Tables, Charts, Timelines, and Outlines with one click.
  • 🔗 Quick Insert Menu: Easy access to symbols, external data, document styles, and more.
  • ⌨️ Comprehensive Keyboard Shortcuts: Full keyboard shortcuts for all insert menu items and formatting options.

Typst Compiler Management

  • 🔄 Typst Compiler Updates: Built-in updater to download and install the latest Typst compiler.
  • ⚙️ Flexible Update Modes: Choose between stable pre-compiled binaries or bleeding-edge source builds.
  • 🛠️ Custom Compiler Path: Support for custom Typst binary locations.

User Experience

  • 🍎 Native macOS Experience: Built with SwiftUI. 100% Native, fast, and lightweight.
  • 🐞 Error Reporting: Integrated error panel to quickly spot compilation issues.
  • 🌑 Dark Mode Optimized: Designed for a comfortable, eye-friendly dark editing environment.
  • ♿️ Accessibility Features: Color blindness simulation modes (Protanopia, Deuteranopia, Tritanopia).
  • 📐 Flexible Layout: Split views vertically or horizontally, editor-only, preview-only, or both panels.
  • 🔍 Per-Window Preview Zoom: Each window keeps its own preview scale. Zoom in, zoom out, or Fit to Window from the preview toolbar or Editor → Preview Zoom; fitting one window never changes another.

AI (100% opt-in)

  • 🤖 Assistant: Autocomplete, cursor-insertion prompts, selection rewrite/grammar fixes, and a persistent agentic chat panel — each configurable to OpenAI, Anthropic, Gemini, OpenRouter, or a local model.
  • 🧩 Skills, Tools & Search: Reusable Skills, your own MCP tools, and semantic project search (RAG), enabled per request.
  • 🔒 Safety First: File edits and code execution always need your approval, with a diff/code preview, unless you opt into YOLO mode — and the AI can't see outside the open project. AI Git tools are off by default. Fetch, Pull, and Push have independent permissions. Push always requires approval, even in YOLO mode.
  • 🔌 MCP Server & CLI: Let external agents (Claude Code, Codex, opencode, …) check and compile your documents, or script from the terminal with typstedit.

See AI Assistant & Agent Integration below for the full rundown.

🧠 AI Assistant & Agent Integration — click to expand (100% opt-in, every provider/tool is off until you turn it on)

Pick a provider per task in Settings → AI — OpenAI, Anthropic, Google Gemini, OpenRouter, or a local OpenAI-compatible server like Ollama/LM Studio. Nothing talks to a model until you enable it. If you run several local servers, each saved model can carry its own endpoint and API key, and Autocomplete and Embeddings can each use their own key instead of the shared Local Server key.

Autocomplete, AI Prompt, Refine & Grammar

  • Autocomplete offers inline ghost-text suggestions as you type, using whichever model you've assigned to the Completion task (a fast local model works well here).
  • AI Prompt (⌃⌘A, or Insert → AI Prompt…) generates or rewrites content at the cursor from a single instruction, optionally using enabled Skills and MCP tools.
  • Refine Writing / Fix Grammar, from the editor's right-click menu, rewrite the current selection and show a preview you accept or reject.

AI Chat Panel

Toggle with ⌘⇧J (or the status bar icon). Unlike AI Prompt, this is an ongoing, persistent conversation scoped to your whole open project — or just the current notebook, for notes — with its own agentic tool-calling loop. The chat never sees anything outside that scope: every path a tool touches is checked in code, not just told to behave in the prompt.

Built-in tools (always available, no MCP server required):

Tool What it does
list_files Lists files in the project/notebook; folders appear with a trailing /, so even an empty delivery folder can be confirmed.
read_file Reads a file, paged — large files come back a window at a time instead of flooding the conversation. outline: true returns only the headings (with each section's line range) and #include lines.
grep_project Case-insensitive by default; exact-string or regex search across the project, with a glob filter.
search_project Searches the project by meaning and by keyword together. Each result names its file, line range and section, and is flagged when the file changed after it was indexed. Weak matches are dropped; when nothing matches, a literal scan runs instead.
recall Searches the parts of the chat that are no longer in the model's context: summarized turns, and what earlier tool calls returned. Offered only once there is something to recall.
edit_file Precise find-and-replace on a file it has already read — cheaper and safer than rewriting the whole thing. Automatically compiles the .typ file it just touched and reports the result (success or the compiler error) in its own output, so a mismatched delimiter gets caught immediately instead of left behind.
write_file Creates or overwrites a whole file — same automatic compile check as edit_file for .typ files.
move_lines Moves a block of lines elsewhere in the same file without retyping it — the cheap, exact way to restructure a document (for example, moving sections into an appendix). Checks that the first line still matches, so stale line numbers can't move the wrong block; same approval and compile check as edit_file.
export_document Compiles a .typ/.md/.note document to PDF, PNG or SVG with the same pipeline as the editor's Export As. Output stays in the project/notebook scope and is approved first.
export_markdown Converts a .typ/.note document to a Markdown copy with the editor's own converter (or copies an .md through), showing the diff before writing.
create_folder Creates an empty delivery/evidence folder (and missing parents) inside the scope.
copy_files Copies related files or folders into a delivery folder in one approved batch, preserving names; existing items are only replaced with overwrite: true.
convert_markdown_to_typst Runs text through the same Markdown→Typst converter the editor uses when you paste — lets the AI write in Markdown instead of guessing at Typst syntax it's unsure of.
read_skill_file Reads a file attached to a currently enabled Skill.
run_typst_check Compiles a snippet, or an existing file in place, to check for errors on demand — without touching your document.
evaluate_math Evaluates one math expression with the bundled Giac CAS, exact or approximate: arithmetic, algebra, calculus, matrices. Returns plain text and Typst math. No project, Python, file, or network access is needed.
web_fetch (opt-in) Fetches a public web page as readable text. Private/loopback/internal addresses are always refused (even via a redirect), no credentials are ever sent, and every fetch needs its own approval showing the full URL.
run (Python, opt-in) Runs a short Python script, sandboxed by default — no network access, writes limited to a scratch folder.
git_status, git_diff, git_log (opt-in) Read the Git state of the open project: branch, ahead/behind, changed files, diffs, recent commits. Only when the open folder is the repository root.
git_stage, git_commit (opt-in) Stage files, or create a local commit with a message. Each call shows the exact paths and message and needs approval. Never pushes.
git_fetch, git_pull, git_push (independent opt-ins) Each action has its own permission. Pull is fast-forward only. Push always waits for approval, even in YOLO mode. No force-push or new upstream.

Anything that changes a file or runs code is shown to you first — a diff for writes/edits, the exact code for Python — and needs your approval, unless you turn on YOLO mode (a session-only toggle, with a confirmation dialog) to auto-approve everything for the rest of the session. Git tools are separate and off by default: Settings → AI Chat → Git Tools enables status/diff/log, staging, and local commits with a required message. Fetch, Pull, and Push each have an independent switch. Push always asks for approval, even in YOLO mode. YOLO never relaxes the project/notebook scope boundary, and Git tools refuse to run unless the open folder is the repository root. The automatic compile check is purely advisory — it never blocks a write — and can be turned off in Settings → AI Chat. If a file the AI (or another program) changes is open in an editor window with no unsaved edits, that window reloads it automatically. With unsaved edits in the way, it doesn't interrupt you — a small reload button lights up (next to the toolbar, same idea as the PDF preview's refresh button) so you can review and decide when you're ready, instead of a popup grabbing focus mid-edit.

For multi-step requests, the chat keeps a visible task checklist (update_plan). When the assistant finishes a turn with a written answer, any item it left in progress or pending is marked completed, so the checklist can't stay parked mid-step after the work is handed back. The checklist and a one-line-per-call log of earlier tool calls (what was read, searched, or edited, and the result) go into every new turn, so a follow-up starts where the last one stopped instead of re-reading the project. Within a turn, an identical read or search repeated with nothing edited in between is labelled as a repeat, then refused; the refused call still shows in the chat as "not run". When old tool output must be dropped to fit the budget, a digest takes its place: the line range, the headings with line numbers, or the files a search matched. read_file has an outline mode that returns only a document's headings (with the line range of each section) and #include lines, so the assistant can map a long document in one call, plan the change, and then read and edit one section at a time. After eight rounds in a row of only reading and searching, the assistant is told to act or answer. When the turn limit is near, it is warned, so it writes a final answer instead of running out mid-task. All three limits are in Settings → AI Assistant → AI Chat → Loop Protection, where 0 turns each one off. Each reply has its own token cap (Settings → AI Chat → Reply Size; automatic = 16,384, separate from the AI Prompt Max Tokens), because reasoning models spend hidden thinking from the same budget. A reply cut off by the cap — empty, half a tool call, or an answer stopping mid-sentence — is retried with a larger cap instead of being treated as empty; a cut-off tool call never runs, and an answer that still doesn't fit says it was cut off. The chat also tells the model which file is open in the editor, so it works on that file rather than a copy with the same name. The chat can also delegate large research jobs to a read-only subagent (delegate_task) that explores in its own context and reports back only its findings. The subagent can run on a different model or server than the chat (Settings → AI Assistant → Model per Feature → AI Chat Subagent): a cheap local model does the reading while a stronger cloud model runs the conversation, and the subagent's saved model can carry its own context budget. By default it follows the chat's model. If that model's server stays busy past a short wait (local servers usually serve one request at a time and queue the rest silently) or the request fails, the delegation falls back to the chat's model so the job still completes — the generating row says when it does, and pressing Stop never falls back. If the model exposes reasoning/"thinking" content, it streams live in the generating row (open by default) and stays under that reply as a collapsible "Thought process" section, with the newest one opened automatically. A context-size estimate sits in the chat header against a configurable budget — global, or set per saved model, so a paid long-context model can use its real window while small local models keep a conservative one. Once a conversation grows past that budget, older turns are automatically (or manually) summarized so the chat keeps working instead of failing with an empty response. The summary records the objective, decisions, open items, and evidence as file and line pointers. A later summary updates the earlier one instead of summarizing it again. The summarized turns stay searchable with recall, and the first turn after a summary gets the most relevant project passages again. Empty or stalled replies are retried automatically, and the generating row says what is being retried and which attempt it is; the retry budget is configurable, including unlimited until you press Stop. Auto-proceed (Settings → AI Chat → Auto-proceed) covers the other kind of "not finished": when a reply ends by naming work that is still left — a Next steps heading, Still to do, I still need to …, or "next step when you're ready" — the chat waits out a visible countdown and then sends the continuation itself, for unattended runs on local models. The panel shows the countdown with Continue now and Cancel, typing a message takes over from it, and Stop always ends the run. It can also retry automatically after a failed attempt, up to a per-request cap (default 10, or unlimited); when the cap is reached the panel says so and offers Keep going rather than going quiet. A goal armed from the chat panel owns continuations while it is active, so the two never run together. Every tool call is tracked while it runs: the chat names the running task and shows its elapsed time, keeps waiting for it to finish, and lets you stop that one task from its row or stop the whole turn — either Stop aborts the work itself, terminating a running Python script, compile or Git process and cancelling an in-flight MCP request, rather than only hiding the spinner. Stopping one task lets the assistant continue without its result; stopping the turn ends it. The transcript follows new output automatically, but switches to a manual mode as soon as you scroll up during a reply (or open earlier messages): it stops following until you ask for the newest part again — the jump-to-latest button, sending a message, or switching to another chat — and a manual scroll never gets yanked away mid-gesture. A long conversation renders only its newest messages — earlier ones load on demand — and a very long thought stream shows a bounded live preview, with the full reasoning kept on the finished reply and one click away. That keeps the panel responsive without throwing any transcript content away. Malformed tool arguments are repaired against the tool's own schema before they run — a "20" where a number belongs, a stringified JSON object, or a null in an optional field is fixed in place (only provably-safe repairs, per opencode's tool-input-repair), so small local models don't burn a turn on refused calls.

Plan mode (the lightbulb toggle) removes every mutating tool from what the model is offered — no edits, writes, exports, Python, or Git changes — so it can only read, search, check syntax, and keep its checklist current. A reminder tells it to explore and discuss a plan in the conversation, and to ask you to turn Plan mode off rather than start implementing. The toggle persists with the conversation. Steering removes the old "can't talk while it works" limit: while a reply is running, the composer stays live, and sending (Return, or the outlined arrow next to Stop) routes your message into the running turn — the model reads it at its next step boundary and adjusts course, instead of the message being dropped or waiting for the turn to end. Goal mode (the flag menu) arms an objective the chat keeps working toward: each finished reply is judged by a reviewer — continue (a continuation is injected and the turn goes on), complete, or paused — with a round cap (50), stall detection (the reviewer repeating itself, or no tool activity, parks it after three rounds), and automatic pausing when you press Stop or a turn fails. An active goal shows a banner with the objective, round progress, and the reviewer's last word; pause/resume/clear live in the same menu. A goal survives a restart and re-engages on the next message, where the reviewer can also settle it as done.

Skills, MCP Tools & Semantic Search

  • Skills are reusable, portable instruction sets — the same idea as Claude Code/OpenCode's Agent Skills — stored as a SKILL.md folder, either global or project-scoped (.typstedit/skills/, shareable via git), with optional attachments the AI reads only when it actually needs them.
  • MCP Tools: add your own MCP servers in Settings → MCP Servers and enable them per request to give AI Prompt/Chat extra tools.
  • Semantic Project Search (RAG): a local embedding index over your project's files, enriching prompts and powering search_project. Files are split by section, and each passage is embedded with its file and heading path. A saved file re-embeds only the passages that changed, in batched requests. Keyword (BM25) search runs next to the vector search, so exact labels and names are found too. The relevance cutoff is calibrated to each embedding model: it measures how similar unrelated texts look to that model and drops anything near that level. Switching the embedding model rebuilds the index.

AI Agents & Command Line (TypstEdit as a tool for other agents)

Open Settings → Integrations and click Install to add the typstedit command. It then prints ready-to-paste MCP configs for common agents.

typstedit check report.typ            # errors/warnings as file:line:col (exit 1 on errors)
typstedit check report.typ --json     # machine-readable diagnostics
typstedit compile report.typ          # → report.pdf
typstedit compile notes.md out.png --ppi 200   # Markdown/.note files are converted first
echo '= Hi' | typstedit check -       # source from stdin
typstedit convert notes.md notes.typ  # Markdown → Typst (written to notes.typ)
typstedit to-markdown report.typ      # Typst/Note → Markdown (prints it)
typstedit open report.typ             # show it in the editor

convert and to-markdown use the same converters as the editor, so what an agent produces matches what you would get by pasting or copying in the app. --hybrid converts Markdown for a .note file (live Typst regions survive), and --verify compiles the converted Typst and reports errors with the same diagnostic shape as check.

--json emits a stable, agent-oriented shape rather than Typst's box-drawing text. Every diagnostic carries what an agent needs to act without guessing:

Field Purpose
status ok or errors — branch on this, not on prose.
code Stable slug such as unknown-variable or incompatible-operands. Typst itself has no diagnostic codes (typst#6059 is still open); these are derived from the message's leading phrase, so they survive rewording and operand order.
explanation Plain-English cause and suggested fix — the same text the editor's error panel shows.
fixes Concrete replacements for the offending line.
trace The while calling … chain, so it is clear the fix belongs at the call site rather than the definition.
file, line, column, endColumn Mapped back to your original .md/.note source, not the converted file.
deps Every file the compile read (typst --deps). When a diagnostic's file is not the file you edited, the defect is in an import.
typstVersion The compiler that produced the result.

Diagnostics are de-duplicated, and log stays empty unless the output could not be parsed — so a normal check costs a few hundred tokens, not thousands.

To connect an agent, register an MCP server with command typstedit and argument mcp, e.g.:

claude mcp add --scope user typstedit -- typstedit mcp
{ "mcpServers": { "typstedit": { "command": "typstedit", "args": ["mcp"] } } }

If you skip the install, use the full path /Applications/TypstEdit.app/Contents/Helpers/typstedit as the command — not Contents/MacOS/TypstEdit, because a headless process started from the GUI binary makes macOS treat it as the running app and the editor then refuses to open until that process exits. Tools exposed: typst_check, typst_compile, typst_render_preview (returns page images), markdown_to_typst, typst_to_markdown, typstedit_open, typstedit_info and read_pdf (reads a PDF as Markdown with the PDF importer).

markdown_to_typst exists so an agent never has to hand-write Typst delimiters from memory: it runs the editor's own Markdown→Typst pass (headings, lists, tables, links, images, code and math). typst_to_markdown goes the other way, for handing a document to a tool or person that needs Markdown; layout and page setup have no Markdown equivalent and are dropped, while functions the converter does not know are left as visible Typst source.

📈 Calculator Inside Your Documents (unique to TypstEdit)

TypstEdit can evaluate mathematics while your document compiles — something no other Typst editor does. Write plots of Bessel functions, tables of exact CAS results and parameter studies directly in Typst:

#let k = calc-param(2)                      // a document parameter, shared with the CAS

// One curve per loop step — the loop runs inside Typst, calling compiled functions.
#plot(
  curves: range(4).map(n => (fn: x => BesselJ(n, x), label: [$J_#n$])),
  x-min: 0, x-max: 20, title: [Bessel $J_n (x)$],
)

// Any of ~80 special functions by name, curried for plotting.
#plot(curves: ((fn: calc-fn("erf"), label: [erf]), (fn: calc-fn("airyai"), label: [Ai])), -4, 4)

// Any Giac expression, sampled by the CAS at compile time.
#plot(points: #calc-table("sin(k*x)/x", x, 0.05, 15, 150), x-min: 0, x-max: 15)

// Exact CAS values, specialised per loop iteration.
#table(columns: 2, ..for n in (1, 2, 3) {
  ([#n], [#calc("integrate(x^n*exp(x), x, 0, 1)")])
})

Plots and CAS values computed while the document compiles

The image above is the real output of the document, rendered by TypstEdit.

Three ways to reach the calculator, by when they run:

You write Runs Covers Values
#calc("…"), #calc-table("…", …) before Typst, through the Giac CAS any CAS expression exact
…the same inside for loops with literal bounds specialised per iteration, before Typst any CAS expression exact
calc-call("name", …), BesselJ, plot, calc-fn inside Typst (compiled WebAssembly) ~80 functions: Bessel, Airy, gamma, erf, zeta, Lambert W, elliptic, orthogonal polynomials, … chart-grade, verified
  • Verified numerics. The WebAssembly core is checked against arbitrary-precision references (mpmath) on 16,765 cases across every function — no function returns a plausible-but-wrong number; where it cannot guarantee accuracy it returns NaN instead.
  • Editor integration. Offline autocomplete knows every function (calc-call("bess… → besselj), plot( parameters and CAS names inside #calc("…"). Put the cursor in an equation and use Send to Calculator to load it into the calculator panel.
  • Portability is explicit. A .typ that uses these features shows a Non-portable Typst badge. File ▸ Export As ▸ Standard Typst writes a version that compiles in stock Typst: every CAS value is inlined, and if runtime functions remain, the bridge and the 51 KB WebAssembly core are bundled next to it through Typst's standard plugin API. The export is verified with the plain typst compiler before it is reported as done. (.note files are TypstEdit-native and show no warning.)
  • Every export — PDF, PNG, SVG, Markdown/Note sources, and the command line (typstedit compile) — runs the same calculator step as the live preview.

See Docs/calculator.md for the full guide and Tools/calc-plugin/README.md for the numeric core.

📥 Installation

TypstEdit is available for both Apple Silicon (M1/M2/M3) and Intel Macs.

  1. Go to the Releases page.
  2. Download the correct installer for your Mac:
    • Apple Silicon (M1/M2/M3): Download TypstEdit_AppleSilicon.dmg
    • Intel Macs: Download TypstEdit_Intel.dmg
  3. Open the .dmg and drag the app to your Applications folder.

🛠️ Building from Source

If you want to contribute or build the app yourself:

Prerequisites

  • macOS 13.0+ (Ventura) or later to run; building needs a recent macOS.
  • Xcode 26+ (Swift 6.2+) installed — the package uses swift-tools-version: 6.2. Full Xcode is required for universal builds; Command Line Tools alone ship an Apple-Silicon-only copy of the Swift compatibility runtime.
  • For the Calculator's CAS helper: Homebrew autoconf, automake, libtool, and bash (brew install autoconf automake libtool bash).
  • For the calculator's WebAssembly core (functions used inside documents): brew install lld (provides wasm-ld). The bundle scripts run Tools/calc-plugin/build_calc_plugin.sh automatically.

Steps

  1. Clone the repository:

    git clone https://github.com/tmagri/TypstEdit.git
    cd TypstEdit
  2. Ensure the Typst binaries are present: Place typst-aarch64-apple-darwin/typst (Apple Silicon), typst-x86_64-apple-darwin/typst (Intel), and for universal builds the combined typst-universal.

  3. Build the app (universal, recommended):

    ./bundle_universal.sh

    This builds one app that runs on both Apple Silicon and Intel Macs. The first run also builds the Calculator's CAS helper (allow 20–40 minutes per architecture; later runs reuse the cached libraries).

    For a single-architecture app on this Mac, use ./bundle.sh (defaults to this Mac's architecture).

    Both scripts finish by running Tools/packaging/verify_bundle.sh, which fails the build if the app could not launch on another Mac — for example a missing bundled Swift runtime, a wrong architecture, or a library that needs a newer macOS than the app declares.

  4. Create Installer (Optional):

    ./create_dmg.sh            # universal (TypstEdit_Universal.dmg)
    ./create_dmg.sh arm64      # Apple Silicon only
    ./create_dmg.sh x86_64     # Intel only

Build separate architecture binaries

In VS Code, run swift: build all (arm64 + Intel x86_64) to build two separate release binaries. The task builds ARM64 and Intel sequentially; it does not combine them into a universal binary. You can also run either architecture task individually:

  • swift: build (arm64)
  • swift: build (Intel x86_64)

The binaries are written to .build/arch-build/arm64/out/Products/Release/TypstEdit and .build/arch-build/x86_64/out/Products/Release/TypstEdit, respectively.

Publish installers

./create_dmg.sh (no argument) creates TypstEdit_Universal.dmg, one installer for every Mac. The VS Code publish: installer tasks still build the separate TypstEdit_AppleSilicon.dmg and TypstEdit_Intel.dmg if you prefer smaller per-architecture downloads.

⚠️ "App is damaged" / "can't be opened" on another Mac

The app is ad-hoc signed, not signed with a paid Apple Developer ID or notarized, so Gatekeeper blocks it the first time it is opened after a download.

To fix this (once per Mac):

  1. Drag TypstEdit from the DMG to Applications.
  2. Open Terminal and run:
    xattr -dr com.apple.quarantine /Applications/TypstEdit.app
  3. Open the app normally. (Alternatively: right-click the app → Open → Open.)

📄 License

This project is open source.

MIT License

Copyright (c) 2024-2026 SuperMegaFort, tmagri

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

❤️ Credits

  • Built for Typst.
  • Uses the official Typst CLI binary.

About

A native macOS editor for Typst.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages