Octavo
The quantitative social science starter pack
日本語版 README はこちら / Japanese README
A command-line tool that converts a single Markdown source file into
Typst (papers, handouts, slides), Word (.docx), LaTeX, and Beamer, with unified
citations from one .bib file and a CSL style across all of them. Works equally well for
English and Japanese documents.
It was built for an academic workflow where the same underlying content often needs to become several different documents: a journal-formatted paper, an A4 handout for a class, and slides for a talk — all sharing the same section text, figures, tables, and bibliography, without hand-copying between them.
octavo init 2026-research && cd 2026-research
octavo new paper example-paper # add a paper (any number of them)
octavo new lecture example-lecture # add lecture notes
octavo build # build everything
octavo build example-paper --to docx # just Word
octavo build example-lecture --to typst-slides --compile # one PDF deck per session
| Output | Toolchain | Produces |
|---|---|---|
typst |
Typst | body.typ (paper, dropped into a hand-maintained main.typ) or a fully standalone .typ |
typst-slides |
Typst | a standalone slide-deck .typ (plain Typst, no packages) |
typst-notes |
Typst | the speaker script for that deck — A4, one page per slide, ::: notes underneath |
docx |
pandoc only | .docx |
latex |
LuaLaTeX (optional) | body.tex (paper, dropped into a hand-maintained main.tex) or a fully standalone .tex |
beamer |
LuaLaTeX (optional) | a standalone slide-deck .tex |
The defaults are typst, typst-slides and docx, which need no TeX. Add TeX Live only
if you use LaTeX or Beamer (bash setup.sh --with-tex).
Citations are unified through CSL: change one csl setting in
octavo.config.py and every format — LaTeX, Typst, Word — switches
bibliography style together. There is no per-format citation logic to keep
in sync.
1. Install
Octavo runs on Linux — a native Ubuntu/Debian box, an Ubuntu server, or
Ubuntu-on-WSL2 on Windows — and on macOS. setup.sh installs with apt on Linux
and with Homebrew on macOS (install Homebrew first).
git clone https://github.com/yoshida-kd/octavo.git ~/octavo
bash ~/octavo/setup.sh # installs pandoc / Typst / quarto / CJK fonts
octavo doctor # reports anything still missing
octavo selftest # one real end-to-end citation check
octavo selftest builds a small sample document in a temp directory
(English and Japanese references, an organization-as-author entry, a
table, a figure, cross-references, \poscite) and prints the actual
rendered citations and bibliography. Run it once per machine so you can
eyeball whether the CSL formatting looks right before trusting real output.
The language it speaks
Messages, --help and octavo doctor come out in English by default, and in
Japanese when your locale is Japanese (LC_ALL / LC_MESSAGES / LANG).
OCTAVO_LANG overrides that either way:
export OCTAVO_LANG=ja # Japanese, whatever the locale says
export OCTAVO_LANG=en # English, likewise
octavo doctor # the "display language" row shows what is in effect
This is the interface language only. What language a project is written
in is a separate setting — lang in octavo.config.py, which octavo init --lang ja|en writes — and it decides the manuscript templates, the generated
CLAUDE.md and README, and the citation locale. A Japanese interface with an
English paper is a perfectly ordinary combination.
Installing with pip instead
setup.sh + the symlink it makes is the recommended route, because it also
installs the external tools. If you only want the converter and will install
pandoc / Typst / quarto yourself, octavo is a normal Python package:
pipx install octavo-kit # or `pip install octavo-kit` inside a virtualenv
octavo doctor
(A plain pip install outside a virtualenv is refused on recent Ubuntu/Debian —
PEP 668 — which is why pipx is the suggestion: it gives the command its own
environment.)
It has no Python dependencies (standard library only) and carries its own
templates, so octavo init / octavo new work the same way. What you don't
get is setup.sh itself — octavo doctor then tells you to clone the repo for
it. On PyPI it is octavo-kit (PyPI does not allow the name octavo); the command
and the Python module are octavo.
setup.sh is idempotent — safe to re-run. It does not install TeX Live by
default; add it with bash setup.sh --with-tex (several GB) if you also use
LaTeX or Beamer. octavo doctor reports a missing TeX as a warning, not as
something missing. quarto (which renders the .qmd analysis) is installed by
default; skip it with --no-quarto. R and Python themselves are not installed —
which one a project uses is the project's business.
Version requirements:
| Minimum | Recommended | Why | |
|---|---|---|---|
| pandoc | 2.11 | 3.11 | 2.11 adds --citeproc (CSL citations); 3.1 adds Typst export; verified on 3.11 |
| Python | 3.9 | — | standard library only — no external packages required |
| LuaLaTeX (optional) | — | TeX Live 2021+ | needs texlive-lang-japanese for Japanese documents |
| Typst | — | 0.15 | Typst slides need 0.12+. CJK fonts aren't bundled (setup.sh installs them) |
On macOS, setup.sh runs brew install pandoc typst, brew install --cask quarto
and the font casks (font-biz-udmincho and friends), and --with-tex adds MacTeX
(mactex-no-gui, which has Japanese LuaLaTeX and Beamer included). octavo doctor
gives its hints as brew commands there. The CI macos job goes from a Homebrew
install to typeset PDFs on every push.
If you're on Windows, running Octavo inside WSL2 (Ubuntu) works well;
just make sure your source files live on the Linux filesystem (or a
Windows-side folder mounted under /mnt/...) so paths resolve normally.
The analysis environment is per project (.venv and renv)
pandoc, Typst, quarto and Octavo live once per machine (setup.sh above).
The packages an analysis uses belong to the project. octavo init lays the
Python groundwork — a requirements.txt, plus .venv/ and renv/library/ in
the project's .gitignore.
cd 2026-research
python3 -m venv .venv && source .venv/bin/activate # Python
pip install -r requirements.txt
Rscript -e 'renv::init()' # R
renv does not come with R, so install it once per machine (octavo doctor
shows whether it is there, under "Analysis"; the personal library folder has to
exist first or install.packages() has nowhere to write):
mkdir -p "$(Rscript -e 'cat(Sys.getenv("R_LIBS_USER"))')"
Rscript -e 'install.packages("renv", repos = "https://cloud.r-project.org")'
Git holds only the records (requirements.txt / renv.lock), never the
environment itself. Add a package to requirements.txt after pip install, and
run renv::snapshot() after install.packages(). The generated project's
README.md / CLAUDE.md say the same thing.
Typefaces
Every Typst output uses the same defaults (they live in one place,
octavo/backends/typst.py::FONTS):
| Japanese text | Latin letters and digits | Fallback | |
|---|---|---|---|
| Papers, A4 handouts | BIZ UDMincho | Libertinus Serif | Noto Serif CJK JP → Hiragino Mincho ProN |
| Slides, speaker scripts | BIZ UDGothic | Inter | Noto Sans CJK JP → Hiragino Kaku Gothic ProN |
The BIZ UD faces are the fixed-width ones (not the proportional BIZ UDP
variants). In a Japanese document the Latin font only takes Latin letters and
digits (covers: "latin-in-cjk"), so Japanese punctuation stays with the
Japanese font; in an English document the Latin font simply comes first.
setup.sh installs all of them (fonts-morisawa-bizud-gothic,
fonts-morisawa-bizud-mincho, fonts-inter, fonts-noto-cjk, or the matching
Homebrew casks on a Mac); on a machine without BIZ UD or Inter, Typst falls back
to Noto, and on a Mac with nothing added, to the Hiragino fonts macOS ships —
octavo doctor says which are missing. Override with typst_mainfont
(handouts) and typst_slides_font (slides and scripts), or edit a paper's
main.typ. Note that Google Fonts' "Noto Serif JP" is a different family name
from Noto Serif CJK JP and won't be found.
2. Documents and profiles
Octavo tracks "which source file, for what purpose, into which
format(s)" in the documents section of octavo.config.py.
The config octavo init writes registers each kind of manuscript by folder,
with a glob, so adding manuscripts with octavo new never requires editing it.
'documents': {
'papers': {'src': 'papers/*/paper.md', 'appendix': 'papers/*/appendix.md',
'profile': 'paper', 'targets': ['typst']},
'slides': {'src': 'slides/*.md', 'profile': 'slides',
'targets': ['typst-slides']},
'lectures': {'src': 'lectures/*.md', 'profile': 'handout',
'targets': ['typst', 'typst-slides'], 'split_slides': True},
},
# papers/example-paper/paper.md -> document example-paper (appendix: appendix.md next to it)
# slides/example-talk.md -> document example-talk
# lectures/example-lecture.md -> document example-lecture (decks example-lecture-01, -02, ...)
- A glob in
srcmakes each matching file its own document, named after what the first*matched (the folder for papers, the file name for slides). A*in the key is filled in with it ('lecture-*': {'src': 'lectures/*.md'}giveslecture-lecture01) appendixmay use*too; it is filled with the same match and attached only if the file exists- Two documents with the same name stop the run (even of different kinds —
octavo build <name>couldn't tell them apart) - A glob that matches nothing just means "none of this kind yet", so it stays quiet
- Single entries work too:
'week3': {'src': 'lecture03.md', 'profile': 'handout'}
If you don't write documents at all, it's inferred from whichever of
these files exist:
| File | Document name | Profile | Default output |
|---|---|---|---|
draft.md |
paper |
paper |
typst |
slides.md |
slides |
slides |
typst-slides |
handout.md |
handout |
handout |
typst |
A profile describes the document's purpose. It controls the table of
contents, section numbering, how the abstract is handled, and whether a
hand-maintained main.tex preamble is used.
| profile | TOC | Section numbers | Abstract | LaTeX/Typst output |
|---|---|---|---|---|
paper |
no | no (typesetting engine numbers it) | split into its own file | body.typ + your own main.typ (LaTeX: body.tex + main.tex) |
handout |
yes | yes | left in the body | one standalone .typ / .tex |
slides |
no | no | left in the body | one standalone .typ (Beamer: .tex) |
Only paper uses the external main.typ / main.tex pattern, because journal
submissions each need their own preamble tweaks. Handouts and slide decks
are faster to keep as single, self-contained files.
Output goes to one folder per format (build/typst/, ...). Standalone documents
sit side by side as <document>.typ, but the main.* pattern has fixed names
(main.typ, body.typ) that two papers would overwrite, so each paper gets
build/typst/<document>/.
octavo init and octavo new
octavo init creates the part that doesn't depend on what you write. Papers,
talks and lecture notes differ only in their manuscript template, so there is
just one project layout; manuscripts of any kind, and any number of them, are
added afterwards with octavo new.
2026-research/
CLAUDE.md working agreements for Claude Code (never type a result, ...)
README.md the human-facing walkthrough
octavo.config.py configuration (a glob per kind of manuscript, analysis registration)
literature.bib bibliography (exported from your reference manager)
requirements.txt the Python packages the analysis uses (installed into .venv)
data/raw/ raw data. read-only, gitignored
README.md where provenance, retrieval date and terms are recorded
data/derived/ cleaned data, written by the .qmd. gitignored
analysis/
analysis.qmd a worked example (estimate -> ov_value / ov_figure / ov_table)
octavo.R the analysis-side helper (ov_value / ov_figure / ov_table)
results/
analysis.json starter values so {{...}} resolves before R is ever run
figures/ tables/ with placeholder figures (a box with an ×)
refs/ material from elsewhere (codebooks, guidelines). kept in git
notes/ things you wrote (reading, referee notes). never in the manuscript
Everything octavo init / octavo new writes is an example and says so:
a octavo:example comment in the manuscripts, analysis.qmd and
literature.bib, _placeholder in results/analysis.json, and a figure that
is just a box with an ×. Delete the mark along with the content once it's yours;
octavo check counts what is left. Placeholder values are fatal — before the
analysis has ever run every {{...}} still resolves, so a PDF full of fake
numbers would otherwise typeset without a word of warning.
octavo new paper example-paper # papers/example-paper/: paper.md, appendix.md
# and the layout main.typ / main.tex
octavo new slides example-talk # slides/example-talk.md
octavo new lecture example-lecture # lectures/example-lecture.md
Every manuscript template uses {{...}} values and the placeholder figure, so
octavo build --compile succeeds right after octavo new. octavo new never
edits octavo.config.py; it re-reads the config afterwards to confirm the new
manuscript really is registered, and refuses (writing nothing) when the name is
already taken — even by a document of another kind.
The .gitignore excludes data/raw/ and data/derived/ so that large or
non-redistributable data can't be committed by accident. data/raw/README.md
is the one tracked exception — by convention, that's where data provenance
is written down. build/ is excluded in full: the hand-written layout
(main.typ / main.tex) lives next to the manuscript in papers/<name>/ and is
copied into build/ on every build, so deleting build/ never loses it.
Lecture notes: one file, an A4 handout and a deck per session
octavo new lecture example-lecture
octavo build example-lecture --to typst --compile # A4 handout (all sessions), straight to PDF
octavo build example-lecture --to typst-slides --compile # one PDF deck per session
octavo build example-lecture-03 --to typst-slides # just the third session
Lecture notes are a split_slides: True document, and each # heading is one
session. The handout is built from the whole file; the slides are built per
#, as separate documents (example-lecture-01, -02, ...).
- A session deck's title slide takes the
#heading as its title and the notes'titleas its subtitle; each##is a slide - Numbers follow the order of the
#headings. Inserting a session renumbers the ones after it, so give a heading an id to pin its name:# Second session {#second}→example-lecture-second - Anything before the first
#(an overall preamble) goes into the handout only - A
#inside a code block doesn't split;# Referencesand everything after is dropped
Content that should only appear in one output goes inside conditional blocks:
::: {.handout-only}
Fill-in-the-blank space and detailed footnotes: handout only.
( )
:::
::: {.slides-only}
Figures and short prompts: slides only.
:::
::: notes
Speaker notes (in the speaker script and in Beamer; never on the projected deck)
:::
| Marker | Kept for |
|---|---|
.slides-only / .only-slides |
typst-slides, typst-notes, beamer |
.handout-only / .paper-only |
latex, typst, docx (when that profile is active) |
.print-only |
latex, typst, docx |
.no-slides / .not-slides |
negation — dropped only for that format |
.notes / ::: notes |
beamer, typst-notes (dropped from the projected deck) |
Divs without a recognized marker (e.g. ::: {.warning}) pass through
unchanged.
3. Writing the source
| Element | Syntax | Notes |
|---|---|---|
| Heading | ## 1. Section Title / ### 1.1 Subsection / # 2. ... |
numbers become {#sec:1} labels; the typesetting engine renumbers them |
| Appendix | ## Appendix A. ... (or ## 付録A.…) |
becomes {#sec:appA} |
| Abstract | ## Abstract (or ## 要旨 / ## 概要) |
a *Word count: N words* line, if present, is split out with it |
| References heading | ## References (or ## 参考文献) |
dropped at conversion time — the bibliography is built from .bib instead |
| Citation | @key (inline), [@key; @key2] (parenthetical) |
|
| Possessive citation | \poscite{key} |
e.g. "Smith and Taylor's (2003)" |
| Analysis value | {{n_obs}} / {{coef:.2f}} |
filled in from what the .qmd wrote to results/*.json (§4) |
| Table | **Table 1. Caption** + a Markdown table |
replaced with an external .tex/.typ file if table_map has an entry |
| Figure |  + **Figure 1.** Caption |
the extension is swapped per output format (LaTeX gets .pdf) |
| Cross-reference | "Table 3", "Figure 1", "Section 4.1" (in either language) | becomes an automatic link in LaTeX/Typst |
| Title block | YAML front matter at the top of the file | title / author / institute / date |
Both English and Japanese cross-reference phrasing are recognized by
default (crossref_vocab: 'both'), and the source's own wording is
preserved: "Section 2" becomes Section~\ref{sec:2}, while a Japanese
"第2節" becomes 第\ref{sec:2}節. Typst gets the #ref(<sec:2>) form, because
@sec:2 would swallow any Japanese that follows it into the label name.
Figure extensions
You only need to drop one .png into your source. Each output format
looks for its preferred extension automatically:
| Format | Looks for |
|---|---|
latex / beamer |
figures/fig1_x.pdf |
typst / typst-slides / typst-notes / docx |
figures/fig1_x.png |
Configurable via figure_ext. A missing file is reported as a "missing
figure" at build time rather than failing silently.
External table files (for analysis-script pipelines)
If an R or Python script writes tables directly as .tex / .typ, map
table numbers to filenames under tables/ with table_map:
'table_map': {'1': 'tbl1_summary', '2': 'tbl2_models'},
For LaTeX this becomes \inputtable{../../tables/tbl1_summary}; for Typst,
#include "../../tables/tbl1_summary.typ". Cross-references are resolved
by reading the \label the external file actually defines, not by
guessing.
Word and slide decks can't embed external table files, so the Markdown
table is used as a fallback there even when table_map has an entry.
4. Analysis (Quarto) and manuscript, kept apart
The manuscript is .md; the analysis is .qmd. Numbers, figures and
tables that appear in the paper are never typed into the manuscript — the
analysis writes them to fixed locations, and each build pulls them in.
There is exactly one source for every number, so re-running an estimation
can't leave the prose describing the old one.
analysis/*.qmd --quarto render--> results/*.json the {{...}} in the text
figures/*.pdf|png figures
tables/*.tex|typ tables
manuscripts + the three above --octavo build--> Typst / Word / LaTeX / Beamer
4.1 The three channels
octavo init puts octavo.R (the helper) and an example analysis.qmd in
analysis/. Load it in the first chunk:
root <- Sys.getenv("OCTAVO_ROOT", unset = "")
source(if (nzchar(root)) file.path(root, "analysis", "octavo.R") else "octavo.R")
| What | In the analysis (.qmd) | In the manuscript (.md) |
|---|---|---|
| A number | ov_value("n_obs", nrow(d)) |
{{n_obs}} |
| A figure | ov_figure(p, "fig1_trend") |
 + **Figure 1.** Caption |
| A table | ov_table(tab, "tbl1_summary") |
**Table 1. ...** plus table_map: {'1': 'tbl1_summary'} in the config |
ov_value(name, x, fmt = NULL, note = NULL)— registers one value intoresults/<name of this .qmd>.json.ov_values(a = 1, b = 2)registers several.ov_figure(x, name, width, height, dpi)— writes both.pdfand.pngintofigures/, which is exactly what the defaultfigure_extmapping expects (LaTeX takes the PDF; Word and Typst take the PNG).xmay be a ggplot or a function that draws with base graphics.ov_table(x, name, caption, notes, align)— writes both.texand.typintotables/.xis a data frame, orlist(tex = ..., typ = ...)if you already have rendered strings. The LaTeX file carries\label{tab:<name>}and the Typst file carries<name>, so "Table 1" in the prose resolves to it.ov_pval(p)— formats a p-value by convention (0.023→.023,< .001).
4.2 Number formatting
How {{name}} renders is decided by the value's type.
| Value | Example in R | Output |
|---|---|---|
| Integer | nrow(d) (integer) |
1,523 (thousands separator) |
| Double | coef(m)[["x"]] |
0.342 (value_float_format, default .3f) |
| String | ov_pval(p) |
as-is |
Override with {{coef_x:.2f}} in the manuscript, or
ov_value("coef_x", x, fmt = ".2f") in the analysis; the manuscript wins.
The spec is a Python format spec (.2f, ,, .1%, ...).
A name with no value is left in place as {{name}} and reported —
silently deleting it would quietly break the text. octavo values lists the
whole cross-check.
4.3 When it runs
Before converting, octavo build compares each .qmd's mtime against the
last run recorded in results/.analysis-stamp.json, and runs
quarto render only for the ones that are newer.
'analysis': ['analysis/*.qmd'],
# to watch data files too:
'analysis': [{'src': 'analysis/main.qmd', 'deps': ['data/*.csv']}],
'analysis_deps': [], # dependencies shared by every .qmd
'analysis_to': None, # quarto render --to (None: whatever the .qmd says)
'analysis_args': [], # extra arguments passed to quarto
'analysis_auto': True, # False: only run via octavo analysis run
'results_dir': 'results',
'value_float_format': '.3f',
'value_thousands_sep': True,
octavo build --no-analysis— don't run anything; convert withresults/as-isoctavo build --force-analysis— re-run even when nothing changedoctavo analysis— what's stale, and how many values existoctavo analysis run [--force]— run the stale ones (manual ones included)octavo analysis run analysis/01-clean.qmd— run just that one (even if it isn't stale)octavo values [--unused] [--json]— cross-check{{...}}againstresults/octavo values --diff— what changed in the paper's numbers since the last run
Mark a slow .qmd 'manual': True (next section). octavo build then leaves
it alone and only tells you when it is stale; octavo analysis run runs it.
In VS Code all of this is a button. The sidebar's Analysis section lists each
.qmd as up to date, stale or manual, with a run button, and an open .qmd gets a
run button in the editor's title bar. The preview never runs the analysis: when
something is stale, a bar above the PDF says so, and its button runs it and rebuilds
the preview afterwards. Progress shows in a notification, quarto's output in the
Output panel (Octavo).
If quarto isn't installed, this step warns and is skipped (conversion
itself only needs pandoc). If quarto is installed and render fails, the
build stops rather than typesetting a paper around stale numbers. Use
--no-analysis if you want it to proceed anyway.
octavo analysis run snapshots the values just before it runs, so after a
re-estimation:
$ octavo values --diff
Compared with 2026-09-07 10:12:33
Changed (2):
n_obs 1,523 -> 1,608
coef_x 0.342 -> 0.311
The comparison is on the text that reaches the paper, so 0.3419 becoming
0.3421 is not a change (both print as 0.342 under .3f). When a number
moves, check the prose around it too ("slightly", "about").
4.4 Splitting the analysis, and adding an appendix
Multiple analysis files are the expected case. analysis takes globs, so
dropping in another .qmd is all it takes to register it.
'analysis': ['analysis/*.qmd'],
Each .qmd owns results/<name of that .qmd>.json. The split is invisible from
the manuscript — every {{name}} resolves the same way, because octavo reads
all of results/*.json into one namespace. If two .qmd files write the same
name, it warns and the later one wins, so check the source column of
octavo values when a number looks off.
When one .qmd produces another's input, do two things: order them, and
list the upstream output in the downstream deps.
'analysis': [
{'src': 'analysis/01-clean.qmd', 'manual': True}, # writes data/derived/ (slow)
{'src': 'analysis/02-model.qmd', 'deps': ['data/derived/*']},
{'src': 'analysis/03-robustness.qmd', 'deps': ['data/derived/*']},
],
Put slow steps that rarely change — data cleaning, say — in their own .qmd
and mark it 'manual': True. Neither octavo build nor the preview runs it; they
only say that a manual analysis is stale. Run it with octavo analysis run (every
stale one), octavo analysis run analysis/01-clean.qmd, or the sidebar button in
VS Code. When it rewrites data/derived/, the downstream .qmd (through deps)
turns stale and runs at the next build.
With a single glob (analysis/*.qmd), prefixing file names with 01-, 02-
gives the same ordering. Either way, if 01-clean rewrites data/derived/,
02-model re-runs within that same octavo build — staleness is re-checked
one file at a time, as each is reached.
Deleting or renaming a .qmd leaves its old results/<old name>.json behind. If
the manuscript still references those names, stale numbers keep flowing in
silently, so octavo analysis and octavo values report them as value files
with no matching .qmd.
The appendix
Keep the appendix in appendix.md in the paper's folder (octavo new paper
puts one there). 'appendix': 'papers/*/appendix.md' in documents attaches it
to the paper in the same folder.
# external table files for the appendix use their own map
'appendix_table_map': {'A1': 'tblA1_robustness'},
octavo build example-paper --to typst --appendix # writes both body.typ and appendix.typ
Then uncomment #include "appendix.typ" in papers/example-paper/main.typ
(or \appendix and \input{appendix} in main.tex).
The appendix gets exactly the same treatment as the body: value
substitution, citation resolution, conditional blocks and cross-references all
work, and octavo values, octavo checkbib and octavo outline all read it
(shown as example-paper (appendix)). Number it separately from the body — Appendix A.,
"Figure A1", "Table A1" — with file names like figA1_balance and
tblA1_robustness.
4.5 Using something other than R
The bundled helper is R, but octavo only ever looks at locations and
shapes, so Python or Julia work just as well if they follow the convention.
- Write
{"name": value}intoresults/<anything>.json. Integers and doubles are distinguished by JSON itself (1523vs1523.0select different default formatting). The form{"name": {"value": ..., "fmt": ".2f", "note": "..."}}is also read. Keys starting with_are ignored. - Figures go to
figures/<name>.pdfand.png; tables totables/<name>.texand.typ.
5. Citations (.bib → journal style)
Getting the .bib
Export BibTeX / BibLaTeX from your reference manager (Zotero or any other)
and put the file where bib_file points (literature.bib by default). With
Zotero, Better BibTeX's automatic export ("Keep updated") keeps the file in
step with your library and keeps citation keys stable.
Checking your bibliography
octavo checkbib # cross-check citation keys against .bib
octavo checkbib --list # list everything currently cited
octavo checkbib --unused # entries in .bib that are never cited
What it flags:
- citation keys used in the text but missing from
.bib(these render as??in the typeset output) — near-miss typos get a suggested key - missing year / author / journal name / no page range or DOI
- an organization name your reference manager has mistakenly split into given/family
name (e.g.
Organization, Example) - Japanese-language entries missing a
langid - duplicate imports of the same reference
Octavo never edits your .bib file. Fix issues in your reference manager. If
you've decided a flagged issue is fine as-is, silence it with a reason in
bib_accepted:
'bib_accepted': {('yamada2020', 'no page range or DOI'): 'bulletin uses a running number instead'},
octavo bib clean writes a separate, lighter copy with file /
abstract / keywords stripped out — your original file is never
touched.
Matching a journal's style
octavo csl list # styles you already have
octavo csl get modern-language-association # fetch one
octavo csl which # the one currently in use
One line in octavo.config.py affects every output format:
'csl': 'apa', # short alias, or: chicago / ieee / mla / nature / ...
Look up style IDs at https://www.zotero.org/styles. Dependent styles
(ones that just point at a parent style) are resolved automatically.
Fetched styles are cached under octavo/csl/, so subsequent builds
work offline.
chicago-author-date is pandoc's built-in default, so it works even with
no .csl file at all.
How \poscite works
CSL has no notion of a possessive citation form. Octavo reads the
author surnames straight out of .bib, assembles the possessive phrase
itself, and lets citeproc render only the year, parentheses, and link:
\poscite{smith2003} → Smith and Taylor's [-@smith2003]
→ Smith and Taylor's (2003)
\poscite{yamada2020} → 山田・田中[-@yamada2020]
→ 山田・田中(2020)
Joining multiple authors ("and" / "et al." / Japanese "・"/"ほか") is an approximation, not a full re-implementation of CSL's own naming rules — double-check the rendered output for journals that care about exact formatting.
6. Commands
octavo build [documents...] [--to formats] [--appendix] [--compile] [--no-citations]
[--no-analysis] [--force-analysis] [--json]
octavo watch [documents...] [--to formats] rebuild whenever a source or .qmd is saved
octavo documents [--json] list the registered manuscripts
octavo config [set KEY VALUE | unset KEY] [--json] show or change the common settings
octavo analysis [status|run] [--force] inspect / run the .qmd analyses
octavo values [--unused] [--diff [ref]] [--json] cross-check {{...}} against results/
octavo lint [--json] find results typed into the manuscript
octavo check [--strict] [--anonymous] one pre-submission audit
octavo bundle [--anonymous] [--replication] [--with-raw-data]
octavo review returned.docx [--comments|--insertions] [--json]
octavo data [status|hash] fingerprint the data (sha256)
octavo checkbib [--list] [--unused]
octavo bib clean a lighter copy of the .bib without fields typesetting ignores
octavo csl get|list|which [ID]
octavo doctor
octavo init <dir> [--lang ja|en]
octavo new paper|slides|lecture <name> add a manuscript
octavo template list|copy|diff [name] [--user] your own templates (§7)
octavo release <document> <label> [--dry-run] tag this version, PDF to a GitHub Release
octavo selftest [--to formats] [--csl ID] [--keep] verify citation rendering end-to-end
octavo reference-docx [out.docx] generate a Word style template
octavo outline [documents...] show the heading structure
octavo targets list available output formats
--to takes a comma-separated list, e.g. typst,docx. all and print
(shorthand for typst+docx) are also accepted. --compile runs
typst compile / latexmk on standalone documents, and for the paper
profile's Typst output compiles main.typ (after writing the appendix, if
any). For the paper profile's LaTeX output, run latexmk on main.tex
yourself — how it has to be run differs from journal to journal.
--no-citations skips citation resolution — useful for a fast visual
check while drafting. --no-analysis skips running the .qmd files and
converts with whatever is currently in results/, which is what you want
when an estimation is slow.
One audit before submitting
octavo check runs every check in one pass, separating fatal (the output
would be broken or wrong) from warnings (a human decides). By default it
exits non-zero only on fatal findings.
$ octavo check
[ ok ] sources papers/example-paper/paper.md, papers/example-paper/appendix.md, slides/example-talk.md
[ warn ] analysis 1 of 2 stale
analysis/02-model.qmd
-> octavo analysis run
[fatal ] figure files 1 missing
figures/fig2_effect.pdf
-> octavo analysis run, or drop it into figures/
It covers: sources exist / analysis freshness / orphaned value files /
unresolved {{...}} / placeholder values still in place / citation keys /
bibliography quality / figure and table files actually existing /
placeholder figures / template leftovers / results typed into the prose /
submission limits / data fingerprints / recorded analysis environment.
Use --strict in CI to fail on warnings too.
Placeholder values are the one fatal item here, because a project whose
analysis has never run still resolves every {{...}} and would typeset fake
numbers silently. octavo build also prints a warning on every run.
octavo lint reports the template leftovers and the "typed into the prose"
part in detail.
$ octavo lint
3 things that look like results typed into the manuscript
example-paper
51: N = 1,523 [sample size]
The coefficient was 0.342 (SE 0.081) with N = 1,523.
This is the machine-checkable version of "never type a result into the
manuscript". Tables, headings, code blocks, DOIs, links and {{...}} format
specs are skipped, and conventional constants (0.05, 1.96, ...) are ignored
by default; add anything else that isn't a result to lint_accepted.
Word and character limits
Put the journal's limits in octavo.config.py and octavo check enforces them.
Omit them and nothing is checked. Exceeding one is fatal — you cannot submit.
'word_limit': 8000, # body text
'char_limit': 20000, # body text, for Japanese journals
'abstract_word_limit': 150,
'abstract_char_limit': 400,
Counting happens after {{...}} substitution, with the abstract, references and
title block taken out of the body (the same counting octavo build reports as
[length]). The appendix is not counted.
Blind review
Produce a version with everything identifying removed. No new syntax — the
conditional blocks you already have simply gain an anonymous flag.
::: {.no-anonymous}
Acknowledgements: this research was funded by ...
:::
::: {.anonymous-only}
[Acknowledgements withheld for review]
:::
octavo build example-paper --anonymous
octavo bundle example-paper --anonymous # checks for leaks before packaging
octavo check --anonymous # also lists self-citation candidates
--anonymous does three things:
- drops
.no-anonymousblocks and keeps.anonymous-onlyones - removes
anonymous_drop_meta(author, institute, thanks, email by default) from the title-block metadata - writes the switch into
build/typst/<paper>/flags.typ(build/latex/<paper>/flags.texfor LaTeX). It is rewritten on every build, so a normal build always restores the non-anonymous version — you cannot leave it on by accident.
The title block lives in main.typ / main.tex, which Octavo never rewrites. The bundled
layouts (templates/paper/{ja,en}/main.{tex,typ}) wrap the author line in
\ifanonymous (#if anonymous for Typst), so they switch as they are.
octavo bundle --anonymous then checks the packaged files for leaks:
- a name outside any conditional → failure (you forgot)
- a name inside one → won't be typeset, but you're told it still sits in the
.texsource (for journals that want sources) - no
octavo build --anonymousbehind it → refused
Self-citations (cited works whose authors include you) are listed by
octavo check --anonymous. Whether to mask them depends on the journal, so it
only lists candidates.
Repackaging for submission
Journal submission systems rarely accept a directory tree. octavo bundle
rewrites image("../../figures/fig1.png") to image("fig1.png") (for LaTeX,
\includegraphics{../../figures/fig1.pdf} to {fig1.pdf}), collects every
referenced file into one place, and zips it. Typst is the default; pass
--to latex to package a LaTeX submission. It packages one paper, so give
its name (it can be left out when the repository has a single paper).
octavo build example-paper
octavo bundle example-paper # submission-example-paper.zip
octavo bundle example-paper --dir --out example-talk # a folder instead of a zip
\newcommand definitions and comment lines in main.tex, and // comment lines
in main.typ, are not followed, and
an \input guarded by \IfFileExists{...} isn't counted as missing when it
isn't there. build/ itself is never touched — the work happens on a copy.
Reading a coauthor's Word edits
Coauthors mark up Word and send it back. octavo review lists what they did.
octavo review 20260907_draft_tanaka.docx
octavo review returned.docx --comments # comments only
20260907_draft_tanaka.docx: 3 changes in 2 paragraphs
Hanako Tanaka: 3
paragraph 1: The sample had 1,523 cases and the coefficient was 0.342 (statistically sign
[inserted] Hanako Tanaka 2026-09-05 10:11
(statistically significant)
paragraph 2: We then turn to the robustness checks.
[deleted] Hanako Tanaka 2026-09-05 10:14
in detail
[comment] Hanako Tanaka 2026-09-05 10:20
Cite the new replication here?
There is no round trip, deliberately. In the returned .docx, {{n_obs}}
is already the literal text "1,523". Converting docx → md would, at that
moment, break the one thing this tool exists to guarantee. So it extracts the
tracked changes and comments only and you apply them to paper.md yourself.
That is the only safe direction, and this design is the consequence.
It reads the .docx (a zip) directly, so no pandoc and no extra dependency.
Keeping the version you sent (octavo release)
build/ stays out of git — everything in it can be rebuilt from what git holds.
What cannot be rebuilt exactly is the PDF you actually sent: a newer Octavo,
pandoc, Typst or font changes the result. So at the milestones (to coauthors,
submitted, presented) put that PDF somewhere outside your machine:
octavo release example-paper v1-submitted
This tags the current commit example-paper-v1-submitted, pushes the tag, and makes
a GitHub Release with the freshly typeset PDF (and Word file, if the document
builds one) attached as example-paper-v1-submitted.pdf. The release notes record
the commit and the Octavo / pandoc / Typst versions. The files live on GitHub,
not in the git history, so the repository doesn't grow.
It refuses — listing every reason at once — when there are uncommitted changes
(the tag has to point at what was typeset), the analysis is stale or still the
placeholders, or the tag already exists. It builds on the spot and never runs the
analysis (that would change results/ after the commit). --dry-run checks and
builds but tags and uploads nothing; --anonymous releases the blind version.
It needs the GitHub CLI (gh auth login) and a
GitHub remote called origin.
Showing what changed in a revision (R&R)
Give octavo values --diff a git tag or commit and it compares against that
version — the tags octavo release makes work as they are.
octavo release example-paper v1-submitted # at submission (or just git tag it)
# ... review, revise ...
octavo values --diff example-paper-v1-submitted
Compared with git example-paper-v1-submitted
Changed (2):
n_obs 1,523 -> 1,608
coef_x 0.342 -> 0.311
No new versioning machinery: git already holds the manuscript diff, so Octavo only supplies the part git couldn't give you — the numbers as of that version.
Fingerprinting the data
_session records the software the analysis ran under. The other half of
reproducibility is whether it ran on the same data, and that is this.
octavo data hash # record what's in data/ right now
octavo data status # has anything drifted from the record?
data/raw/ and data/derived/ are gitignored, which makes
data/HASHES.json the only surviving record of which data was used (paired
with the provenance in data/raw/README.md). octavo check reports drift too.
The replication package (after acceptance)
Submission packaging collects "what typesetting needs". A replication package collects "what reproducing this result needs" — a different set.
octavo bundle --replication # replication.zip
octavo bundle --replication --with-raw-data # include the raw data too
It contains analysis/ (helper included), results/, figures/, tables/,
data/derived/, data/HASHES.json, octavo.config.py, literature.bib, and a
generated README with the recorded _session. Raw data is excluded by
default — it often can't be redistributed — so --with-raw-data is explicit.
Use replication_exclude globs to drop individual files.
7. Making it yours: templates and Word styles
Your own templates
Everything Octavo writes or typesets from — the project skeleton, the
manuscript templates, a paper's main.typ / main.tex, the slide and handout
looks — is a file under templates/, and each can be replaced with your
own. Put a file with the same relative path in <project>/templates/ (that
project only) or ~/.config/octavo/templates/ (all your projects). Octavo
looks in the project first, then your folder, then the bundled templates, and
uses the first it finds, as a whole.
octavo template list # what is in use, and from where
octavo template copy slides/typst-slides.typ # into this project's templates/, to edit
octavo template copy paper/en/main.typ --user # your journal layout for every new paper
octavo template diff slides/typst-slides.typ # yours against the bundled one
A few that are worth knowing:
| Template | Used by | Replace it to |
|---|---|---|
slides/typst-slides.typ, slides/typst-notes.typ |
every slide build | change the look of decks and scripts |
paper/<lang>/main.typ, main.tex |
octavo new paper |
start every paper from your usual layout |
manuscripts/<lang>/*.md |
octavo new |
start manuscripts from your own skeleton |
project/<lang>/… |
octavo init |
change what a new project contains — files you add there are written into every new project too |
handout/handout-header.tex, slides/beamer-header-<lang>.tex |
LaTeX / Beamer builds | change the LaTeX preamble |
A copy doesn't follow later changes to the bundled template. After updating
Octavo, octavo template diff <name> shows what differs. The full list is in
templates/README.md.
Word styles
octavo reference-docx reference.docx
This produces pandoc's default template. Open it in Word, adjust the
Heading 1, Body Text, Table Caption, etc. styles to taste, save it,
and point the config at it:
'docx_reference': 'reference.docx',
Its actual content doesn't matter — only the style definitions are used. Word has no LaTeX-style auto-numbered cross-references, so table/figure numbers are written into the caption as literal text (e.g. "Table 1. Descriptive Statistics"), and in-text mentions like "Table 3" stay as plain text too. As long as the numbering in your source is sequential, these stay consistent.
Section numbers work the same way: the numbers already present in the
source (the "1." in ## 1. Introduction) are restored as literal text.
pandoc's --number-sections doesn't work for Word, and renumbering from
scratch would break sequences like "1, 2, Appendix A" — so the source's
own numbering is used instead.
8. Differences between formats (worth knowing)
| LaTeX | Typst | Word | |
|---|---|---|---|
| Japanese fonts | haranoaji ships with TeX Live — no setup needed |
not bundled — setup.sh installs them (see Typefaces; check with typst fonts) |
handled by Word itself |
| Section numbers | typesetting engine numbers them | typesetting engine numbers them | source's own numbers restored as text |
| Table/figure numbers | automatic | automatic | written into the caption as text |
| Cross-references | linked via \ref |
linked via #ref(<label>) |
plain text |
| External table files | \inputtable |
#include |
not supported |
**bold** inside captions |
rendered literally | rendered as actual bold | — |
| Bibliography | CSLReferences environment (needs a definition in main.tex) |
embedded in the body | plain paragraphs |
Setting typst_citations: 'native' hands citations to Typst's own
bibliography() function instead of CSL (driven by main.typ). The
default is 'csl'.
Slide decks don't get a bibliography list by default (no room in the
frame). To include one, add a heading and an empty ::: {#refs} at the
end of the slide manuscript and set slides_bibliography: True.
## References
::: {#refs}
:::
Typst slides
typst-slides is plain Typst with no packages, so compiling needs no
network (Typst 0.12+). There are no animations or incremental reveals.
- With both
#and##headings,#makes a section divider and##a slide; with a single heading level, each heading is a slide. A#heading followed directly by content becomes a titled slide rather than a divider - Figures are fitted into the remaining height of the slide; figures and tables are not numbered
- The title slide comes from
title/subtitle/author/institute/datein the front matter - The look lives in
templates/slides/typst-slides.typ; to change it, copy it withoctavo template copy slides/typst-slides.typand edit the copy (§7). The config keys are:
| Key | Default | What it changes |
|---|---|---|
typst_slides_aspect |
'16-9' |
'16-9' / '4-3' |
typst_slides_font |
BIZ UDGothic + Inter | font list, used as-is when set (see Typefaces) |
typst_slides_numbering |
None |
heading numbers, as a Typst numbering string ('1.' / '1.1'); '1.1' needs # sections |
typst_slides_section_slides |
True |
False makes a # section advance the counter without producing a divider slide |
typst_slides_accent |
'#0e2f92' |
accent colour, or None for plain black. Any non-None value switches the look (below) |
typst_slides_running_header |
True |
prints the current # section small in the top-left corner (the deck's title where there is none — a lecture session, say) |
The look only changes when an accent colour is set — which it is by
default (the blue above). Set typst_slides_accent to None for the plain
look instead: bold titles, • ‣ – markers, a plain page number. With an
accent, titles are set in the accent colour instead of bold, list markers
become ▶, the page number becomes "4 / 11", and links are coloured too.
List indentation is fixed regardless of the accent — nested items used to
start exactly where their parent's text did, so the hierarchy was unreadable.
- Five theorem-like helpers are available from a raw
```{=typst}passthrough block (values in{{…}}still resolve inside one):#case[…]/#question[…]/#aside[…]share one counter that resets each#section and includes the section number (e.g.Case 2.1);#nb[…]/#memo[…]are unnumbered.#smallgray[…]sets small gray text (for a source line under a figure, say). Beamer does not have these.
The speaker script (typst-notes)
::: notes is dropped from the projected deck — there is nowhere on a slide to
put it. typst-notes is where it goes: the same source, same conditional
blocks, but A4 portrait with one page per slide and the notes in a ruled box
underneath.
octavo build example-lecture-03 --to typst-notes --compile
It reuses every typst_slides_* setting (same meta block), and the five
theorem-like helpers work there too — a deck that uses #case[…] builds as a
script unchanged. The look lives in templates/slides/typst-notes.typ, and is
changed the same way (§7). Figures are capped at 6cm high so
the notes still fit on the page. Add it to a document's targets to get it on
every build, or ask for it with --to when you need it.
LaTeX and Japanese
For Japanese documents, standalone LaTeX/Beamer output deliberately skips
loading babel. Citation localization needs the lang metadata, but
pandoc's template tries to map that into a babel language name and emits
a broken line in the process (Japanese typesetting is handled by
luatexja, which doesn't need babel at all).
9. Project layout
octavo/
octavo entry point (put this on your PATH)
pyproject.toml packaging (pip install; distribution octavo-kit, command octavo)
setup.sh install script for Linux / WSL2 (apt) and macOS (Homebrew)
config.example.py configuration template (English)
config.example.ja.py the same template in Japanese — keep the two in key-for-key sync
octavo/
cli.py subcommands
config.py loads octavo.config.py and defines defaults
md.py format-agnostic preprocessing (headings, tables, figures, conditional blocks)
values.py substitutes analysis values into the {{...}}; diffs against the last run
analysis.py staleness check for .qmd files and quarto render
lint.py finds results typed into the manuscript
audit.py octavo check (gathers every check)
bundle.py octavo bundle (submission and replication packages)
review.py reads tracked changes out of a coauthor's .docx
ghrelease.py octavo release (tag a version, PDF to a GitHub Release)
dataset.py data fingerprints (data/HASHES.json)
build.py the preprocess -> pandoc -> postprocess pipeline
pandocrun.py invoking pandoc and checking its version
bib.py .bib parsing and validation
csl.py resolving and fetching CSL styles
paths.py where the bundled templates live (repo checkout vs. pip install)
tmpl.py finds a template: the project's, then yours, then the bundled one
confedit.py octavo config: which settings the sidebar offers, and the line-level edit
i18n.py the display language (OCTAVO_LANG); t() wraps every message
lang_ja.py the Japanese of every message (the source strings are English)
check.py octavo checkbib
doctor.py octavo doctor
selftest.py octavo selftest
scaffold.py octavo init (the research project tree, placeholder figures) and octavo new (add a manuscript)
backends/
base.py the Backend base class and shared cross-reference logic
latex.py typst.py typst_slides.py typst_notes.py beamer.py docx.py
templates/ every template, each replaceable (§7; templates/README.md)
project/ the project skeleton octavo init writes (common/ + ja/ or en/)
common/analysis/octavo.R the analysis-side helper (ov_value / ov_figure / ov_table)
manuscripts/ what octavo new writes (paper / appendix / slides / lecture)
paper/ main.typ / main.tex per language, and csl-preamble.tex
slides/ handout/ the looks used by octavo build (Typst, Beamer, LaTeX handout)
csl/ cache of fetched CSL styles
tests/test_octavo.py unit tests
Adding a new output format means writing one file under backends/
and registering it in REGISTRY — build.py never branches on format
name.
python3 tests/test_octavo.py # anything needing a missing tool is skipped automatically
octavo init also drops in placeholder figures (a PNG and a PDF showing a
box with an ×, carrying a octavo:placeholder mark that octavo check reads)
that the example source references, so octavo build --compile succeeds
end-to-end even before you've added real figures.
10. What is tested, and what to check yourself
Please read this section before trusting the output for something that matters.
Covered by the test suite, run in CI on every push:
- Building every format from sample sources (Typst, Typst slides and scripts,
Word, and the LaTeX / Beamer source), and Typst all the way to a PDF with
typst compile— CSL citations, figures, tables, cross-references, themain.typpattern and standalone documents - A project made from scratch with
octavo init+octavo new(a paper, a talk, lecture notes), from a wheel installed outside the repository - Heading-to-label conversion, English/Japanese cross-references, conditional-block filtering, table/figure substitution, front-matter handling
.bibparsing and validation,\posciteexpansion, CSL style resolution from cache- Value substitution (
{{...}}formatting, missing names, collisions between result files, leaving code blocks alone) and.qmdstaleness detection (mtime vs. stamp, dependency tracking, chained.qmdfiles) quarto renderof the bundled example analysis withoctavo.R, checking what it writes toresults/,figures/andtables/- Raw-number detection, the
octavo checkverdicts, andoctavo bundlepath flattening - Blind-review filtering (conditional blocks, title metadata, the build flag and
leak detection), submission length limits, data fingerprints, reading tracked
changes out of a
.docx, diffing values against a git ref, and the contents of the replication package - The VS Code extension compiling and packaging, and the JSON contract between it and the CLI (its behaviour inside VS Code is not automated)
Not exercised automatically — check these on your own machine the first time:
- Actual CSL citation rendering. This depends on the version of
pandoc installed on your system; older pandoc (< 2.11) lacks
--citeprocentirely. Runoctavo selftest— that's exactly what it's for. - Japanese fonts in Typst. Typst bundles no CJK fonts; without BIZ UD
or Noto CJK another font is substituted, so look at your first PDF
(
octavo doctorreports which of the default typefaces Typst can see). - LuaLaTeX compilation. Nothing in CI compiles LaTeX. The
ltjsarticle/luatexja/ Beamer theme combination should be visually checked the first time, on a machine with the right fonts installed (octavo doctorreports what's missing). - Your own analysis environment. The example analysis runs in CI, but whether your R version and packages work is worth checking once.
- Live CSL style downloads — cached resolution is well-tested; live fetches depend on your network access.
If you hit something that looks wrong, please open an issue with the
octavo doctor output and the pandoc/TeX/Typst versions involved.
11. VS Code extension
vscode-extension/ contains a VS Code extension that wraps this CLI. It
adds command-palette access to build / checkbib / doctor and friends,
@-triggered citation completion from your .bib file in Markdown,
{{-triggered completion and hovers for the values your analysis produced,
and red squiggles under citation keys and {{...}} names that don't resolve. All validation logic
lives in the Python side (octavo checkbib --json) — the extension is a
thin client, not a second implementation. When VS Code itself runs on
Windows, the extension shells out through wsl.exe to run octavo inside
WSL automatically. It also has a live preview: the typeset PDF sits in the
column beside the manuscript and rebuilds on every save, and lecture notes get
a third column with the deck for the session the cursor is in (switchable to
the speaker script, or off for a plain two-column layout).
See vscode-extension/README.md for details.
Its interface is English by default and Japanese when VS Code runs in Japanese, and it passes the same language on to the CLI.
cd vscode-extension
npm ci && npm run compile
npx @vscode/vsce package # produces a .vsix — install via "Install from VSIX..." in VS Code
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md.
License
MIT — see LICENSE.
Release files for octavo-kit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| octavo_kit-0.1.0.tar.gz | 315.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| octavo_kit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 586.8 kB
Release files / octavo_kit-0.1.0.tar.gz
| Download URL | octavo_kit-0.1.0.tar.gz |
|---|---|
| Size | 315.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee9251ff35c87437d37524cb21f0e61e80fe5dee23d36697754b1a2e72df6f9b
|
|
BLAKE2b-256 checksum How to use checksums |
a0687199548812c160aa556f0091d3e7c3b2a49bcb6c93f26a1d9c521fb7913c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / octavo_kit-0.1.0-py3-none-any.whl
| Download URL | octavo_kit-0.1.0-py3-none-any.whl |
|---|---|
| Size | 270.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
09bc0d1c8ca2a06384144e59c73469d83783cdbf79686bb4cb379e071a032265
|
|
BLAKE2b-256 checksum How to use checksums |
0b204b402e5b7821aede32992f49547faaa55aa315ae785629e41373ca158b20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log