1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
[]
= "inkhaven"
= "1.6.21"
= "2024"
# Rust 1.85 stabilised edition 2024. Bump alongside any toolchain-only
# feature usage (let-else / chained-comparisons / etc. are already stable).
= "1.85"
= "Apache-2.0 AND MIT"
= "Inkhaven — TUI literary work editor for Typst books"
= ["Vladimir Ulogov <vulogov@gmail.com>"]
= "https://github.com/vulogov/blackInkhaven"
= "https://github.com/vulogov/blackInkhaven"
= "https://github.com/vulogov/blackInkhaven/tree/main/Documentation"
= "README.md"
= ["tui", "editor", "typst", "writing", "ai"]
= ["command-line-utilities", "text-editors", "text-processing"]
# `cargo publish` packaging filter. Keeps the crate tarball
# small + focused on what a `cargo install inkhaven` consumer
# actually needs to compile and run the binary. Without
# this, the Book/ PDF (≈7 MB), screenshots (1+ MB each),
# rendered artefacts, and the entire Documentation/ tree
# would get bundled — pushing the crate past crates.io's
# default per-file ceilings and the practical 10 MB total
# soft-cap, and bloating the cargo-binstall fallback path
# for users without prebuilts.
#
# Patterns are glob-style relative to the crate root. The
# Book of Inkhaven companion volume lives in git for readers
# and gets re-rendered on every release tag — it does NOT
# need to ship inside the crate.
= [
# Companion book — typst sources, markdown mirror, the
# rendered PDF, every screenshot PNG, the cover art.
"Book/**",
# Documentation/ ships in the crate — the markdown
# tutorials + release notes are small (~800 KB
# uncompressed, ~150 KB after gzip) and useful to
# `cargo install` consumers without a clone. The only
# carve-out is rendered PDFs of those docs, which are
# ephemeral local builds and should never travel inside
# the crate.
"Documentation/**/*.pdf",
# CI workflows + per-machine editor / agent state.
".github/**",
".claude/**",
".vscode/**",
".idea/**",
# macOS / shell scratch.
".DS_Store",
"**/.DS_Store",
# Rendered artefacts from `Ctrl+B O` takes that ended up
# at the repo root during development. Same patterns the
# top-level .gitignore protects against.
"/*.pdf",
"/*.epub",
# PDF-1 imposition proof corpus — a fake book + its compiled /
# imposed PDFs, kept for local verification only. Never ships in
# the crate.
"proof_corpus/**",
# NB: do NOT exclude `*.png` globally — `logo.png` at the
# repo root is `include_bytes!`'d into the binary; the
# build breaks without it. assets/* is also untouched
# because every file there is loaded via include_str! /
# include_bytes!.
]
# Distribution model: binary releases via GitHub Actions
# (`.github/workflows/release.yml`) on tag push. Users install with:
# * `cargo install --git https://github.com/vulogov/blackInkhaven`
# * `cargo binstall inkhaven` (prebuilts; see [package.metadata.binstall])
# * Direct download from https://github.com/vulogov/blackInkhaven/releases
#
# `cargo-binstall` metadata: tells `cargo binstall inkhaven` where to
# fetch a prebuilt for the current host triple. The asset filenames
# below must match what `.github/workflows/release.yml` uploads.
[]
= "{repo}/releases/download/v{version}/{name}-{target}{archive-suffix}"
= "{name}-{target}/{bin}{binary-ext}"
= "tgz"
# Windows override kept commented so it's a one-line revert when the
# windows-latest matrix entry comes back in release.yml. While disabled,
# `cargo binstall inkhaven` on a Windows host correctly errors with
# "no prebuilt for target" — users on Windows can still
# `cargo install --git`.
# [package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
# pkg-fmt = "zip"
[[]]
= "inkhaven"
= "src/main.rs"
[]
# Storage / search. Inkhaven owns its own slim storage layer at
# `src/storage/` — DuckDB-backed metadata + blob stores, an HNSW vector
# index via `vecstore`, and a fastembed wrapper for multilingual
# embeddings. This module replaces what used to be a dependency on
# `bdslib` (vendored at vendor/bdslib/), shedding ~300+ transitive
# crates that inkhaven never reached (Tantivy, augurs, axum, …).
= { = "1", = ["bundled", "r2d2"] }
= "5"
# RESRCH-2 (R2-B) — text extraction from imported PDF research sources. The
# only new runtime crate; the MD/text import path needs none.
= "0.9"
# RESRCH-2 (R2-C) — HTTP client for web search & fetch (`/web`). Already in the
# tree transitively via genai; pinned to rustls to avoid a native-TLS / OpenSSL
# system dep. Only used when `research.web` is configured.
= { = "0.12", = false, = ["json", "rustls-tls"] }
= "1"
= "0.8"
= "0.12"
# CLI
= { = "4", = ["derive", "wrap_help"] }
# Config (HJSON requirement)
= "1.1"
= { = "1", = ["derive"] }
= "1"
# STRUCT-1 — Jinja template rendering for content_type: "jinja" paragraphs.
# Environment built at book assembly time; templates registered from the
# Snippets system book; manuscript Jinja paragraphs rendered to .typ before
# typst compile runs. minijinja chosen over tera for in-memory template
# registration (no filesystem glob needed).
= "2"
# Async runtime (required by genai)
= { = "1", = [
"macros",
"rt-multi-thread",
"fs",
"io-util",
"signal",
"sync",
"time",
] }
= "0.1"
= "0.3"
# TUI — pinned to ratatui 0.29 because tui-textarea 0.7 hasn't bumped to 0.30 yet
= "0.29"
= "0.28"
= { = "0.7", = false, = [
"crossterm",
"ratatui",
"search",
] }
# Syntax highlighting. Pinned to 0.20 because the in-tree Typst grammar
# under `src/grammar/` is generated against tree-sitter 0.20's parser
# ABI, and the `Language` type is ABI-incompatible across major
# versions. Bump alongside any grammar refresh.
= "0.20"
= "0.20"
# AI clients (one crate covers Gemini, DeepSeek, OpenAI, Anthropic, …)
= "=0.6.0-beta.20"
# Bund scripting language (Vladimir's own VM stack). Exact-pinned to
# the versions inkhaven was built against — bump in lockstep when
# refreshing the scripting surface. See `src/scripting/`.
= "=0.7.0"
= "=0.14.0"
= "=0.38.0"
= "=0.49.0"
# bundcore / rust_multistackvm use easy_error for their function-pointer
# `Result<_, Error>` signatures; we need a direct dep to construct that
# error type at the FFI boundary in `scripting::stdlib::ink`.
= "1"
# Misc
= "1"
= "2"
= "0.1"
= { = "0.3", = ["env-filter"] }
= "6"
= { = "1", = ["v7", "serde"] }
= { = "0.4", = ["serde"] }
= "3"
= "0.1"
= "2"
= "1"
# PDF management + imposition (PDF-1). Pure-Rust, audited: no external
# apps / native libs (the only -sys crate in the tree, core-foundation-sys,
# is already vendored). `lopdf` is trimmed with default-features=false to
# drop the redundant `jiff` date lib — inkhaven already has time + chrono.
# `barcoders` (EAN-13) is kept rather than hand-rolling the encoder.
= { = "0.41", = false, = ["chrono", "rayon", "time"] }
= "2"
# Snowball stemmers (English, Russian, and 17 other languages). Used by the
# editor's Place/Character highlight overlay so a paragraph named
# "Москва" lights up "Москве", "Москвою", etc.
= "1.2"
= "1"
# 1.2.12+ — character-n-gram language detector used by the
# `editor.prompt_language_mode = "paragraph_detected"` mode in
# `App::active_prompt_language`. Offline, no network, ISO 639-3 →
# `iso_from_alpha3` maps to inkhaven's five supported languages
# (en/ru/fr/de/es). Cached on `OpenedDoc.detected_language` and
# only consulted when the paragraph has at least
# `editor.prompt_language_detection_min_chars` of non-whitespace
# text — whatlang is unreliable on very short inputs.
= "0.16"
# Markdown parsing for the AI pane's rendered output, plus the
# markdown→Typst translator that runs when the user applies an AI result to
# a paragraph via r/i/t/b.
= { = "0.13", = false }
# Zip backup / restore commands and the TUI auto-backup-on-exit path.
# Also reused by the `inkhaven export epub` pipeline to assemble the
# EPUB3 container — same `deflate` feature flag, no extra dep cost.
= { = "2", = false, = ["deflate"] }
# Pure-Rust Typst→LaTeX converter for `inkhaven export tex` and the
# Ctrl+B O extra-format pipeline. `default-features = false` drops
# the bundled clap CLI; we only consume the library API
# (`tylax::typst_document_to_latex` / `typst_to_latex`).
= { = "0.3", = false }
# Line diff for the F6 snapshot-diff view (press `V` on a row in
# the snapshot picker). Pure Rust, Apache-2.0 — uses Myers + LCS
# under the hood. `text` feature is what exposes `TextDiff` (its
# default-features pull in unicode + bytes which we don't need).
= { = "2", = false, = ["text"] }
# Scrivener import pipeline (1.2.4+).
# `quick-xml` parses the `.scrivx` binder XML (MIT).
# `rtf-parser-tt` is a maintained fork of rtf-parser with
# smart-quote / em-dash support that's essential for prose
# imports (MIT). Both pure Rust — single binary stays single.
= "0.36"
= "0.5"
# Human-friendly duration parsing for the auto-backup `max_age` config knob.
= "2"
= "1"
# Typewriter sounds (Enter / focus-out). The output stream is initialised
# lazily and silently skipped when the host has no audio device, so a
# headless build (CI, SSH session without audio forwarding) still works.
# Linux release builds need `libasound2-dev` at build time.
= { = "0.20", = false }
# 1.2.9+ — text-to-speech for Ctrl+B S / greeting / goodbye.
# Originally targeted at the `tts` crate (tts-rs) for cross-
# platform coverage, but its AVFoundation backend exhibits a
# per-process state bug on at least one macOS version: the first
# `Tts::default()` engine speaks once, every subsequent speak()
# returns Ok with a fresh utterance id but produces no audio.
# Drop-and-recreate fails entirely with "Operation failed".
# Falling back to `/usr/bin/say` subprocess (see `tui::say`) —
# each invocation is a fresh process, no shared state, reliable.
# Crate removed; non-macOS platforms fall back to the
# "TTS is macOS-only" modal until a different cross-platform
# backend lands in 1.3+.
# Image preview in the editor pane. The Picker is created lazily by
# querying the host terminal's graphics-protocol support; on terminals
# that don't speak kitty / sixel / iterm2 the half-block backend is the
# fallback. Disabled at the application level by `images.preview_enabled`.
= "5"
# In-memory decoder for the image bytes we feed to ratatui-image.
# Limit codecs to the formats we accept on import to keep the binary
# from pulling in TIFF / OpenEXR / etc.
= { = "0.25", = false, = [
"png",
"jpeg",
"gif",
"webp",
] }
# WORLD-4 P1 — the world-simulation geology layer. Both pure-Rust, MIT/Apache
# (RFC §11). `noise` synthesises the procedural heightmap; `delaunator` gives the
# Delaunay triangulation behind the Voronoi plate boundaries. `image` (above) is
# reused for DEM/heightmap import. (Determinism uses a tiny in-tree seeded PRNG,
# not `rand`, to keep `(definition, seed) -> identical output` airtight.)
= "0.9"
= "1"
# typst-syntax — Phase 1 of the typst-as-library plan (1.2.5+). Pulls
# in the Typst parser only (no eval, no layout, no render, no fonts).
# Used by `src/typst_check.rs` to surface parse errors at the source
# line/col while the user edits.
= "0.14"
# typst — full compiler umbrella for the in-process engine
# (Phase 2+3+4 combined). Activated at runtime by
# `typst_compile.engine = "inprocess"` in HJSON; the default
# `external` engine still shells out to the host's `typst` binary.
# Bringing it in unconditionally so the HJSON switch is a pure
# runtime decision — no rebuild required to flip engines.
= "0.14"
# typst-pdf — PDF emitter. Pairs with `typst::compile::<PagedDocument>()`
# to produce the same `.pdf` bytes the external CLI would.
= "0.14"
# typst-render — rasterise a compiled `Frame` to a PNG bitmap. Used
# by the Ctrl+V R "render paragraph" feature to paint the in-process
# compile's first page into the editor preview pane (via
# ratatui-image) and to write a full-DPI PNG on `S`.
= "0.14"
# resvg — rasterise hand-built SVG into a PNG bitmap. Used by
# the Ctrl+V W "story view" path: `src/story_view.rs` computes a
# twopi-style radial layout in pure Rust and emits SVG
# directly, then `usvg::Tree::from_str` parses it and
# `resvg::render` rasterises against the tiny-skia Pixmap we
# encode to PNG. tiny-skia is pulled in transitively via
# typst-render, so no extra direct dep needed.
= "0.47"
# typst-kit — convenience kit for tools that embed Typst. We enable:
# * `fonts` — fontdb-backed system-font searcher.
# * `embed-fonts` — bundle Computer Modern + Linux Libertine so
# manuscripts work on hosts without system fonts (gated at
# runtime by `typst_compile.bundle_fonts`; default on but
# skippable). Adds ~10 MB.
# * `downloads` — `ureq`-based (no tokio) HTTP for `@preview/<pkg>`
# resolution. Adds ~1 MB of TLS + http parsing.
# * `package` — package cache + `prepare_package` that gives us
# on-disk paths for resolved imports. Gated at runtime by
# `typst_compile.packages_enabled`; default on.
= { = "0.14", = false, = [
"fonts",
"embed-fonts",
"downloads",
"packages",
] }
# 1.2.8+ — embedded nushell as a library, powering the
# `Ctrl+Z o` floating shell pane. We pull the minimum
# practical set: protocol types, the eval engine, the parser
# (also used for syntax highlight of the input line), and
# nu-command's default declarations so built-ins like `ls`,
# `where`, `str`, `path` are available. No nu-cli (it owns
# reedline + the REPL, both of which conflict with ratatui).
# No nu-plugin (IPC; out of scope). Versions pinned to 0.113
# — nu's API moves between minor releases, so we'll re-pin
# deliberately during dependency review.
= "0.112"
= "0.112"
= "0.112"
= "0.112"
# Per-project shell command history (`.inkhaven/shell_history.db`).
# Bundled SQLite — no system dependency. Tiny (~1 MB linked).
= { = "0.32", = ["bundled"] }
# 1.2.20+ Phase G — cross-platform free-disk-space query for the
# low-disk pre-flight warning. Tiny (a thin statvfs / GetDiskFreeSpace
# wrapper over libc/winapi); no system dependency.
= "0.4"
# LANG-1 P5.2 — UFO (Unified Font Object) reader/writer, pure Rust. The
# ConLang suite's `language font-build` compiles a directory of glyph SVGs
# (parsed by the in-tree usvg) into a UFO font source. First dependency added
# for the writing-systems/fonts pillar (deps approved).
= "0.18.4"
# LANG-1 P5.3 — in-process UFO → TTF compilation. write-fonts (fontations)
# assembles the OpenType tables directly; kurbo supplies the BezPath that
# write-fonts' `SimpleGlyph::from_bezpath` consumes (cubics auto-quadified).
# Both are ALREADY in the tree (write-fonts via typst-pdf→krilla→subsetter,
# kurbo via typst) at these exact versions — pinning them direct adds no new
# compiled crate, and is far lighter than fontc (which would drag in
# fontir/fontbe/fontdrasil + a duplicate write-fonts). NB norad's own `kurbo`
# feature targets kurbo 0.13, incompatible with write-fonts' 0.12 — so the
# norad-contour → BezPath bridge is hand-rolled against this 0.12.
= "0.43"
= "0.12"
[]
# Compiles the in-tree Typst tree-sitter grammar (`src/grammar/`)
# during `cargo build`. See `build.rs`.
= "1"
# 1.2.10+ — parse `src/config.rs` at build time and
# emit a `(path, doc_comment)` lookup table for the
# config-TUI's in-process help pane. Only the
# `parsing` feature is needed; we don't need
# `extra-traits` or `proc-macro` for our walk.
= { = "2", = ["full"] }
[]
= "3"
# 1.3.32+ (road to 1.4.0) — property-based testing for the panic-prone parsers
# (calendar parse/format, HJSON config layering, language detection) + invariant
# checks. Pure-Rust, test-only; never linked into the shipped binary.
= "1"
# LANG-1 P5.3 — parse-round-trip validation of the TTFs emitted by
# conlang::writing::compile. Already in the tree (via typst/krilla); test-only,
# never linked into the shipped binary.
= "0.37"
# 1.2.18+ I.1.2 — criterion bench harness for the
# perf-pass scenarios. Subprocess-style benchmarks
# spawn the release binary against a generated fixture
# (`inkhaven gen-fixture`) and measure wall-clock time
# with statistical analysis. html_reports feature
# emits HTML output for inspection.
= { = "0.5", = false, = ["html_reports"] }
# 1.2.18+ I.1.2 — bench entries. Each is a standalone
# binary with `harness = false` (criterion provides its
# own main). Run with `cargo bench --bench <name>`.
# Requires INKHAVEN_BENCH_FIXTURE=<path> to a project
# generated via `inkhaven gen-fixture`. See
# `benches/README.md`.
[[]]
= "startup"
= false
[[]]
= "search"
= false
[]
= "thin"
= 1
= "symbols"
# Single-crate workspace. Kept explicit so any future vendored-source
# experiments under `vendor/` don't accidentally become workspace
# members. The grammar itself lives in-tree at `src/grammar/`.
[]
= ["."]