font-lab 0.1.0
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.
- package/README.md +281 -0
- package/analyze.mjs +19 -0
- package/analyzer.mjs +431 -0
- package/apply-test.mjs +131 -0
- package/apply.mjs +22 -0
- package/bundle-skill.mjs +20 -0
- package/catalog-build.mjs +126 -0
- package/catalog.mjs +79 -0
- package/codegen.mjs +399 -0
- package/curate.mjs +27 -0
- package/curator.mjs +92 -0
- package/engine.mjs +343 -0
- package/font-lab.mjs +129 -0
- package/gen-catalog.mjs +32 -0
- package/init-test.mjs +52 -0
- package/init.mjs +38 -0
- package/install.mjs +188 -0
- package/loop-test.mjs +119 -0
- package/m3-test.mjs +255 -0
- package/m4-test.mjs +92 -0
- package/m5-test.mjs +142 -0
- package/m6-test.mjs +119 -0
- package/mcp.mjs +228 -0
- package/package.json +79 -0
- package/rewire-test.mjs +79 -0
- package/rewire.mjs +25 -0
- package/screenshot-demo.mjs +42 -0
- package/screenshots.mjs +30 -0
- package/select.mjs +36 -0
- package/skill/font-lab/SKILL.md +75 -0
- package/templates/font-lab-panel.tsx +293 -0
- package/undo.mjs +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# cli — the Font Lab loop (M1 walking skeleton)
|
|
2
|
+
|
|
3
|
+
The first real (non-throwaway) slice: the whole loop runs end to end. The human flips
|
|
4
|
+
between curated directions on their own running site and picks one; the pick is written to
|
|
5
|
+
`.font-lab/selection.json` — the seam the agent reads to ship the real code (M4).
|
|
6
|
+
|
|
7
|
+
## Install (one command)
|
|
8
|
+
|
|
9
|
+
Inside your Next.js + Tailwind project — or just ask your agent *"install Font Lab"*:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx font-lab install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This does two things, idempotently and reversibly (mirroring the `npx impeccable install`
|
|
16
|
+
pattern):
|
|
17
|
+
|
|
18
|
+
1. **Skill** → copies the `font-lab` skill into `~/.claude/skills/font-lab`, so the agent
|
|
19
|
+
*discovers* it every session. You just say "pick new fonts" and it reaches for Font Lab.
|
|
20
|
+
2. **MCP server** → registers `font-lab` in the project's `.mcp.json` so the agent has the
|
|
21
|
+
`font_lab_*` tools to drive the loop. (A newly registered MCP server is picked up on the
|
|
22
|
+
next session/MCP reload.)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx font-lab uninstall # remove the skill + the .mcp.json entry
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Useful flags: `--project <dir>` (target a project other than the cwd), `--no-mcp` /
|
|
29
|
+
`--no-skill` (do only one half), `--local` (register the MCP server as `node <checkout>/mcp.mjs`
|
|
30
|
+
for testing an unpublished clone), `--dry-run` (print the plan, write nothing).
|
|
31
|
+
|
|
32
|
+
## The choosing moment: Headless vs Live
|
|
33
|
+
|
|
34
|
+
This is the heart of Font Lab. **The human always makes the taste decision** — Font Lab's job
|
|
35
|
+
is to hand you a *curated, controlled* menu (never a 1,500-font dump), rendered on your own
|
|
36
|
+
site, and ship exactly what you pick. There are two ways to present that menu. They are the
|
|
37
|
+
same loop and the same catalog — only the surface differs.
|
|
38
|
+
|
|
39
|
+
### 1. Headless — the default, works everywhere
|
|
40
|
+
|
|
41
|
+
The agent screenshots your real site in each direction and shows you the images right in the
|
|
42
|
+
chat. You pick one by name (on a phone: just tap it). The agent ships it.
|
|
43
|
+
|
|
44
|
+
- Works in **every** surface: Claude Code on the web, the iPhone/desktop apps, any MCP agent.
|
|
45
|
+
- No dev server for *you* to babysit, no browser window to manage — you compare finished
|
|
46
|
+
pictures and choose.
|
|
47
|
+
- The screenshots are driven through the real preview engine, so **what you see is what ships**
|
|
48
|
+
(same `next/font` + Tailwind, same metric-matched fallback).
|
|
49
|
+
|
|
50
|
+
Tools: `font_lab_screenshot_directions({ projectDir, baseUrl })` → show the images → the human
|
|
51
|
+
picks → `font_lab_select({ projectDir, directionId })` → `font_lab_apply`.
|
|
52
|
+
|
|
53
|
+
### 2. Live — the full UI, when you want to drive
|
|
54
|
+
|
|
55
|
+
A real dev-panel on your running site: flip with `← →`, mix a heading from one direction with a
|
|
56
|
+
body from another (`[ ]`), toggle before/after (`B`), pin two to compare, "more like this," and
|
|
57
|
+
see each face across multiple routes. This is the richest way to choose — and it needs a browser
|
|
58
|
+
*you* can click in, so it runs **locally**: a Mac/Linux terminal, or the integrated terminal in
|
|
59
|
+
**VS Code / Cursor / the Claude Code IDE extension**.
|
|
60
|
+
|
|
61
|
+
Get the exact commands anytime with `font_lab_live_instructions({ projectDir })`.
|
|
62
|
+
|
|
63
|
+
### How an agent should offer this
|
|
64
|
+
|
|
65
|
+
> **Default to Headless.** Present the screenshots first — it works no matter where the user is.
|
|
66
|
+
> Then tell them the full live UI exists: *"Want to flip, mix, and compare these yourself? I can
|
|
67
|
+
> give you a one-time command to open the full editor locally."* Give them the option; never
|
|
68
|
+
> auto-pick. Curated and controlled, with a clear path to expand — that's how Font Lab rolls.
|
|
69
|
+
|
|
70
|
+
| | Headless (default) | Live (full UI) |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| Where it runs | anywhere (web, phone, any agent) | local terminal / IDE / Cursor |
|
|
73
|
+
| You interact with | screenshots in chat | a live panel on your site |
|
|
74
|
+
| Mix roles, before/after, multi-route | pick a whole direction | yes — the complete UX |
|
|
75
|
+
| Fidelity | preview == ship | preview == ship |
|
|
76
|
+
| Who decides | **the human** | **the human** |
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
> **Status: M1 + M2 + M3 PASS.**
|
|
80
|
+
> - **M1** (`cli/run-m1.sh`, 16/16): arrow-flip, live display+body+mono swap, Pick →
|
|
81
|
+
> `selection.json`, re-pick appends to `picks.log.jsonl`.
|
|
82
|
+
> - **M2** (`cli/run-m2.sh`, 19/19): `selection.json` → real `next/font` + Tailwind edits
|
|
83
|
+
> that **build** and **render** the picked fonts, applied **idempotently** and
|
|
84
|
+
> **reversibly** (backup-first undo, byte-identical restore). The link nobody else closes.
|
|
85
|
+
> - **M6** (`cli/run-m6.sh`, M1 16/16 + M6 17/17): the **choosing moment polished**, driven in
|
|
86
|
+
> a real browser. **Mixed picks** (heading from one direction, body from another), **before/
|
|
87
|
+
> after**, **pin-two-to-compare**, **more-like-this**, refined keyboard UX, and **multi-route
|
|
88
|
+
> flipping** — the working pairing persists across routes (`/`, `/dense`, `/form`) via
|
|
89
|
+
> sessionStorage, because a face reads differently on a hero vs. a dense page vs. a form. A
|
|
90
|
+
> mixed pick ships end to end (verified: Fraunces/Figtree/JetBrains Mono → real `next/font`).
|
|
91
|
+
> - **M5** (`cli/run-m5.sh`, 26/26): the **MCP server + skill** so an agent drives the whole
|
|
92
|
+
> loop (analyze → curate *or* compose → preview → read the pick → apply). The agent gets the
|
|
93
|
+
> curated default for free **and can take the wheel** — composing its own directions from the
|
|
94
|
+
> catalog (option 3) — but only from catalog fonts, so preview == ship still holds. The human
|
|
95
|
+
> always makes the final pick. Verified over real stdio (initialize / tools-list / tools-call).
|
|
96
|
+
> - **M4** (`cli/run-m4.sh`, 96/96): the **parity catalog + curator**. A 41-font catalog of
|
|
97
|
+
> variable Google fonts, each gated on *verified* capsize coverage (checked by importing
|
|
98
|
+
> the metrics) and single-woff2 variable parity. A deterministic, **LLM-free** curator
|
|
99
|
+
> turns the analysis into ~5 directions — moving off the current fonts, rankable by vibe,
|
|
100
|
+
> reproducible. Evidence in `cli/out/m4-report.json`.
|
|
101
|
+
> - **M3** (`cli/run-m3.sh`, 60/60): the **real analyzer** reads framework, App vs Pages
|
|
102
|
+
> Router, Tailwind v3 vs v4, current fonts per role, and font wiring — and feeds both the
|
|
103
|
+
> codegen branch selection and the panel's before/after. Verified on the in-repo fixtures
|
|
104
|
+
> **and the real jack-mcgovern.com site**, where it correctly reads `Bricolage Grotesque /
|
|
105
|
+
> Hanken Grotesk` on `<body>` and codegen **adopts** those project variables to ship a
|
|
106
|
+
> building, reversible swap. Out-of-branch projects (v3 / Pages / hardcoded) are refused
|
|
107
|
+
> with a clear reason.
|
|
108
|
+
>
|
|
109
|
+
> Evidence in `cli/out/{m1,m2,m3}-report.json` (+ `cli/out/jack-applied.*` — the actual code
|
|
110
|
+
> Font Lab generated for the real site). Runs on Next 16 + Tailwind v4 + Turbopack.
|
|
111
|
+
|
|
112
|
+
### Dogfood note — what jack-mcgovern.com taught us (M3)
|
|
113
|
+
|
|
114
|
+
Applied into the real site, Font Lab **builds and renders**: body fonts swap
|
|
115
|
+
Hanken Grotesk → Libre Franklin everywhere they show. The headings keep rendering the body
|
|
116
|
+
font *both before and after* — because the site's own `@layer base { h1,h2,h3 { font-family:
|
|
117
|
+
var(--font-display) } }` never resolves (Tailwind v4's `@theme inline` doesn't emit
|
|
118
|
+
`--font-display` as a `:root` variable; only the `font-display` *utility* derefs it). Font
|
|
119
|
+
Lab **preserved that behavior faithfully** rather than silently "fixing" it — exactly the
|
|
120
|
+
honesty the WYSIWYG promise requires: we swap the families through the project's own wiring
|
|
121
|
+
and change nothing else.
|
|
122
|
+
|
|
123
|
+
That dogfood turned into a feature. The analyzer now ships **coverage diagnostics** so the
|
|
124
|
+
tool *detects* this class of problem instead of being surprised by it:
|
|
125
|
+
|
|
126
|
+
- **Dead roles.** Under `@theme inline`, Tailwind v4 doesn't publish `--font-*` as a `:root`
|
|
127
|
+
variable — only the generated `font-*` utilities deref it. A site that hand-writes
|
|
128
|
+
`font-family: var(--font-display)` (a very common pattern) therefore has *silently broken*
|
|
129
|
+
display wiring. `analyze` flags it: `⚠ dead display — swap invisible until rewired`.
|
|
130
|
+
- **Other subsystems.** Fonts declared with their own next/font in another route/component
|
|
131
|
+
(jack's `/gus` uses `--font-fraunces`/`--font-dm-sans`) are reported, so the agent/user
|
|
132
|
+
knows a global swap's true scope (full per-route flipping is M6).
|
|
133
|
+
|
|
134
|
+
The principle this protects: **preview and ship must operate on the same leaf next/font
|
|
135
|
+
variable, applied at the same element next/font uses** (the analyzer reports both
|
|
136
|
+
`classNameTarget` and each role's `nextFontVar`). When they match, preview == ship *by
|
|
137
|
+
construction* on any site — and when a swap genuinely can't be seen, the tool says so rather
|
|
138
|
+
than letting the user pick blind.
|
|
139
|
+
|
|
140
|
+
## Run it
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd cli && pnpm install # @capsizecss/metrics + playwright
|
|
144
|
+
bash cli/run-m1.sh # from the repo root
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`run-m1.sh` builds the parity catalog, starts the fixture dev server, and drives the loop
|
|
148
|
+
with a headless browser, asserting the pick lands on disk. Verdict + assertions in
|
|
149
|
+
`cli/out/m1-report.json`.
|
|
150
|
+
|
|
151
|
+
## Use it by hand
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
node cli/analyze.mjs --project <your-project> # what Font Lab sees (read-only)
|
|
155
|
+
node cli/curate.mjs --project <your-project> # the ~5 directions it would offer
|
|
156
|
+
node cli/curate.mjs --project <your-project> --vibe editorial
|
|
157
|
+
node cli/gen-catalog.mjs # self-host fonts + build catalog
|
|
158
|
+
cd examples/sample-next-site && pnpm dev # your dev server
|
|
159
|
+
node cli/font-lab.mjs --project examples/sample-next-site # the pick endpoint (:7777)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Then open the dev site: a panel (bottom-right) shows the current state + the curated
|
|
163
|
+
directions, swapping live on your real content. Keys (M6):
|
|
164
|
+
|
|
165
|
+
- `←` `→` — flip direction · `↑` `↓` — focus a role · `[` `]` — swap just that role (**mixed
|
|
166
|
+
picks**: heading from one direction, body from another)
|
|
167
|
+
- `B` — before/after · `P` then `Space` — **pin two and compare** · `M` — more like this
|
|
168
|
+
- `Enter` or **Pick** — write the selection
|
|
169
|
+
|
|
170
|
+
The working pairing follows you across routes (`/`, `/dense`, `/form`) so you can judge a face
|
|
171
|
+
on a hero, a dense page, and a form — "your real site" is more than one screen.
|
|
172
|
+
|
|
173
|
+
### Run it on your own project (`font-lab init`)
|
|
174
|
+
|
|
175
|
+
One command makes any supported project (App Router + Tailwind v4 + CSS-variable fonts)
|
|
176
|
+
previewable — it self-hosts the parity bundles, drops in the dev panel, and mounts it
|
|
177
|
+
dev-only in your layout:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
node cli/init.mjs --project <your-project> # scaffold panel + parity bundles (reversible)
|
|
181
|
+
cd <your-project> && <your dev command> # e.g. next dev / npm run dev
|
|
182
|
+
node cli/font-lab.mjs --project <your-project> # the pick endpoint (:7777)
|
|
183
|
+
# → flip in the panel, Pick, then `node cli/apply.mjs --project <your-project>`
|
|
184
|
+
node cli/init.mjs --project <your-project> --undo # remove the panel scaffolding
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The panel swaps through your project's **own** leaf font variables (the analyzer's `wiring`),
|
|
188
|
+
so the live preview is byte-for-byte what `apply` ships. A role the site doesn't route through
|
|
189
|
+
a variable is shown as *not wired* rather than faked. Proven end to end on the real
|
|
190
|
+
jack-mcgovern.com (body swapped site-wide live → shipped Playfair Display / Source Serif 4 /
|
|
191
|
+
Roboto Mono → reverted clean).
|
|
192
|
+
|
|
193
|
+
### Fix a dead role (`font-lab rewire`)
|
|
194
|
+
|
|
195
|
+
If `analyze` flags a role as **dead** (declared but not actually rendered — a heading rule that
|
|
196
|
+
reads `var(--font-display)` under `@theme inline`), `rewire` points those raw usages at the
|
|
197
|
+
published leaf var so the font renders. Reversible.
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
node cli/rewire.mjs --project <your-project> # var(--font-display) → var(--font-bricolage)
|
|
201
|
+
node cli/undo.mjs --project <your-project> # revert
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Proven on the real jack-mcgovern.com: headings rendered `Hanken Grotesk` (body font) before →
|
|
205
|
+
`Bricolage Grotesque` after, build-verified, then reverted.
|
|
206
|
+
|
|
207
|
+
### Let an agent drive it (the easy way)
|
|
208
|
+
|
|
209
|
+
Register the server once with Claude Code (user scope = available in every project):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd Font-Lab/cli && pnpm install # one-time
|
|
213
|
+
claude mcp add font-lab -s user -- node "$(pwd)/mcp.mjs"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Then, in any supported project, just tell Claude:
|
|
217
|
+
|
|
218
|
+
> "Use Font Lab to pick fonts for this site."
|
|
219
|
+
|
|
220
|
+
Claude runs the whole setup — `analyze` → `init` (installs the panel + self-hosts the fonts) →
|
|
221
|
+
`rewire` if headings are dead → starts your dev server + the pick endpoint. **Your only job:**
|
|
222
|
+
open the site, flip/mix/compare in the panel, and hit **Pick**. Claude reads your pick and
|
|
223
|
+
ships it (`apply`), reversibly.
|
|
224
|
+
|
|
225
|
+
Prefer not to use the CLI? A ready-to-use [`.mcp.json`](../.mcp.json) is committed at the repo
|
|
226
|
+
root (loads automatically when you open this repo), or copy the entry into another project's
|
|
227
|
+
`.mcp.json` with an absolute path:
|
|
228
|
+
|
|
229
|
+
```jsonc
|
|
230
|
+
{ "mcpServers": { "font-lab": { "command": "node", "args": ["/abs/path/to/cli/mcp.mjs"] } } }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The 11 tools (`analyze`, `list_catalog`, `curate`, `compose_directions`, `init`, `uninit`,
|
|
234
|
+
`prepare_preview`, `read_pick`, `apply`, `rewire_dead_roles`, `undo` — all `font_lab_*`) and the
|
|
235
|
+
loop are described in [`../skill/font-lab/SKILL.md`](../skill/font-lab/SKILL.md). The agent gets
|
|
236
|
+
the curated default for free and can compose its own directions from the catalog — but the
|
|
237
|
+
**human always makes the pick**, and composed fonts must be catalog members so preview still
|
|
238
|
+
equals ship. Proven end to end over MCP on the real jack-mcgovern.com.
|
|
239
|
+
|
|
240
|
+
### Ship the pick (M2)
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
node cli/apply.mjs --project <your-project> # selection.json -> next/font + Tailwind edits
|
|
244
|
+
node cli/undo.mjs --project <your-project> # restore the files Font Lab last edited
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`apply` edits `app/layout.tsx` (ts-morph: merges the next/font import, rewrites the
|
|
248
|
+
font consts in a fenced block, merges the `<html>` className) and `app/globals.css`
|
|
249
|
+
(a fenced `@theme` block), backing up every touched file first. Re-running is idempotent;
|
|
250
|
+
`undo` restores byte-for-byte. Run `font-lab --apply` to ship a pick the moment it's made.
|
|
251
|
+
|
|
252
|
+
## Pieces
|
|
253
|
+
|
|
254
|
+
| file | role |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `analyzer.mjs` | **the analyzer (M3):** pure, read-only audit of a project — framework, router, Tailwind version, current fonts per role, and wiring. Traces the CSS custom-property graph from `--font-*` back to the next/font const that feeds it. **Coverage diagnostics**: flags dead roles (a swap that won't be visible) and other font subsystems (routes a global swap won't reach) |
|
|
257
|
+
| `analyze.mjs` | thin CLI around the analyzer (`--project`, `--json`) |
|
|
258
|
+
| `catalog.mjs` | **the catalog (M4):** ~41 variable Google fonts as parity bundles — each with a verified capsize slug, a discovered css2 latin query, role suitability, and vibe tags. Pure data; the parity asset |
|
|
259
|
+
| `curator.mjs` | **the curator (M4):** ~12 hand-authored directions + a deterministic `curate(analysis, {vibe, count})` that returns ~5, moving off the current fonts. No runtime LLM |
|
|
260
|
+
| `curate.mjs` | thin CLI to preview the directions for a project (`--project`, `--vibe`, `--count`, `--json`) |
|
|
261
|
+
| `catalog-build.mjs` | reusable `generateCatalog(projectDir, directions, meta)` — self-hosts fonts + computes parity fallbacks + writes the generated module. Built from curated OR agent-composed directions |
|
|
262
|
+
| `gen-catalog.mjs` | CLI: analyzer → curator → `generateCatalog`, bakes the real `current`/`target`/`directions` into `app/_fontlab/catalog.generated.ts` |
|
|
263
|
+
| `engine.mjs` | **the engine facade (M5):** the stable API the MCP wraps — `analyze`, `listCatalog`, `curate`, `composeDirections` (option 3, catalog-gated), `preparePreview`, `readSelection`, `apply`, `undo` |
|
|
264
|
+
| `mcp.mjs` | **the MCP server (M5):** dependency-free JSON-RPC/stdio server exposing the engine as 8 agent tools, descriptions tuned for discoverability |
|
|
265
|
+
| `init.mjs` | **the installer:** scaffolds the panel + parity bundles into a real project and mounts it dev-only in the layout; `--undo` restores byte-for-byte. The last mile to "your own running site" |
|
|
266
|
+
| `.mcp.json` (repo root) | ready-to-use MCP registration — open the repo in an MCP client and the `font-lab` server loads |
|
|
267
|
+
| `templates/font-lab-panel.tsx` | the portable dev panel `init` installs — same UX as the fixture's, but swaps through the analyzer's `wiring` so it's honest on any site |
|
|
268
|
+
| `../skill/font-lab/SKILL.md` | the skill manifest — how an agent drives the loop and the rules (human picks; catalog-only; be honest about coverage) |
|
|
269
|
+
| `font-lab.mjs` | the CLI: the localhost write-back endpoint (`POST /select` → `.font-lab/selection.json` + `picks.log.jsonl`); `--apply` ships on pick |
|
|
270
|
+
| `codegen.mjs` | the ship engine (M2+M3): `applySelection` / `undo` — analyzer-gated branch selection, ts-morph + fenced markers, backup-first. Handles both the role-var path and the **adopt-existing-variable** path (real sites) |
|
|
271
|
+
| `apply.mjs` / `undo.mjs` / `rewire.mjs` | thin CLI wrappers around the ship engine (`rewire` fixes dead roles) |
|
|
272
|
+
| `loop-test.mjs` / `apply-test.mjs` / `m3-test.mjs` / `m4-test.mjs` / `m5-test.mjs` / `m6-test.mjs` | headless e2e of the loop (M1), ship engine (M2), analyzer + branch selection (M3), catalog + curator (M4), engine + MCP over stdio (M5), and the polished panel — mixed picks / pin / multi-route — in a real browser (M6) |
|
|
273
|
+
| `run-m1.sh` … `run-m6.sh` | loop test; apply+build+render+idempotency/reversibility; analyzer+codegen; catalog+curator; engine+MCP; mixed-picks/pin/multi-route in a browser |
|
|
274
|
+
|
|
275
|
+
## The contract it writes
|
|
276
|
+
|
|
277
|
+
`.font-lab/selection.json` follows the schema in
|
|
278
|
+
[`../ARCHITECTURE.md`](../ARCHITECTURE.md): `direction`, `roles` (display/body/mono with
|
|
279
|
+
family/source/weights), `replaces` (current fonts), and `target` (framework/router/Tailwind
|
|
280
|
+
version/wiring). `picks.log.jsonl` appends every pick — the taste-memory stream the roadmap
|
|
281
|
+
starts capturing at M1.
|
package/analyze.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `font-lab analyze` — print what Font Lab sees in a project (M3). Read-only.
|
|
3
|
+
// node cli/analyze.mjs [--project <dir>] [--json]
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { analyzeProject, summarize } from "./analyzer.mjs";
|
|
6
|
+
|
|
7
|
+
const arg = (f, d) => {
|
|
8
|
+
const i = process.argv.indexOf(f);
|
|
9
|
+
return i !== -1 && process.argv[i + 1] ? process.argv[i + 1] : d;
|
|
10
|
+
};
|
|
11
|
+
const project = path.resolve(arg("--project", process.cwd()));
|
|
12
|
+
const a = analyzeProject(project);
|
|
13
|
+
|
|
14
|
+
if (process.argv.includes("--json")) {
|
|
15
|
+
console.log(JSON.stringify(a, null, 2));
|
|
16
|
+
} else {
|
|
17
|
+
console.log(`Font Lab — analysis of ${path.relative(process.cwd(), project) || "."}`);
|
|
18
|
+
console.log(summarize(a));
|
|
19
|
+
}
|