demolab-cli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- demolab_cli/CHANGELOG.md +445 -0
- demolab_cli/VERSION +1 -0
- demolab_cli/__init__.py +10 -0
- demolab_cli/_paths.py +63 -0
- demolab_cli/build.py +284 -0
- demolab_cli/cli.py +414 -0
- demolab_cli/deploy/deploy.yml +61 -0
- demolab_cli/deploy/preview.yml +56 -0
- demolab_cli/devserver.py +380 -0
- demolab_cli/guides/AUTORESEARCH-RULES.md +149 -0
- demolab_cli/guides/GLOSSARY.md +55 -0
- demolab_cli/guides/HOUSESTYLE.md +108 -0
- demolab_cli/guides/RULES.md +142 -0
- demolab_cli/guides/SLIDES.md +145 -0
- demolab_cli/guides/STRUCTURE.md +78 -0
- demolab_cli/guides/SUPPORT.md +36 -0
- demolab_cli/overlay.py +47 -0
- demolab_cli/runbooks/AUTORESEARCH.md +67 -0
- demolab_cli/runbooks/DOCTOR.md +116 -0
- demolab_cli/runbooks/EMBED-DOCS.md +39 -0
- demolab_cli/runbooks/FROM-JUPYTER.md +84 -0
- demolab_cli/runbooks/FROM-PAPER.md +48 -0
- demolab_cli/runbooks/GETTING-STARTED.md +281 -0
- demolab_cli/runbooks/GROUND-CLAIMS.md +166 -0
- demolab_cli/runbooks/LINT.md +176 -0
- demolab_cli/runbooks/MIGRATE-CODE.md +49 -0
- demolab_cli/runbooks/MIGRATE-STACK.md +50 -0
- demolab_cli/runbooks/NEXT.md +52 -0
- demolab_cli/runbooks/NIGHT-SHIFT.md +68 -0
- demolab_cli/runbooks/PLAN.md +64 -0
- demolab_cli/runbooks/RED-TEAM.md +72 -0
- demolab_cli/runbooks/STEELMAN.md +44 -0
- demolab_cli/runbooks/TOUR.md +42 -0
- demolab_cli/runbooks/UPDATE.md +84 -0
- demolab_cli/scaffold/demo/artifacts/data/ar018/swe-bench.svg +1 -0
- demolab_cli/scaffold/demo/artifacts/data/exp000/lif.svg +1103 -0
- demolab_cli/scaffold/demo/artifacts/data/exp000/net.png +0 -0
- demolab_cli/scaffold/demo/artifacts/data/exp000/numbers.json +38 -0
- demolab_cli/scaffold/demo/artifacts/data/exp000/run.sh +3 -0
- demolab_cli/scaffold/demo/artifacts/data/exp001/eif.svg +1122 -0
- demolab_cli/scaffold/demo/artifacts/data/exp001/enet.png +0 -0
- demolab_cli/scaffold/demo/artifacts/data/exp001/numbers.json +40 -0
- demolab_cli/scaffold/demo/artifacts/data/exp001/run.sh +3 -0
- demolab_cli/scaffold/demo/artifacts/data/exp002/cartpole.mp4 +0 -0
- demolab_cli/scaffold/demo/artifacts/data/exp002/numbers.json +18 -0
- demolab_cli/scaffold/demo/artifacts/data/exp002/run.sh +3 -0
- demolab_cli/scaffold/demo/artifacts/data/exp003/double_pendulum.mp4 +0 -0
- demolab_cli/scaffold/demo/artifacts/data/exp003/numbers.json +21 -0
- demolab_cli/scaffold/demo/artifacts/data/exp003/run.sh +3 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar000.pdf +546 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar003.pdf +773 -1
- demolab_cli/scaffold/demo/artifacts/pdfs/ar004.pdf +2846 -2
- demolab_cli/scaffold/demo/artifacts/pdfs/ar005.pdf +4216 -5
- demolab_cli/scaffold/demo/artifacts/pdfs/ar006.pdf +1200 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar010.pdf +718 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar011.pdf +530 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar012.pdf +634 -1
- demolab_cli/scaffold/demo/artifacts/pdfs/ar013.pdf +575 -1
- demolab_cli/scaffold/demo/artifacts/pdfs/ar014.pdf +0 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar015.pdf +0 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar016.pdf +520 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/ar017.pdf +875 -1
- demolab_cli/scaffold/demo/artifacts/pdfs/ar018.pdf +871 -1
- demolab_cli/scaffold/demo/artifacts/pdfs/book.pdf +10343 -6
- demolab_cli/scaffold/demo/artifacts/pdfs/exp000.pdf +2036 -4
- demolab_cli/scaffold/demo/artifacts/pdfs/exp001.pdf +2226 -2
- demolab_cli/scaffold/demo/artifacts/pdfs/exp002.pdf +683 -0
- demolab_cli/scaffold/demo/artifacts/pdfs/exp003.pdf +767 -0
- demolab_cli/scaffold/demo/demolab.yaml +26 -0
- demolab_cli/scaffold/demo/experiments/exp000.py +106 -0
- demolab_cli/scaffold/demo/experiments/exp001.py +106 -0
- demolab_cli/scaffold/demo/experiments/exp002.py +74 -0
- demolab_cli/scaffold/demo/experiments/exp003.py +73 -0
- demolab_cli/scaffold/demo/experiments/playground.py +157 -0
- demolab_cli/scaffold/demo/site/CNAME +1 -0
- demolab_cli/scaffold/demo/site/landing.typ +38 -0
- demolab_cli/scaffold/demo/tools/mujoco/test_mujoco.py +42 -0
- demolab_cli/scaffold/demo/tools/mujoco/tool.py +393 -0
- demolab_cli/scaffold/demo/tools/neuron/test_neuron.py +106 -0
- demolab_cli/scaffold/demo/tools/neuron/tool.py +526 -0
- demolab_cli/scaffold/demo/writings/ar000.typ +86 -0
- demolab_cli/scaffold/demo/writings/ar003.typ +82 -0
- demolab_cli/scaffold/demo/writings/ar004.slide.typ +418 -0
- demolab_cli/scaffold/demo/writings/ar005.slide.typ +294 -0
- demolab_cli/scaffold/demo/writings/ar006.typ +148 -0
- demolab_cli/scaffold/demo/writings/ar010.typ +54 -0
- demolab_cli/scaffold/demo/writings/ar011.typ +32 -0
- demolab_cli/scaffold/demo/writings/ar012.typ +52 -0
- demolab_cli/scaffold/demo/writings/ar013.typ +47 -0
- demolab_cli/scaffold/demo/writings/ar014.typ +56 -0
- demolab_cli/scaffold/demo/writings/ar015.typ +33 -0
- demolab_cli/scaffold/demo/writings/ar016.typ +29 -0
- demolab_cli/scaffold/demo/writings/ar017.typ +98 -0
- demolab_cli/scaffold/demo/writings/ar018.typ +128 -0
- demolab_cli/scaffold/demo/writings/exp000.typ +77 -0
- demolab_cli/scaffold/demo/writings/exp001.typ +81 -0
- demolab_cli/scaffold/demo/writings/exp002.typ +47 -0
- demolab_cli/scaffold/demo/writings/exp003.typ +61 -0
- demolab_cli/scaffold/demo-manifest.json +54 -0
- demolab_cli/scaffold/root/AGENTS.md +54 -0
- demolab_cli/scaffold/root/CLAUDE.md +18 -0
- demolab_cli/scaffold/root/README.md +28 -0
- demolab_cli/scaffold/root/github/workflows/tests.yml +21 -0
- demolab_cli/scaffold/root/gitignore +30 -0
- demolab_cli/scaffold/root/pyproject.toml +32 -0
- demolab_cli/scaffold/skeleton/HOUSESTYLE.local.md +25 -0
- demolab_cli/scaffold/skeleton/artifacts/data/.gitkeep +0 -0
- demolab_cli/scaffold/skeleton/artifacts/pdfs/.gitkeep +0 -0
- demolab_cli/scaffold/skeleton/demolab.yaml +40 -0
- demolab_cli/scaffold/skeleton/experiments/helpers/__init__.py +1 -0
- demolab_cli/scaffold/skeleton/experiments/helpers/provenance.py +78 -0
- demolab_cli/scaffold/skeleton/experiments/helpers/style.py +39 -0
- demolab_cli/scaffold/skeleton/experiments/helpers/test_provenance.py +37 -0
- demolab_cli/scaffold/skeleton/tools/.gitkeep +0 -0
- demolab_cli/scaffold/skeleton/writings/.gitkeep +0 -0
- demolab_cli/scaffold/starters/monte-carlo-pi/README.md +41 -0
- demolab_cli/scaffold/starters/monte-carlo-pi/exp000.py +110 -0
- demolab_cli/scaffold/starters/monte-carlo-pi/exp000.typ +63 -0
- demolab_cli/slides.py +42 -0
- demolab_cli/typ/cite-popover.js +38 -0
- demolab_cli/typ/favicon.svg +8 -0
- demolab_cli/typ/lib.typ +518 -0
- demolab_cli/typ/main.typ +92 -0
- demolab_cli/typ/style.css +263 -0
- demolab_cli-1.0.0.dist-info/METADATA +101 -0
- demolab_cli-1.0.0.dist-info/RECORD +129 -0
- demolab_cli-1.0.0.dist-info/WHEEL +4 -0
- demolab_cli-1.0.0.dist-info/entry_points.txt +3 -0
- demolab_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
demolab_cli/CHANGELOG.md
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to the **demolab engine** (the `demolab-cli` package). Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com); demolab uses [SemVer](https://semver.org):
|
|
5
|
+
|
|
6
|
+
- **major** — a break that may need edits to *your* content (the tool↔experiment contract, the
|
|
7
|
+
`meta` schema, or import paths). The UPDATE runbook flags it for review.
|
|
8
|
+
- **minor** — a new backward-compatible capability.
|
|
9
|
+
- **patch** — fixes and tweaks.
|
|
10
|
+
|
|
11
|
+
The current version lives in [`VERSION`](VERSION); `demolab version` prints it. On *"update demolab"*
|
|
12
|
+
the runbook shows the entries between your version and the latest.
|
|
13
|
+
|
|
14
|
+
## [Unreleased]
|
|
15
|
+
|
|
16
|
+
## [1.0.0] — 2026-07-11
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **demolab is now a normal Python package: `demolab-cli` on PyPI.** The git-clone
|
|
20
|
+
distribution is gone. A new lab starts with **`uvx demolab-cli init`** in an empty
|
|
21
|
+
directory (root files + structure + `git init` — born the user's own, no upstream history
|
|
22
|
+
to strip); the engine — build code, Typst templates, runbooks, guides, scaffold + demo —
|
|
23
|
+
lives in site-packages, and a lab contains only user content. **Updating the engine is a
|
|
24
|
+
dependency bump** (`uv lock --upgrade-package demolab-cli && uv sync`); `uv.lock` pins the
|
|
25
|
+
engine version for reproducibility. The clone-to-`/tmp` vendor-copy UPDATE flow and the
|
|
26
|
+
root-file "reconcile" tier are gone with it.
|
|
27
|
+
- **BREAKING — writings import the staged lib:** `#import "/demolab-engine/build/lib.typ"`
|
|
28
|
+
becomes `#import "/.demolab/lib.typ"`. The CLI materialises a tiny gitignored `.demolab/`
|
|
29
|
+
dir (lib.typ + web assets + a version stamp) at the lab root — Typst confines reads to the
|
|
30
|
+
lab tree, so these few files must live inside it; they refresh automatically whenever the
|
|
31
|
+
installed engine version changes. `main.typ` is staged into `temp/bundle/` per build.
|
|
32
|
+
**Migration for existing labs** (the UPDATE runbook drives it): add `demolab-cli` to
|
|
33
|
+
`pyproject.toml` (drop the old `[project.scripts]` + `force-include` blocks),
|
|
34
|
+
`git rm -r demolab-engine`, sed the import path in `writings/*.typ`, add `.demolab/` to
|
|
35
|
+
`.gitignore`, `uv sync`, rebuild.
|
|
36
|
+
- **Runbooks + guides are reached through the CLI.** New **`demolab docs`** command: bare, it
|
|
37
|
+
lists every runbook + guide with a one-liner (this menu replaces the hand-kept tables in
|
|
38
|
+
AGENTS.md, which is now a thin stub); `demolab docs <NAME>` prints the file's path
|
|
39
|
+
(`--print` cats it); `demolab docs DEMO` / `STARTERS` / `CHANGELOG` resolve the reference
|
|
40
|
+
data too. `HELP` in an agent chat maps to `demolab docs`.
|
|
41
|
+
- **New `demolab init`** lays a whole lab down in one shot (root stubs, skeleton, `.demolab/`
|
|
42
|
+
staging, `git init` + first commit) and refuses to run inside an existing lab or the
|
|
43
|
+
source repo. `demolab scaffold` remains as the non-destructive repair.
|
|
44
|
+
- **`demolab dev --demo` serves a materialised copy** (`temp/demo-preview/`, rebuilt fresh
|
|
45
|
+
each run) instead of building inside the shipped demo directory; `--landing` still
|
|
46
|
+
previews the marketing homepage. The dual-root `content-prefix` machinery in the build is
|
|
47
|
+
gone — `--root` is always the lab root.
|
|
48
|
+
- **User CI templates updated** (`demolab deploy-setup`): the deploy/preview workflows now
|
|
49
|
+
`uv sync && uv run demolab build` (the vendored build-script path no longer exists). Labs
|
|
50
|
+
with the old workflows should rerun `deploy-setup` after migrating.
|
|
51
|
+
- The launcher shim (`demolab.py` / `launcher.py`) and its hatchling `force-include` wiring
|
|
52
|
+
are gone — the CLI is a normal console script of the installed package.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- `demo-manifest.json` was missing ar018's three paths (`writings/ar018.typ`,
|
|
56
|
+
`artifacts/data/ar018`, `artifacts/pdfs/ar018.pdf`), so `clear-demo-content` left them
|
|
57
|
+
behind; a coverage test now guards the manifest against this drifting again.
|
|
58
|
+
|
|
59
|
+
## [0.6.0] — 2026-07-11
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- **GETTING-STARTED roots the agent *in* the repo, and closes the parent-folder trap.** The
|
|
63
|
+
clone step now clones into `.` (so the agent's working directory *is* the repo root), forbids
|
|
64
|
+
cloning into a named subfolder and working from above it, and — since an agent can't change
|
|
65
|
+
its own working directory mid-session — tells it to ask the user to reopen the session inside
|
|
66
|
+
the repo rather than prefix every path forever. The "prefix the clone's folder name" escape
|
|
67
|
+
hatch is removed.
|
|
68
|
+
- **Agents register the collection a new experiment introduces.** GETTING-STARTED and RULES §6.5
|
|
69
|
+
now require adding a `label` + one-line `description` (and a `collection-order` entry) whenever
|
|
70
|
+
an experiment's `meta.collection` isn't yet in `demolab.yaml`. An unregistered collection still
|
|
71
|
+
renders, but title-cases its slug with **no description**, which reads as unfinished on the
|
|
72
|
+
homepage; only broader collection *curation and reordering* stays deferred.
|
|
73
|
+
- **`demolab` CLI replaces the go-task `Taskfile` — one fewer prerequisite.** The command runner
|
|
74
|
+
is now a Python console script (root `demolab.py` → `demolab-engine/build/cli.py`), installed
|
|
75
|
+
into the venv by `uv sync` and invoked as `demolab <command>`. The toolchain drops to just `uv`
|
|
76
|
+
+ `typst` — no more `go-task` (and no winget `Task.Task`, which removes the Windows
|
|
77
|
+
PATH-restart friction). The command surface is otherwise unchanged, except experiment args lose
|
|
78
|
+
the `--` (`task run -- expNNN` → `demolab run expNNN`) and `task dev:demo` becomes
|
|
79
|
+
`demolab dev --demo`. **Migration:** after updating, run `uv sync` once to install the `demolab`
|
|
80
|
+
command, then use `demolab <command>` wherever you used `task <command>`.
|
|
81
|
+
- **`uv` + `typst` are now the user's own prerequisites — GETTING-STARTED no longer installs
|
|
82
|
+
them.** The onboarding flow *checks* they resolve (`uv --version`, `typst --version`) and, if
|
|
83
|
+
either is missing, asks the user to install it themselves and waits — the agent doesn't run
|
|
84
|
+
`brew`/`winget` for them. The landing page grew a Prerequisites block linking uv's and typst's
|
|
85
|
+
install pages so a newcomer sets the toolchain up before cloning.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
- **Semi-autonomous research programs (AUTORESEARCH).** A new flow for steered-by-day,
|
|
89
|
+
run-overnight research: a program is one collection with a pre-registered `plan` article
|
|
90
|
+
(hypothesis + kill criteria + a machine-readable experiment queue), an append-only `log`
|
|
91
|
+
article (decisions/anomalies, digest at top), and a night-shift contract (budgets, scope,
|
|
92
|
+
stop conditions). Three runbooks — **AUTORESEARCH** (start/steer a program), **PLAN** (triage
|
|
93
|
+
last night + queue the next pre-registered experiments), **NIGHT-SHIFT** (work the queue on a
|
|
94
|
+
branch, open a PR whose description is the digest) — plus the **AUTORESEARCH-RULES** guide
|
|
95
|
+
they cite. DOCTOR gains a one-plan-one-log / every-queued-entry-has-a-kill check; LINT exempts
|
|
96
|
+
the `log` from the prose rules; NEXT reads the `log`'s decision arc. Opt-in and additive:
|
|
97
|
+
nothing changes for a lab that doesn't create a `plan` article.
|
|
98
|
+
- **Per-PR site previews (`deploy.yml` rewrite + new `preview.yml`).** `demolab deploy-setup` now
|
|
99
|
+
installs two workflows. Production (`deploy.yml`) switched from the GitHub-Actions Pages flow
|
|
100
|
+
to the **branch-based** flow (build main → commit `artifacts/site` to a `gh-pages` branch, via
|
|
101
|
+
`JamesIves/github-pages-deploy-action`, `single-commit`, `clean-exclude: [pr-preview/, CNAME]`).
|
|
102
|
+
Previews (`preview.yml`, via `rossjrw/pr-preview-action`) build every PR to
|
|
103
|
+
`pr-preview/pr-<N>/`, post a sticky comment with the URL, and tear down on close/merge — so a
|
|
104
|
+
night-shift PR is a clickable doc set, not a raw diff. A new build test asserts the emitted
|
|
105
|
+
HTML carries no root-absolute URLs, since subpath-served previews depend on relative links.
|
|
106
|
+
- **Canonical Monte Carlo π starter reference (`scaffold/starters/monte-carlo-pi/`).** The
|
|
107
|
+
GETTING-STARTED universal-backup starter now has a concrete reference — a runner + write-up the
|
|
108
|
+
agent models the user's `exp000.py`/`exp000.typ` on — so the two figures come out right every
|
|
109
|
+
time: a scatter of the sampled points coloured inside/outside with the quarter-circle arc, and
|
|
110
|
+
a log-x convergence curve settling toward π. Reference-only (read, never overlaid) and outside
|
|
111
|
+
repo-root `writings/`, so it never builds or ships into a lab; its `README.md` states the
|
|
112
|
+
figure contract.
|
|
113
|
+
|
|
114
|
+
> **Migration — action required if you already publish (`.github/workflows/deploy.yml` exists).**
|
|
115
|
+
> The new deploy uses a `gh-pages` branch instead of the GitHub-Actions Pages source, so an
|
|
116
|
+
> *"update demolab"* that refreshes the template will not publish until you flip one setting.
|
|
117
|
+
> After updating:
|
|
118
|
+
> 1. Re-run `task deploy-setup` (rewrites `deploy.yml`, adds `preview.yml`), commit, push to main.
|
|
119
|
+
> 2. **Settings → Pages → Source: "Deploy from a branch" → `gh-pages` / `(root)`.** The first
|
|
120
|
+
> push to main creates the branch; until you switch the source, the site keeps serving the
|
|
121
|
+
> last GitHub-Actions deploy.
|
|
122
|
+
> 3. (Recommended) **Settings → General → Pull Requests → "Automatically delete head branches"**,
|
|
123
|
+
> and protect `main`.
|
|
124
|
+
> On a **custom domain**, your `CNAME` is preserved by `clean-exclude` — no action. A lab that
|
|
125
|
+
> never opted into Pages is unaffected until it runs `task deploy-setup`.
|
|
126
|
+
|
|
127
|
+
### Changed
|
|
128
|
+
- **Custom landing page: `landing.typ` replaces the `welcome:` config block.** A `landing.typ`
|
|
129
|
+
at the repo root exporting `#let body = [...]` now renders as the homepage (web only) in
|
|
130
|
+
place of the collection directory, below the brand header — full Typst instead of a fixed
|
|
131
|
+
YAML schema. The `demolab.yaml` `welcome:` key (added in 0.5.2) is gone: `welcome-block` is
|
|
132
|
+
removed from `lib.typ`, and the landing-only `scaffold/demo/site/demolab.yaml` is deleted
|
|
133
|
+
(without `welcome:` it duplicated the demo config) — the upstream hero is now
|
|
134
|
+
`scaffold/demo/site/landing.typ`, copied to the root by the Pages deploy only, so it still
|
|
135
|
+
never lands in a user's lab. The `.welcome-*` CSS classes remain as building blocks for
|
|
136
|
+
landing pages. Migration: a lab that set `welcome:` (none are known to) moves the same
|
|
137
|
+
content into a root `landing.typ`; the key is now silently ignored.
|
|
138
|
+
- **GETTING-STARTED rebuilt around the user's own first experiment.** Onboarding no longer
|
|
139
|
+
overlays the demo into a fresh lab — the lab starts clean, and the finished example is the
|
|
140
|
+
landing site (built from `scaffold/demo/`, which stays as the agent's file-shape reference
|
|
141
|
+
and the test fixture). The flow is now three questions (ready? · what should your first
|
|
142
|
+
experiment be? — with field-calibrated starter suggestions for the blank-canvas case, and
|
|
143
|
+
code/notebook/paper as branches into the migrate runbooks · publish?) and a mandatory
|
|
144
|
+
**touch-it moment**: change a parameter, re-run, watch the page's figures and numbers update
|
|
145
|
+
with no prose touched. Branding and publishing moved after the first experiment;
|
|
146
|
+
fresh-vs-migrate, demo-or-clean, and stack are no longer standalone questions.
|
|
147
|
+
`task add-demo-content` remains as an unadvertised escape hatch.
|
|
148
|
+
- **`task dev:demo-site` reads and serves directly from `demolab-engine/scaffold/demo/`.** Sets
|
|
149
|
+
`DEMOLAB_ROOT` there (no root symlinks, no `temp/demo-site/` staging). A `content-prefix`
|
|
150
|
+
Typst input + `data-file()` in `lib.typ` let demo writings resolve `/artifacts/data/…` while
|
|
151
|
+
`--root` stays at the repo checkout for the engine.
|
|
152
|
+
- **The top-level `landing/` directory is gone.** `CNAME` now lives at
|
|
153
|
+
`demolab-engine/scaffold/demo/site/` (served at the Pages root alongside the demo).
|
|
154
|
+
`task add-demo-content` skips `site/` so it doesn't land in a user's lab root.
|
|
155
|
+
|
|
156
|
+
### Fixed
|
|
157
|
+
- **The upstream marketing homepage no longer leaks into user labs.** `task add-demo-content`
|
|
158
|
+
copied `scaffold/demo/demolab.yaml` — which carried the demolab.eoinmurray.info `welcome:`
|
|
159
|
+
hero and `hide-directory: true` — over the lab's root config, so a fresh lab's homepage was
|
|
160
|
+
the upstream landing page (own collections hidden), and `task clear-demo-content` didn't undo
|
|
161
|
+
it. The hero now lives in a landing-only `scaffold/demo/site/demolab.yaml` (site/ is excluded
|
|
162
|
+
from the overlay), applied only by the Pages deploy; the demo config a user receives is
|
|
163
|
+
branding + demo collections. Build tests now pin both: user fixture gets no hero and a
|
|
164
|
+
visible directory, landing fixture gets the hero.
|
|
165
|
+
|
|
166
|
+
### Removed
|
|
167
|
+
- **The one-line install scripts are retired.** `install.sh` / `install.ps1` (and their
|
|
168
|
+
end-to-end tests) are gone; the agent-based install is the one supported path — open a coding
|
|
169
|
+
agent in an empty folder and paste the GETTING-STARTED prompt. The runbook's step 2 owns the
|
|
170
|
+
toolchain install, including the portable `.tools/bin/` fallback for machines without a
|
|
171
|
+
package manager.
|
|
172
|
+
|
|
173
|
+
## [0.5.3] — 2026-07-09
|
|
174
|
+
|
|
175
|
+
Windows portability, driven by a field report from a real Windows setup (thank you!). The flow
|
|
176
|
+
worked conceptually; everything below is plumbing that assumed Unix.
|
|
177
|
+
|
|
178
|
+
### Fixed
|
|
179
|
+
- **`http://localhost` now works on Windows.** The dev server bound IPv4-only while printing a
|
|
180
|
+
`localhost` URL — and Windows resolves `localhost` to the IPv6 `::1` first, so the printed URL
|
|
181
|
+
was unreachable (only `127.0.0.1` worked). It now binds dual-stack, with an IPv4-only fallback
|
|
182
|
+
where IPv6 is absent.
|
|
183
|
+
- **No more Unix-only shell in tasks.** `task scaffold` / `task add-demo-content` used `rsync`
|
|
184
|
+
and `task slides` used `grep` + `basename` — none exist on Windows. Both are now small stdlib
|
|
185
|
+
Python engines (`overlay.py`, `slides.py`) with the same semantics (re-scaffold never clobbers,
|
|
186
|
+
the demo's prebuilt `site/` never lands in your tree).
|
|
187
|
+
- **Demo runners no longer use the `sh` package** (it does not support Windows) — they invoke
|
|
188
|
+
tools via `subprocess` + `sys.executable`, the same idiom as the playground.
|
|
189
|
+
- **Figures render headless.** The shared figure style forces matplotlib's `Agg` backend;
|
|
190
|
+
the default GUI backend broke where Tcl/Tk is absent (notably uv-managed pythons on Windows).
|
|
191
|
+
- **`typst` resolution.** The build prefers a repo-local `.tools/bin/typst(.exe)` — the blessed
|
|
192
|
+
fallback for machines with no package manager — then PATH.
|
|
193
|
+
|
|
194
|
+
### Added
|
|
195
|
+
- **CI runs the test suite on Windows** (`tests.yml`, ubuntu + windows matrix), so these
|
|
196
|
+
regressions get caught on a real Windows runner.
|
|
197
|
+
- **GETTING-STARTED covers Windows**: `winget` install line, the portable `.tools/bin/` fallback,
|
|
198
|
+
and sandbox notes (`UV_CACHE_DIR`, repo-local temp, network approvals for dependency fetches).
|
|
199
|
+
|
|
200
|
+
## [0.5.2] — 2026-07-08
|
|
201
|
+
|
|
202
|
+
### Added
|
|
203
|
+
- **Homepage welcome block (`demolab.yaml` `welcome:`).** An optional hero on the index page —
|
|
204
|
+
pitch prose, quick links, install commands, and an agent prompt — rendered above the
|
|
205
|
+
collection directory. Absent on a normal lab; the shipped demo sets it so
|
|
206
|
+
demolab.eoinmurray.info is the example site with a welcome, not a separate landing page.
|
|
207
|
+
|
|
208
|
+
### Changed
|
|
209
|
+
- **Upstream Pages deploy publishes the demo at `/`.** `.github/workflows/landing.yml` now uploads
|
|
210
|
+
`artifacts/site/` at the site root (plus `install.sh`, `install.ps1`, and `CNAME` from
|
|
211
|
+
`landing/`). The old `/example/` prefix and standalone `landing/index.html` are gone.
|
|
212
|
+
|
|
213
|
+
## [0.5.1] — 2026-07-08
|
|
214
|
+
|
|
215
|
+
### Added
|
|
216
|
+
- **Staged-experiment flow (RULES §7.5) + a DOCTOR check for it.** An optional, opt-in
|
|
217
|
+
contract for expensive runners: split the runner into `compute → analyse → plot` so a run
|
|
218
|
+
can re-enter at any stage (full / skip-compute / plot-only) without repeating the costly
|
|
219
|
+
prefix. The one boundary the contract enforces is that the **plot** stage reads only from the
|
|
220
|
+
committed `artifacts/data/<id>/` record — which is what makes a plot-only pass reproducible
|
|
221
|
+
from a clean clone with no `temp/` (§5.1, §5.3). The rule deliberately defines the *flow and
|
|
222
|
+
the boundary, not the mechanism*: no mandated flag names, stage harness, or function
|
|
223
|
+
signatures, and most runners stay one-shot. DOCTOR §3 adds a matching **behavioural** check —
|
|
224
|
+
re-run a staged runner's plot-only mode with `temp/` hidden and confirm the figures still
|
|
225
|
+
render; a pass that only fails with scratch hidden is reaching into `temp/`. Advisory unless
|
|
226
|
+
the experiment claims clone-and-replot, since plotting from warm `temp/` while iterating is fine.
|
|
227
|
+
|
|
228
|
+
## [0.5.0] — 2026-07-08
|
|
229
|
+
|
|
230
|
+
### Added
|
|
231
|
+
- **Experiment runners now carry provenance like tools do**, via a new
|
|
232
|
+
`experiments/helpers/provenance.py`. Two helpers: `stamp(config)` adds the same
|
|
233
|
+
`_provenance` block (git commit, `dirty` flag, UTC timestamp) that a tool's
|
|
234
|
+
`setup_run_dir` writes — so an **inline** runner (no tool to inherit from) produces a
|
|
235
|
+
`numbers.json` indistinguishable to `numbers-table` / `provenance-footer` / DOCTOR; and
|
|
236
|
+
`write_run_sh(ARTIFACTS)` drops a `run.sh` reproducer into the committed
|
|
237
|
+
`artifacts/data/<id>/` record — the committed twin of the `run.sh` tools write into
|
|
238
|
+
scratch `temp/`. The demo runners (`exp000`–`exp003`) now emit `run.sh`, DOCTOR flags a
|
|
239
|
+
record missing one, and the stamp is kept as a separate copy from `tools/*/tool.py`
|
|
240
|
+
because the firewall (§4.5) forbids a tool importing `experiments/`. (RULES §4.1, §4.7,
|
|
241
|
+
§7.2; FROM-JUPYTER step 4–5.)
|
|
242
|
+
|
|
243
|
+
## [0.4.6] — 2026-07-07
|
|
244
|
+
|
|
245
|
+
### Changed
|
|
246
|
+
- **Runbooks lead with a human overview, then a labeled `Triggers` line.** Each runbook opened with
|
|
247
|
+
its agent-routing `Triggers:` line, which reads as machinery to a person. Every runbook now opens
|
|
248
|
+
with a plain-language description of what it does and when to use it, followed by a
|
|
249
|
+
`**Triggers** — say any of these, or just \`NAME\`:` line. The trigger phrases are unchanged and
|
|
250
|
+
the step-by-step bodies are untouched, so agent routing is unaffected; the files just read as
|
|
251
|
+
documentation for a human too. (The guides already led with a human summary.)
|
|
252
|
+
|
|
253
|
+
## [0.4.5] — 2026-07-07
|
|
254
|
+
|
|
255
|
+
### Changed
|
|
256
|
+
- **The dev server's rebuild log is now one terse line.** `task dev` reprinted the full output paths
|
|
257
|
+
and every entry/deck id on *every* rebuild, so an active editing session buried the terminal in
|
|
258
|
+
wrapping dumps. `build.py` now prints a concise summary last
|
|
259
|
+
(`built 9 entries + 2 decks -> artifacts/site/`) — which is all the watch loop echoes per rebuild —
|
|
260
|
+
and keeps the full id list on the line above, shown only by a one-shot `task build`.
|
|
261
|
+
|
|
262
|
+
## [0.4.4] — 2026-07-06
|
|
263
|
+
|
|
264
|
+
### Fixed
|
|
265
|
+
- **Dev server hardening (round two).** Closed a path-traversal hole — the `.html` serving path read
|
|
266
|
+
`SITE / <request path>` directly, so a crafted `..%2f….html` could read files outside the served
|
|
267
|
+
dir; it's now confined to the site (verified a traversal request returns 404). Also bounded the
|
|
268
|
+
per-tab SSE queue (a stalled tab can't grow it without limit) and unified benign-disconnect
|
|
269
|
+
handling across every socket write.
|
|
270
|
+
|
|
271
|
+
### Changed
|
|
272
|
+
- **A broken entry no longer takes down the whole site.** One entry that referenced a missing figure
|
|
273
|
+
(or had any Typst error) aborted the single bundle compile, so the *entire* site failed to build.
|
|
274
|
+
`build.py` now flags the failing entry and retries, and `main.typ` renders it as a visible stub
|
|
275
|
+
page at its own URL — a red "this page failed to build" notice with the error — kept out of the
|
|
276
|
+
listings and the book, while every other page builds. Decks that fail to compile are skipped the
|
|
277
|
+
same way. The build reports what it stubbed and still exits 0, so the dev server serves the good
|
|
278
|
+
site with the broken page visibly flagged rather than an all-or-nothing error overlay.
|
|
279
|
+
|
|
280
|
+
## [0.4.3] — 2026-07-06
|
|
281
|
+
|
|
282
|
+
### Fixed
|
|
283
|
+
- **`task dev:demo-site` previews the demo's config, not a stale root copy.** It symlinked the demo's
|
|
284
|
+
content but not its `demolab.yaml` / `HOUSESTYLE.local.md` (which live in the skeleton), so the
|
|
285
|
+
preview built against whatever config happened to sit at the root — losing branding and collection
|
|
286
|
+
labels/descriptions. It now symlinks those too (and tears them down on exit), so the preview
|
|
287
|
+
matches what ships, and editing the demo's `demolab.yaml` hot-reloads.
|
|
288
|
+
|
|
289
|
+
## [0.4.2] — 2026-07-06
|
|
290
|
+
|
|
291
|
+
### Fixed
|
|
292
|
+
- **Dev server no longer spews tracebacks or dies quietly.** A browser resetting a connection
|
|
293
|
+
(closing an SSE stream, reloading, navigating away) used to dump a `ConnectionResetError` stack —
|
|
294
|
+
harmless, but it reads as a crash. The server now swallows those benign disconnects (a custom
|
|
295
|
+
`handle_error`), and hardens against the genuinely bad case: the watch loop survives a transient
|
|
296
|
+
error instead of silently stopping all rebuilds, a hung compile times out (120s) instead of
|
|
297
|
+
freezing the server, and a raced port falls through to the next.
|
|
298
|
+
|
|
299
|
+
## [0.4.1] — 2026-07-06
|
|
300
|
+
|
|
301
|
+
### Changed
|
|
302
|
+
- **Command grammar: the NAME is the trigger.** `HELP` lists the runbooks + guides; a runbook's
|
|
303
|
+
SCREAMING-KEBAB name (`LINT`, `DOCTOR`, …) starts it, a guide's name (`RULES`, `SLIDES`, …) walks
|
|
304
|
+
the user through it. AGENTS.md is restructured around the three commands with the name made
|
|
305
|
+
primary (was "a phrase, or the name"), and the grammar is mirrored into the always-loaded
|
|
306
|
+
`CLAUDE.md` so a bare name routes without the agent having to have read AGENTS.md first — the
|
|
307
|
+
reason a bare `LINT` misfired in older repos. `test_command_catalog.py` keeps the AGENTS tables in
|
|
308
|
+
step with the actual runbook/guide files.
|
|
309
|
+
|
|
310
|
+
## [0.4.0] — 2026-07-06
|
|
311
|
+
|
|
312
|
+
### Added
|
|
313
|
+
- **Slide layouts are a named, liftable catalog.** Each layout is a block in the gallery deck
|
|
314
|
+
(`ar005.slide.typ`) marked `// layout: <name>`; SLIDES.md D11 is the index (name → when-to-use), and
|
|
315
|
+
you build a slide by copying the named block out of the gallery rather than from a Typst component
|
|
316
|
+
library. One tested source of truth — `test_slide_catalog.py` asserts the gallery's marker names
|
|
317
|
+
match the D11 catalog, so the two can't drift.
|
|
318
|
+
- **`task dev:demo-site`.** Serves the shipped demo (`demolab-engine/scaffold/demo/`) through the
|
|
319
|
+
live engine by symlinking its content into the root — one source of truth, no duplicated files —
|
|
320
|
+
and tears the links down on exit. Handy for previewing the reference lab or developing engine
|
|
321
|
+
features against it without a full `add-demo-content` sandbox.
|
|
322
|
+
- **Demo ships the layout-gallery deck (`ar005`).** `SLIDES.md` D12 points authors at a gallery
|
|
323
|
+
deck to copy layouts from; the demo now actually includes it — one slide per D11 layout, driven by
|
|
324
|
+
real run data. Expanded to cover five more layouts (three-column, quote, section divider, big
|
|
325
|
+
number, diagram) and to match demolab's two-ink web palette instead of touying's teal accent (the
|
|
326
|
+
D4 skeleton now carries the palette recipe). The slide's title labels its layout (no separate tag).
|
|
327
|
+
Decks now default to `header: none` — touying's running section header just reprinted the title on
|
|
328
|
+
every slide; turn it back on for a long, multi-section talk. Slide content is also vertically
|
|
329
|
+
centred by default (`#set align(horizon)`) — adaptive, so sparse slides balance and full ones keep
|
|
330
|
+
their title near the top.
|
|
331
|
+
|
|
332
|
+
## [0.3.0] — 2026-07-06
|
|
333
|
+
|
|
334
|
+
### Changed
|
|
335
|
+
- **New dev server (`task dev`).** Replaced `typst watch`'s built-in server with a small Python
|
|
336
|
+
dev server (`demolab-engine/build/devserver.py`) that rebuilds via `build.py` on any source
|
|
337
|
+
change and serves the site with live-reload. It fixes two long-standing dev annoyances: a **new
|
|
338
|
+
entry or deck now appears without restarting** (the build re-globs the filesystem each time, which
|
|
339
|
+
`typst watch` couldn't), and a **failed compile shows up in the browser** as a full-screen overlay
|
|
340
|
+
carrying the Typst error — clearing on the next good build — instead of silently serving the stale
|
|
341
|
+
site with the error buried in the terminal. To keep saves snappy it skips deck recompilation
|
|
342
|
+
when the change touched no `.slide.typ` or data asset, so a prose/CSS/lib edit rebuilds in ~0.4s
|
|
343
|
+
instead of ~1.1s. Trade-off vs `typst watch`: a full bundle compile per save rather than Typst's
|
|
344
|
+
incremental recompile. No Node, no new dependencies.
|
|
345
|
+
|
|
346
|
+
## [0.2.5] — 2026-07-06
|
|
347
|
+
|
|
348
|
+
### Added
|
|
349
|
+
- **`guides/SLIDES.md` — deck authoring guide.** Conventions for `writings/*.slide.typ` decks,
|
|
350
|
+
numbered `D1–D13`: the `.slide.typ` marker and skeleton, sizing in absolute `pt` against the
|
|
351
|
+
842 × 474 pt canvas (~350 pt usable under a title), per-aspect figure rows, the
|
|
352
|
+
silent-pagination overflow trap and the page-count check, the layout vocabulary (bullets,
|
|
353
|
+
two-column, code, equation + terms, four figure layouts, table, focus, closer), and the
|
|
354
|
+
dev-server caveat for decks created mid-`task dev`. Indexed from `AGENTS.md`, RULES §3.1,
|
|
355
|
+
STRUCTURE's tree, and GLOSSARY G9.
|
|
356
|
+
- **`pending-figure` — placeholder for an unrendered figure.** A `#pending-figure(caption: …,
|
|
357
|
+
note: …, ratio: …)` helper (and the `#pending` body it wraps) stands in for a figure whose asset
|
|
358
|
+
isn't ready yet — a re-run in flight, data not cleared for release. It numbers as a normal
|
|
359
|
+
"Figure N" and reserves the figure's footprint (a tinted, dashed, rounded panel with a small
|
|
360
|
+
framed-image mark over the muted reason) so the page doesn't reflow when the real plot lands.
|
|
361
|
+
Replaces the bare floating text that a missing asset used to leave on the web. RULES §6.2.
|
|
362
|
+
- **Entry pdf link moved inline.** On an entry page the `pdf` link now sits in the meta strip next
|
|
363
|
+
to the status (`ar000 · 30 May 2026 · Revising · pdf`) instead of being flexed to the right edge
|
|
364
|
+
of the title row.
|
|
365
|
+
- **Inline citations render demibold.** A `#cite(…)` (brackets included) now stands out from the
|
|
366
|
+
body at weight 600 on both targets. CMU ships only Roman + Bold, so the web loads a real
|
|
367
|
+
mid-weight face — Latin Modern Roman Demi, the CM-lineage demibold — for weight 600; it's fetched
|
|
368
|
+
only on pages that actually carry a citation.
|
|
369
|
+
|
|
370
|
+
### Changed
|
|
371
|
+
- **Status ordering reversed.** Listings now sort `final → revising → building → draft` (settled
|
|
372
|
+
work first) instead of leading with work-in-progress.
|
|
373
|
+
- **LINT now audits the figures, not just the prose.** The lint runbook gained a Figures pass
|
|
374
|
+
(H10–H15): mechanical greps over the rendered assets (`artifacts/data/<id>/*.svg`, `*.png`) for
|
|
375
|
+
palette, white background, format, and `alt:` text, plus a **required vision pass** (§2b) that
|
|
376
|
+
opens each figure to check labelled axes with units, no baked-in title, legibility, aspect,
|
|
377
|
+
grayscale-safety, and central style. Documents the SVG trap — matplotlib writes axis labels as
|
|
378
|
+
glyph paths, not `<text>`, so plots can't be grep-linted — the reason agents were linting prose
|
|
379
|
+
and silently skipping the plots.
|
|
380
|
+
|
|
381
|
+
## [0.2.4] — 2026-07-06
|
|
382
|
+
|
|
383
|
+
### Fixed
|
|
384
|
+
- **`#cite` spacing ([#1](https://github.com/eoinmurray/demolab/issues/1)).** The inline citation
|
|
385
|
+
was set flush against the preceding word (`runs[2]`). The helper now owns a thin gap before the
|
|
386
|
+
bracket — a weak `h()` in the PDF, a `margin-left` on the web span — so authors attach `#cite`
|
|
387
|
+
directly to the word (`runs#cite(2)`) and the bracket keeps its space without ever orphaning onto
|
|
388
|
+
the next line. Documented the convention in HOUSESTYLE H24.
|
|
389
|
+
|
|
390
|
+
## [0.2.3] — 2026-07-06
|
|
391
|
+
|
|
392
|
+
### Added
|
|
393
|
+
- **Heading anchors.** Every heading on a web page now carries a slug `id` (its text lowercased,
|
|
394
|
+
non-alphanumerics collapsed to hyphens), so any section is directly linkable as
|
|
395
|
+
`entry.html#the-slug`. A quiet `#` permalink fades in on hover to grab that URL. Applies to entry
|
|
396
|
+
titles, section/subsection headings, and the auto-built References heading.
|
|
397
|
+
|
|
398
|
+
## [0.2.2] — 2026-07-06
|
|
399
|
+
|
|
400
|
+
### Fixed
|
|
401
|
+
- **Figure numbering restarts per entry.** The whole bundle compiles in one pass, so Typst's
|
|
402
|
+
global figure counter was carrying across every document — a standalone entry PDF could open at
|
|
403
|
+
"Figure 7". Each entry (its page + standalone PDF) now numbers figures from 1; the book keeps
|
|
404
|
+
numbering continuously 1…N across chapters.
|
|
405
|
+
|
|
406
|
+
## [0.2.1] — 2026-07-06
|
|
407
|
+
|
|
408
|
+
### Changed
|
|
409
|
+
- **LINT enforces the references system.** The lint runbook now flags any hand-rolled citation —
|
|
410
|
+
typed `[1]` brackets, a manual `== References` section, literal `doi.org` links, or author–year
|
|
411
|
+
cites like "(Smith 2020)" — as an H24 violation; references must go through `#cite` +
|
|
412
|
+
`#reference-list`. Also documented the system fully in RULES §6.6.
|
|
413
|
+
|
|
414
|
+
## [0.2.0] — 2026-07-06
|
|
415
|
+
|
|
416
|
+
### Changed
|
|
417
|
+
- **Listing layout stacked.** Entry rows now put the `id` + title on top with a quiet
|
|
418
|
+
`date · status · pdf` sub-line beneath the title, instead of right-aligning the meta — so long
|
|
419
|
+
titles wrap cleanly without orphaning the metadata. Applies to every listing (collection pages +
|
|
420
|
+
`all.html`).
|
|
421
|
+
|
|
422
|
+
## [0.1.0] — 2026-07-06
|
|
423
|
+
|
|
424
|
+
Initial versioned release — the engine after its foundational build-out.
|
|
425
|
+
|
|
426
|
+
### Added
|
|
427
|
+
- **Engine-only distribution.** The repo ships with no content; `task scaffold` lays down the bare
|
|
428
|
+
structure, `task add-demo-content` overlays a worked demo, `task clear-demo-content` removes it.
|
|
429
|
+
The demo lives in `demolab-engine/scaffold/` and doubles as the smoke-test fixture.
|
|
430
|
+
- **One-line installers** — `install.sh` (macOS/Linux) and `install.ps1` (Windows), served from the
|
|
431
|
+
project landing page at demolab.eoinmurray.info.
|
|
432
|
+
- **14 agent runbooks** — getting-started, tour, migrate-code, from-jupyter, from-paper,
|
|
433
|
+
migrate-stack, embed-docs, next, ground-claims, lint, doctor, red-team, steelman, update — plus a
|
|
434
|
+
`HELP` index, each triggerable by bare name.
|
|
435
|
+
- **Citations** — `#cite(...)` inline numbered cites + `#reference-list(...)` with DOI links, and
|
|
436
|
+
Wikipedia-style hover popovers on the web (DOIs open in a new tab).
|
|
437
|
+
- **Entry status** — a free-form `meta.status` (`draft`/`revising`/`final`), shown as plain text
|
|
438
|
+
across every listing and entry page, and driving listing order (Articles → Experiments → Slides,
|
|
439
|
+
then status, then id).
|
|
440
|
+
- **Author/contact branding** — a byline under the homepage title + `<meta name="author">`.
|
|
441
|
+
- **Friendly empty-state homepage** on a freshly-scaffolded repo.
|
|
442
|
+
|
|
443
|
+
### Changed
|
|
444
|
+
- The figure-data format is the author's choice (CSV, JSON, `.npz`, …); only the contract files
|
|
445
|
+
(`config`/`output`/`manifest`/`numbers.json`) stay JSON.
|
demolab_cli/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0
|
demolab_cli/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""demolab-cli — the demolab engine as a normal Python package.
|
|
2
|
+
|
|
3
|
+
A lab is a plain directory of user content (writings/, experiments/, tools/, artifacts/,
|
|
4
|
+
demolab.yaml); the engine lives here, in site-packages. The CLI materialises the few files
|
|
5
|
+
Typst must read from inside the lab tree into a gitignored `.demolab/` staging dir, and
|
|
6
|
+
stages `typ/main.typ` into temp/bundle/ per build — everything else (runbooks, guides,
|
|
7
|
+
scaffold, demo) ships only in the wheel. `demolab init` lays a new lab down; updating is a
|
|
8
|
+
normal dependency bump (`uv lock --upgrade-package demolab-cli && uv sync`).
|
|
9
|
+
"""
|
|
10
|
+
from ._paths import VERSION as __version__ # noqa: F401
|
demolab_cli/_paths.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Shared path model: where the package's data lives, where the user's lab is, and the
|
|
2
|
+
`.demolab/` staging dir that bridges the two for Typst.
|
|
3
|
+
|
|
4
|
+
The engine ships in site-packages, but typst `--root` confines all file reads to the lab
|
|
5
|
+
tree — so the handful of files Typst must read (lib.typ, imported by user writings; the web
|
|
6
|
+
assets + VERSION, read by lib.typ/main.typ) are materialised into a gitignored `.demolab/`
|
|
7
|
+
at the lab root, refreshed whenever the installed package version changes. Everything else
|
|
8
|
+
is read from the package directly.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
PACKAGE = Path(__file__).resolve().parent
|
|
17
|
+
TYP = PACKAGE / "typ"
|
|
18
|
+
SCAFFOLD = PACKAGE / "scaffold"
|
|
19
|
+
DEPLOY = PACKAGE / "deploy"
|
|
20
|
+
RUNBOOKS = PACKAGE / "runbooks"
|
|
21
|
+
GUIDES = PACKAGE / "guides"
|
|
22
|
+
VERSION = (PACKAGE / "VERSION").read_text().strip()
|
|
23
|
+
|
|
24
|
+
MARKER = "demolab.yaml"
|
|
25
|
+
|
|
26
|
+
# Everything stage() writes into <lab>/.demolab/ (plus the VERSION stamp).
|
|
27
|
+
_STAGED = ("lib.typ", "style.css", "cite-popover.js", "favicon.svg")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_lab_root(start: Path | None = None) -> Path | None:
|
|
31
|
+
"""Nearest ancestor of `start` (default: cwd) holding demolab.yaml — the lab marker
|
|
32
|
+
`demolab init`/`demolab scaffold` write. Like git finding its root."""
|
|
33
|
+
start = (start or Path.cwd()).resolve()
|
|
34
|
+
for d in (start, *start.parents):
|
|
35
|
+
if (d / MARKER).is_file():
|
|
36
|
+
return d
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_lab_root() -> Path:
|
|
41
|
+
root = find_lab_root()
|
|
42
|
+
if root is None:
|
|
43
|
+
sys.exit("demolab: not inside a lab (no demolab.yaml found from here upward).\n"
|
|
44
|
+
" Start one with `demolab init` in an empty directory.")
|
|
45
|
+
return root
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def stage(root: Path) -> Path:
|
|
49
|
+
"""Materialise <root>/.demolab/ (idempotent; refreshed on version-stamp mismatch).
|
|
50
|
+
Returns the staging dir."""
|
|
51
|
+
dot = root / ".demolab"
|
|
52
|
+
stamp = dot / "VERSION"
|
|
53
|
+
prev = stamp.read_text().strip() if stamp.is_file() else None
|
|
54
|
+
if prev == VERSION and all((dot / n).is_file() for n in _STAGED):
|
|
55
|
+
return dot
|
|
56
|
+
dot.mkdir(exist_ok=True)
|
|
57
|
+
for name in _STAGED:
|
|
58
|
+
shutil.copy2(TYP / name, dot / name)
|
|
59
|
+
stamp.write_text(VERSION + "\n")
|
|
60
|
+
if prev and prev != VERSION:
|
|
61
|
+
print(f"→ engine {prev} → {VERSION} — run `demolab docs CHANGELOG --print` to see what changed",
|
|
62
|
+
flush=True)
|
|
63
|
+
return dot
|