Skip to main content
Glama
README.md
<p align="center">
  <img src="docs/assets/galley-logo.svg" alt="Galley" width="440" />
</p>

<p align="center">
  <em>An open-source, <strong>local-first</strong>, <strong>model-agnostic</strong>,
  <strong>AI-native</strong> document workspace built on
  <a href="https://typst.app">Typst</a>.</em>
</p>

<p align="center">
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0--only-blue" alt="License: AGPL-3.0-only" /></a>
  <a href="https://typst.app"><img src="https://img.shields.io/badge/built%20on-Typst-239dad" alt="Built on Typst" /></a>
  <a href="docs/security-model.md"><img src="https://img.shields.io/badge/security-threat%20model-success" alt="Security: threat model" /></a>
  <a href="CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen" alt="PRs welcome" /></a>
  <a href="https://ko-fi.com/vito507767"><img src="https://img.shields.io/badge/Ko--fi-support%20this%20project-ff5e5b?logo=ko-fi&logoColor=white" alt="Support this project on Ko-fi" /></a>
</p>

<p align="center">
  <strong><a href="https://vitofico.github.io/galley/">▶ Try the live demo</a></strong> —
  no signup, no API key. Everything runs in your browser. It's meant for the
  built-in offline Demo agent — don't enter a real API key or GitHub token there.
</p>

<p align="center">
  <a href="https://vitofico.github.io/galley/">
    <img src="docs/assets/galley-editor.png" width="900"
      alt="The Galley editor: Typst source on the left, the typeset 'Annus Mirabilis' manuscript rendering in the middle, and the agent panel on the right showing the offline Demo model." />
  </a>
</p>

A *galley proof* is the preliminary typeset version you check and correct before
final layout — which is exactly this app's core loop: **compile → inspect →
correct → repeat.**

Galley is, conceptually, "AI-enhanced ShareLaTeX, but Typst instead of LaTeX, open source,
and not locked to one vendor's cloud or model." The differentiation is three
bets a cloud-only, single-vendor product structurally cannot make:

1. **Agentic, not autocomplete.** Typst compiles in milliseconds and emits
   structured, machine-readable diagnostics. That makes a tight
   **edit → compile → read errors → self-correct** loop cheap. _That loop is the
   product._
2. **Local-first.** The Typst compiler runs in the browser via WASM
   ([`typst.ts`](https://github.com/Myriad-Dreamin/typst.ts)). Your document and
   your compile stay on your machine. Server-side compile is an optional
   enhancement, never a dependency. _(See the honest [local-first
   definition](docs/vision.md#what-local-first-means-and-doesnt) — remote model
   endpoints still receive your document.)_
3. **Bring-your-own-model.** A provider abstraction runs the agent against any
   OpenAI-compatible endpoint, Anthropic, or a local Ollama. Model choice is
   config, never a fork.

## Status

Galley works end-to-end in the browser today: a CodeMirror editor with a live
SVG preview (typst.ts compiling in a Web Worker) and the human-in-the-loop
agent loop — **request → self-correct → diff → Accept/Reject → re-render**.
Bring your own model (any OpenAI-compatible endpoint, Anthropic, or a local
Ollama) or use the built-in offline **Demo** agent. Real-time collaboration is
opt-in — one-click **Share** upgrades a project to Yjs CRDT co-editing with the
**agent as a peer**, presence, and per-author attribution ([ADRs
0005–0012](docs/decisions)). Multi-file projects and templates, named versions
with visual compare, broad import/export (Markdown, LaTeX, Overleaf `.zip`,
Zotero/`.bib` in; PDF/PNG out), and opt-in MCP access behind an explicit
consent gate round it out. Self-host with Docker compose or Kubernetes; the
whole test suite (typecheck + unit + Playwright e2e) runs **in Docker**. See
the [CHANGELOG](CHANGELOG.md) for the full feature inventory and the
[roadmap](docs/roadmap.md) for what's next.

### Default product & routes

Galley uses **path-based routes** (a tiny History-API router):

- **`/`** — the **Projects page**, the landing surface (create / open / import projects).
- **`/p/<id>`** — a specific **local-first project** (auto-saved to the browser, reload-survivable).
- **`/library`** — the project dashboard.
- **`/join/<room>`** — share-link entry (joins a collaboration room; carries `?sync=` / `?role=`).
- **`/settings`** — device-scoped settings.

Collaboration is an explicit **Share** action, never on by default. The only
editor-on-home hatch is **`?seed=…`** (the seeded Einstein showcase, also the e2e entry).

## The core loop

```
User request
   │
   ▼
Agent proposes edits (search/replace blocks)
   │
   ▼
Apply to a SCRATCH copy ──► compile ──► read diagnostics
   │                                         │
   │   ◄── errors? revise (up to MAX_ITERS) ─┘
   ▼
Clean compile → present a reviewable diff
   │
   ▼
User clicks Accept ──► apply to live document
        or Reject  ──► discard scratch
```

In the editor loop the human is always in control — the in-app agent never
auto-applies an edit. (External MCP agents can be granted opt-in auto-accept — off
by default, signed, checkpointed, and revertable — which only drives the same
Accept path; see the [security model](docs/security-model.md).)

## Bring your own agent (no API key)

Galley's built-in agent needs a provider API key, billed per token. You can skip
that entirely: Galley ships a **local MCP kernel** that carries no model at all.
No provider, no key, no inference. It is a bridge that exposes your project as
tools to an agent you already run, so **Claude Code or Codex drives Galley on the
plan you already pay for**.

```bash
# One-time: install the kernel's bin from your checkout
cd apps/mcp && pnpm link --global

# Share the project in the browser to mint a room id, then:
claude mcp add galley -- galley-mcp \
  --sync ws://localhost:1234 --room <room-id> --file /main.typ
```

Your agent gets the live document, the file tree, query-relevant excerpts across
files, and real Typst diagnostics. What it does **not** get is write access:
`propose_edit` and `propose_files` publish a pending review card, and a human
clicks Accept. That gate is structural, not a preference.

Control mode goes further and lets the agent list your library and ask to open a
project, behind a one-time pairing code, per-project read grants that default to
zero, and a blocking confirmation for every open.

→ **[Use your own coding agent with Galley](docs/mcp.md)** for the walkthrough,
or [the full kernel reference](docs/mcp-kernel-setup.md) for every flag.

## Monorepo layout

```
galley/
├─ apps/
│  ├─ web/          # React + Vite app: editor, preview, agent panel, diff UI
│  ├─ web-server/   # tiny Hono static server for the built SPA (self-host runtime)
│  ├─ sync/         # @galley/sync — optional y-websocket collaboration server
│  ├─ compile/      # optional server-side Typst compile service (Hono + @galley/compiler)
│  ├─ proxy/        # thin, optional, self-hostable model-API proxy (keys off the browser)
│  └─ mcp/          # inbound MCP local kernel: exposes the agent tools to external MCP clients
├─ packages/
│  ├─ compiler/     # typst.ts wrapper: check(), render(), export() — in a Worker
│  ├─ agent/        # agent loop, tools, provider abstraction
│  ├─ collab/       # Yjs CRDT doc, presence, and cross-peer author attribution
│  ├─ auth/         # generic OIDC (Auth Code + PKCE) auth core
│  ├─ persistence/  # ProjectStore / CrdtStore / VersionStore adapters
│  └─ shared/       # cross-package types: Diagnostic, EditBlock, AgentEvent, …
├─ docs/            # foundation docs (start here ↓)
├─ AGENTS.md        # rules for AI agents working ON this repo
└─ CONTRIBUTING.md
```

`compiler` and `agent` are framework-agnostic (no React) so they can be reused
server-side. The optional server pieces — `apps/proxy` (model proxy, keys off the
browser), `apps/sync` (collaboration relay), and `apps/compile` (server-side
compile) — are stateless, off by default, and self-hostable (see the
[ADRs](docs/decisions)). The default single-user path needs none of them.

## Documentation

Read these in order:

| Doc | Purpose |
| --- | --- |
| [`docs/vision.md`](docs/vision.md) | Product principles, the three bets, non-goals, honest definitions |
| [`docs/architecture.md`](docs/architecture.md) | Package boundaries, data flow, the worker model, source-of-truth rules |
| [`docs/roadmap.md`](docs/roadmap.md) | What's built, what activates with config, future directions |
| [`docs/security-model.md`](docs/security-model.md) | Threat model, per-surface posture, deployment hardening checklist |
| [`docs/self-host.md`](docs/self-host.md) | Docker compose + Kubernetes packaging |
| [`docs/server-and-collaboration.md`](docs/server-and-collaboration.md) | Server topology, accounts/auth, Yjs collaboration, agent-as-peer (design rationale) |
| [`docs/agent-loop.md`](docs/agent-loop.md) | The agent state machine, tools, iteration/error/cancel handling, event stream |
| [`docs/editing-and-diff.md`](docs/editing-and-diff.md) | Search/replace contract, scratch isolation, diff, Accept/Reject conflicts |
| [`docs/compiler.md`](docs/compiler.md) | typst.ts init, worker protocol, fonts, diagnostics normalization, preview/export |
| [`docs/providers.md`](docs/providers.md) | Provider types, capability probing, CORS/key caveats, local vs remote privacy |
| [`docs/mcp.md`](docs/mcp.md) | Drive Galley from Claude Code or Codex over MCP, on your own plan instead of an API key |
| [`docs/decisions/`](docs/decisions) | Architecture Decision Records (ADRs) |

## Quick start

The fastest path — run it with Docker (no toolchain needed):

```bash
docker compose up --build    # → http://localhost:8080
```

The **Demo (offline)** model lets you try the full agent loop with no provider
or API key configured; open **Settings** to point at an OpenAI-compatible
endpoint, Anthropic, or a local Ollama.

For development with a local toolchain (Node ≥ 20, pnpm 9):

```bash
pnpm install
pnpm dev          # runs @galley/web
```

Tests build and run entirely **in Docker**:

```bash
# Full green-gate: typecheck + unit + web build + Playwright e2e
# (--build is required: plain `run` reuses a cached image and would test stale code)
docker compose -f docker-compose.test.yml run --rm --build test

# Fast typecheck + unit only
docker compose -f docker-compose.test.yml run --rm --build unit
```

## Self-hosting

`docker compose up --build` runs Galley (web + proxy + sync) at
http://localhost:8080 — see [`docs/self-host.md`](docs/self-host.md). The web app
compiles Typst in the browser by default, so it is complete on its own.

**Enabling server-side compile (opt-in).** The heavier compile service isn't run
by default, so `Settings → Compile → Server` reports "Not configured." To wire it
up, use the ready-to-run overlay:

```bash
docker compose -f docker-compose.yml -f docker-compose.compile.yml \
  --profile compile up --build
```

It pre-sets `GALLEY_COMPILE_URL=http://127.0.0.1:3001/compile` (the
browser-reachable endpoint) so Server/Auto "just work"; the default
`docker compose up` stays web-only. Details in
[`docs/self-host.md`](docs/self-host.md#enabling-server-side-compile).

## License

Galley is licensed under **AGPL-3.0-only** (see [`LICENSE`](LICENSE)): use,
modify, and self-host freely; if you offer a **modified** Galley to others over
a network, you must publish your modified source under the same license. The
"Galley" name and logo are protected separately by the
[trademark policy](TRADEMARKS.md) — forks must rename. Contributions require
the [CLA](CLA.md) (see [CONTRIBUTING](CONTRIBUTING.md)).

The Typst compiler Galley builds on is **Apache-2.0** (permissive — building on
it is fine); keep the name distinct (don't name anything "Typst") and ship
Typst's `NOTICE` if you ever redistribute its binary. See
[`docs/vision.md#licensing--branding`](docs/vision.md#licensing--branding).

TDQS

A3.9/5.0

Scored across 8 tools

Disambiguation4/5

Tools map to distinct actions: ping, read, list, search, propose, compile. The close pairs read_document/read_file and propose_edit/propose_files are separated clearly by session-target convenience vs exact-path reads and single-file vs atomic multi-file proposals.

Naming Consistency4/5

Most tools follow a readable snake_case verb_noun pattern (read_document, list_files, propose_edit), but project_context is a noun phrase, compile is a bare verb, and galley_ping uses a server prefix unlike the others. These are minor deviations rather than chaotic naming.

Tool Count5/5

8 tools is well-scoped for a collaborative document editing server. Each tool earns its place and the count sits comfortably within the ideal 3-15 range.

Completeness4/5

The surface covers the main workflow: inspect files, search the project, propose single or multi-file changes, and compile to verify. Minor gaps like no proposal history or binary byte retrieval don't create dead ends because statuses are returned and binary pointers are provided.

Maintenance

ActivityMaintained
ResponsivenessNo issues