tformula 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 +255 -0
- package/README.zh-CN.md +221 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +121 -0
- package/dist/cli.js.map +1 -0
- package/dist/detect.d.ts +47 -0
- package/dist/detect.js +342 -0
- package/dist/detect.js.map +1 -0
- package/dist/formula-cache.d.ts +18 -0
- package/dist/formula-cache.js +238 -0
- package/dist/formula-cache.js.map +1 -0
- package/dist/geometry.d.ts +26 -0
- package/dist/geometry.js +47 -0
- package/dist/geometry.js.map +1 -0
- package/dist/kitty.d.ts +15 -0
- package/dist/kitty.js +61 -0
- package/dist/kitty.js.map +1 -0
- package/dist/math-renderer.d.ts +18 -0
- package/dist/math-renderer.js +204 -0
- package/dist/math-renderer.js.map +1 -0
- package/dist/output-checkpoints.d.ts +15 -0
- package/dist/output-checkpoints.js +36 -0
- package/dist/output-checkpoints.js.map +1 -0
- package/dist/probe.d.ts +30 -0
- package/dist/probe.js +97 -0
- package/dist/probe.js.map +1 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +150 -0
- package/dist/proxy.js.map +1 -0
- package/dist/screen.d.ts +25 -0
- package/dist/screen.js +397 -0
- package/dist/screen.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# TFormula
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Render LaTeX from **any CLI agent** directly inside your terminal.
|
|
6
|
+
|
|
7
|
+
> **Ghostty is the recommended terminal.** TFormula is developed and tested
|
|
8
|
+
> primarily with Ghostty and also works with terminals that implement the Kitty
|
|
9
|
+
> graphics protocol, including Kitty and WezTerm.
|
|
10
|
+
|
|
11
|
+
TFormula is a terminal-agnostic PTY proxy. It does not use Codex, Claude,
|
|
12
|
+
Gemini, or any other agent-specific API.
|
|
13
|
+
|
|
14
|
+
The child program still sees a normal terminal. TFormula forwards its ANSI
|
|
15
|
+
output unchanged, maintains a headless copy of the terminal screen, detects
|
|
16
|
+
visible TeX, renders it locally with MathJax, and places the result over the
|
|
17
|
+
source text using the Kitty graphics protocol. The original text remains in
|
|
18
|
+
the terminal buffer for copying.
|
|
19
|
+
|
|
20
|
+
## Quick start in Ghostty
|
|
21
|
+
|
|
22
|
+
Open Ghostty and run an agent immediately without a global install:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npx --yes tformula@latest codex
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install the short `tformula` command once:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install --global tformula
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then place `tformula` before whichever agent command you already use:
|
|
35
|
+
|
|
36
|
+
| Agent | Command |
|
|
37
|
+
|---|---|
|
|
38
|
+
| OpenAI Codex | `tformula codex` |
|
|
39
|
+
| Claude Code | `tformula claude` |
|
|
40
|
+
| Cursor Agent | `tformula agent` |
|
|
41
|
+
| Pi coding agent | `tformula pi` |
|
|
42
|
+
| Gemini CLI | `tformula gemini` |
|
|
43
|
+
| OpenCode | `tformula opencode` |
|
|
44
|
+
| Aider | `tformula aider` |
|
|
45
|
+
| Goose | `tformula goose` |
|
|
46
|
+
| Qwen Code | `tformula qwen` |
|
|
47
|
+
| Any other CLI agent | `tformula -- <agent-command> [args...]` |
|
|
48
|
+
|
|
49
|
+
The agent itself must already be installed and available on your `PATH`.
|
|
50
|
+
TFormula simply wraps the command; it has no agent-specific integration.
|
|
51
|
+
|
|
52
|
+
To use npx instead, replace `tformula` in any row with
|
|
53
|
+
`npx --yes tformula@latest`. For example:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx --yes tformula@latest codex
|
|
57
|
+
npx --yes tformula@latest claude
|
|
58
|
+
npx --yes tformula@latest agent
|
|
59
|
+
npx --yes tformula@latest pi
|
|
60
|
+
npx --yes tformula@latest gemini
|
|
61
|
+
npx --yes tformula@latest opencode
|
|
62
|
+
npx --yes tformula@latest aider
|
|
63
|
+
npx --yes tformula@latest goose
|
|
64
|
+
npx --yes tformula@latest -- claude --resume
|
|
65
|
+
npx --yes tformula@latest --shell
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--shell` starts an enhanced login shell, so commands launched inside that shell
|
|
69
|
+
can be wrapped without creating an alias for each agent:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
tformula --shell
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Running `tformula` without arguments is equivalent to `tformula --shell`.
|
|
76
|
+
|
|
77
|
+
## Requirements
|
|
78
|
+
|
|
79
|
+
- Ghostty recommended; Kitty, WezTerm, or another Kitty-graphics terminal
|
|
80
|
+
- macOS or Linux
|
|
81
|
+
- Node.js 20 or newer
|
|
82
|
+
- The CLI agent you want to run
|
|
83
|
+
|
|
84
|
+
The current implementation has been developed against Ghostty 1.3.1.
|
|
85
|
+
|
|
86
|
+
## Install from this checkout
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npm install
|
|
90
|
+
npm run check
|
|
91
|
+
npm link
|
|
92
|
+
tformula codex
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Formula sizing
|
|
96
|
+
|
|
97
|
+
At startup and after terminal resize events, TFormula queries the terminal for
|
|
98
|
+
its cell dimensions in pixels. MathJax's natural `ex` dimensions are mapped to
|
|
99
|
+
the terminal cell height, so an ordinary mathematical symbol has approximately
|
|
100
|
+
the same visual size as neighboring terminal text. Fractions, sums, and other
|
|
101
|
+
tall constructs retain their natural proportions.
|
|
102
|
+
|
|
103
|
+
Formulas are never enlarged merely to fill the source rectangle. They are only
|
|
104
|
+
scaled down when they would exceed the columns or rows already occupied by the
|
|
105
|
+
source. This is necessary because inserting terminal rows behind a full-screen
|
|
106
|
+
TUI would desynchronize its cursor coordinates.
|
|
107
|
+
|
|
108
|
+
Formula scans are coalesced during streaming output instead of waiting for the
|
|
109
|
+
terminal to become completely idle. Unrelated status-bar or spinner updates do
|
|
110
|
+
not cancel formulas that remain unchanged on screen. Long PTY output bursts are
|
|
111
|
+
forwarded at line-boundary checkpoints of roughly one third of the terminal
|
|
112
|
+
height. TFormula completes a formula scan at each checkpoint before forwarding
|
|
113
|
+
more rows, so rendered images enter scrollback together with their source text
|
|
114
|
+
instead of being missed after an entire response scrolls past. When the terminal
|
|
115
|
+
grid or cell pixel size changes, TFormula replaces only the affected Kitty
|
|
116
|
+
placements.
|
|
117
|
+
The underlying PNG is retained and shared by every placement with the same
|
|
118
|
+
formula, size, and colors. Normal resizing only replaces images still in the
|
|
119
|
+
live viewport; off-screen placements are preserved so the terminal can scroll
|
|
120
|
+
and scale them with its own scrollback. Replacement is transactional: the old
|
|
121
|
+
placement is deleted only after the new cached variant is ready, and xterm
|
|
122
|
+
markers track its source rows through terminal reflow. A rapid sequence of font
|
|
123
|
+
size changes therefore keeps the previous rendered formula instead of exposing
|
|
124
|
+
the underlying TeX. `CSI 2J` invalidates visible placements,
|
|
125
|
+
while `CSI 3J` and `RIS` invalidate all placements and cached terminal images.
|
|
126
|
+
TFormula reserves a private image-ID range and deletes that complete range on
|
|
127
|
+
full reset and shutdown, including interrupted transmissions.
|
|
128
|
+
|
|
129
|
+
When an agent emits display math as a single standalone `$$...$$` or
|
|
130
|
+
`\[...\]` line, TFormula borrows adjacent blank terminal rows when available.
|
|
131
|
+
This gives fractions and derivatives enough vertical space to retain the same
|
|
132
|
+
base glyph size as simple equations. Inline math and display delimiters mixed
|
|
133
|
+
with prose are never expanded, so neighboring text is not covered.
|
|
134
|
+
|
|
135
|
+
A trailing inline formula can similarly use one following blank row for tall
|
|
136
|
+
fraction content. TFormula absorbs terminal punctuation into that overlay so it
|
|
137
|
+
stays next to the rendered expression. The common reciprocal-root form
|
|
138
|
+
`1/\sqrt{...}` is typeset as `\frac{1}{\sqrt{...}}`; slashes in ordinary units
|
|
139
|
+
such as `m/s` are left unchanged.
|
|
140
|
+
|
|
141
|
+
If a terminal does not answer pixel-size queries, TFormula falls back to 9x18
|
|
142
|
+
pixels. You can override that explicitly:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
tformula --cell-size 10x20 claude
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The default text-relative scale is 1.0. It can be adjusted without changing the
|
|
149
|
+
terminal font:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
tformula --scale 1.1 codex
|
|
153
|
+
TFORMULA_SCALE=0.9 tformula --shell
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Formula cache
|
|
157
|
+
|
|
158
|
+
Math rendering is content-addressed and shared by every TFormula-wrapped Agent
|
|
159
|
+
run for the current user. A normalized formula is typeset to SVG once. Each
|
|
160
|
+
terminal-ready PNG variant is then rasterized once for its exact display mode,
|
|
161
|
+
cell dimensions, scale, foreground, background, and source rectangle. Returning
|
|
162
|
+
to an earlier terminal font size reuses the existing PNG instead of invoking
|
|
163
|
+
MathJax or the rasterizer again.
|
|
164
|
+
|
|
165
|
+
Cache writes use per-item cross-process locks and atomic renames, so concurrent
|
|
166
|
+
Agents can safely request the same formula. In a live terminal session, one PNG
|
|
167
|
+
is uploaded once and reused by independent Kitty placements wherever the same
|
|
168
|
+
variant appears. The original TeX text remains in terminal scrollback.
|
|
169
|
+
|
|
170
|
+
On macOS the disk cache defaults to `~/Library/Caches/TFormula`; on Linux it
|
|
171
|
+
uses `$XDG_CACHE_HOME/tformula` or `~/.cache/tformula`. Override the location or
|
|
172
|
+
the default 256 MB limit with:
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
TFORMULA_CACHE_DIR=/path/to/cache tformula codex
|
|
176
|
+
TFORMULA_CACHE_MAX_MB=512 tformula claude
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Detection
|
|
180
|
+
|
|
181
|
+
TFormula recognizes these explicit forms:
|
|
182
|
+
|
|
183
|
+
```text
|
|
184
|
+
\[ ... \]
|
|
185
|
+
$$ ... $$
|
|
186
|
+
\( ... \)
|
|
187
|
+
$ ... $
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Some agent TUIs consume the backslashes around `\[` and `\]` while rendering
|
|
191
|
+
Markdown. For that case TFormula conservatively recognizes a bare `[`/`]`
|
|
192
|
+
block only when its body contains strong TeX features such as `\frac`, `\sum`,
|
|
193
|
+
subscripts, superscripts, or braced arguments.
|
|
194
|
+
|
|
195
|
+
The same compatibility rule applies when a TUI turns inline delimiters such as
|
|
196
|
+
`\(\rho\)` into `(\rho)`. TFormula renders the parenthesized span only when its
|
|
197
|
+
contents contain a recognized TeX command or similarly strong math structure;
|
|
198
|
+
ordinary prose in parentheses remains unchanged.
|
|
199
|
+
|
|
200
|
+
Short equations such as `E=mc^2`, `p=0`, and `c^2` are also recognized from
|
|
201
|
+
their operator structure. A single letter is inferred only in a symbol
|
|
202
|
+
definition item, where forms such as `- (E):energy` are unambiguous.
|
|
203
|
+
|
|
204
|
+
Consecutive definition items such as `- (\rho):电荷密度` are rendered as one
|
|
205
|
+
compact two-column MathJax array. This keeps symbols, colons, and descriptions
|
|
206
|
+
aligned even though their original TeX source strings have different widths.
|
|
207
|
+
Math expressions embedded in a description, including units written with
|
|
208
|
+
`\text{...}`, remain mathematical content inside that array.
|
|
209
|
+
|
|
210
|
+
Single-dollar expressions also require mathematical structure, which prevents
|
|
211
|
+
ordinary prices such as `$12.50` from being rendered.
|
|
212
|
+
|
|
213
|
+
## Options
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
--shell Start the login shell
|
|
217
|
+
--no-math Run only as a transparent PTY proxy
|
|
218
|
+
--scale <number> Formula-to-terminal text scale (0.5 to 2.0)
|
|
219
|
+
--cell-size <WxH> Override terminal cell pixels
|
|
220
|
+
-C, --cwd <directory> Child working directory
|
|
221
|
+
--debug Print detection and sizing diagnostics
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Use `--` when an agent command or its arguments could be mistaken for TFormula
|
|
225
|
+
options:
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
tformula -- claude --resume
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Safety and fallback behavior
|
|
232
|
+
|
|
233
|
+
- MathJax and fonts are installed locally; rendering does not use a CDN.
|
|
234
|
+
- Formula length is limited to 8192 characters.
|
|
235
|
+
- Commands that can load or embed external content, including `\require`,
|
|
236
|
+
`\href`, `\url`, and MathJax HTML/style commands, are rejected.
|
|
237
|
+
- A parse or render failure leaves the original LaTeX visible.
|
|
238
|
+
- On terminals without Kitty graphics support, TFormula remains a transparent
|
|
239
|
+
PTY proxy.
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
npm run build
|
|
245
|
+
npm test
|
|
246
|
+
npm run check
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The test suite covers delimiter inference, false-positive filtering, Unicode
|
|
250
|
+
column positions, terminal response parsing, text-relative geometry, Kitty
|
|
251
|
+
encoding, MathJax-to-PNG rendering, and generic command wrapping.
|
|
252
|
+
|
|
253
|
+
For an end-to-end diagnostic, add `--debug`. A successful render reports the
|
|
254
|
+
terminal cell size, source location, and generated pixel rectangle without
|
|
255
|
+
printing the formula image bytes.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# TFormula
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
直接在终端中渲染**任意 CLI Agent** 输出的 LaTeX 公式。
|
|
6
|
+
|
|
7
|
+
> **推荐在 Ghostty 中使用。** TFormula 主要基于 Ghostty 开发和测试,也支持
|
|
8
|
+
> Kitty、WezTerm 等实现了 Kitty Graphics Protocol 的终端。
|
|
9
|
+
|
|
10
|
+
TFormula 是一个与 Agent 无关的 PTY 代理。它不调用 Codex、Claude、Gemini
|
|
11
|
+
或其他 Agent 的专用 API,因此只要 Agent 能在终端中运行,就可以尝试使用
|
|
12
|
+
TFormula 包装。
|
|
13
|
+
|
|
14
|
+
Agent 看到的仍然是普通终端。TFormula 原样转发 ANSI 输出,同时维护一份
|
|
15
|
+
headless 终端画面,检测其中的 TeX,使用本地 MathJax 渲染,再通过 Kitty
|
|
16
|
+
图形协议把公式图片覆盖到原始文字上。原始 LaTeX 仍保留在终端缓冲区中,
|
|
17
|
+
可以正常复制。
|
|
18
|
+
|
|
19
|
+
## 在 Ghostty 中快速开始
|
|
20
|
+
|
|
21
|
+
打开 Ghostty,无需全局安装即可直接运行:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx --yes tformula@latest codex
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
也可以先全局安装一次,以后使用更短的命令:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install --global tformula
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
然后把 `tformula` 放在你平时使用的 Agent 命令前面:
|
|
34
|
+
|
|
35
|
+
| Agent | 命令 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| OpenAI Codex | `tformula codex` |
|
|
38
|
+
| Claude Code | `tformula claude` |
|
|
39
|
+
| Cursor Agent | `tformula agent` |
|
|
40
|
+
| Pi coding agent | `tformula pi` |
|
|
41
|
+
| Gemini CLI | `tformula gemini` |
|
|
42
|
+
| OpenCode | `tformula opencode` |
|
|
43
|
+
| Aider | `tformula aider` |
|
|
44
|
+
| Goose | `tformula goose` |
|
|
45
|
+
| Qwen Code | `tformula qwen` |
|
|
46
|
+
| 任意其他 CLI Agent | `tformula -- <agent命令> [参数...]` |
|
|
47
|
+
|
|
48
|
+
相应的 Agent 本身需要已经安装,并且可以从当前 `PATH` 中找到。TFormula
|
|
49
|
+
只负责包装命令,不负责安装 Agent。
|
|
50
|
+
|
|
51
|
+
如果不想全局安装,把上表中的 `tformula` 换成
|
|
52
|
+
`npx --yes tformula@latest` 即可。例如:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npx --yes tformula@latest codex
|
|
56
|
+
npx --yes tformula@latest claude
|
|
57
|
+
npx --yes tformula@latest agent
|
|
58
|
+
npx --yes tformula@latest pi
|
|
59
|
+
npx --yes tformula@latest gemini
|
|
60
|
+
npx --yes tformula@latest opencode
|
|
61
|
+
npx --yes tformula@latest aider
|
|
62
|
+
npx --yes tformula@latest goose
|
|
63
|
+
npx --yes tformula@latest -- claude --resume
|
|
64
|
+
npx --yes tformula@latest --shell
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`--shell` 会启动一个增强的登录 Shell。在这个 Shell 中启动命令时,不需要
|
|
68
|
+
为每一个 Agent 单独创建别名:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
tformula --shell
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
不带参数运行 `tformula` 等价于 `tformula --shell`。
|
|
75
|
+
|
|
76
|
+
## 环境要求
|
|
77
|
+
|
|
78
|
+
- 推荐 Ghostty;也支持 Kitty、WezTerm 或其他兼容 Kitty 图形协议的终端
|
|
79
|
+
- macOS 或 Linux
|
|
80
|
+
- Node.js 20 或更高版本
|
|
81
|
+
- 需要运行的 CLI Agent
|
|
82
|
+
|
|
83
|
+
当前实现主要基于 Ghostty 1.3.1 开发和验证。
|
|
84
|
+
|
|
85
|
+
## 从源码安装
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
git clone https://github.com/mikewang817/TFormula.git
|
|
89
|
+
cd TFormula
|
|
90
|
+
npm install
|
|
91
|
+
npm run check
|
|
92
|
+
npm link
|
|
93
|
+
tformula codex
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 公式大小
|
|
97
|
+
|
|
98
|
+
TFormula 会在启动时以及终端尺寸变化后查询单元格的像素尺寸,再把 MathJax
|
|
99
|
+
公式的自然 `ex` 高度映射到终端文字高度。因此普通数学符号会与周围文字大小
|
|
100
|
+
接近,而分式、求和与导数仍保留自然的上下结构。
|
|
101
|
+
|
|
102
|
+
公式不会为了填满源码区域而强制放大;只有在超出原始行列范围时才会缩小。
|
|
103
|
+
这是因为全屏 TUI 中不能随意插入额外行,否则会破坏 Agent 的光标坐标。
|
|
104
|
+
|
|
105
|
+
对于很长的 Agent 输出,TFormula 大约每输出三分之一屏就建立一个检查点,
|
|
106
|
+
先完成公式检测和 placement,再继续输出。这样公式会与文字一起进入 scrollback,
|
|
107
|
+
不会因为整段回复一次滚过屏幕而漏掉。
|
|
108
|
+
|
|
109
|
+
当使用 `CMD +` 或 `CMD -` 改变字体大小时,新尺寸图片准备完成之前,旧公式
|
|
110
|
+
会继续保留。替换操作是事务式的:只有新 placement 可以创建时,才删除旧
|
|
111
|
+
placement。xterm marker 会在终端 reflow 后继续跟踪公式所在行,因此滚出屏幕
|
|
112
|
+
的已渲染公式不会退回 LaTeX 源码。
|
|
113
|
+
|
|
114
|
+
如果终端无法返回单元格像素尺寸,默认使用 9×18 像素。也可以手动指定:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
tformula --cell-size 10x20 claude
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
默认公式比例为 1.0,可以独立调整:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
tformula --scale 1.1 codex
|
|
124
|
+
TFORMULA_SCALE=0.9 tformula --shell
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 公式缓存
|
|
128
|
+
|
|
129
|
+
公式缓存采用内容寻址方式,并由当前用户启动的所有 TFormula Agent 进程共享:
|
|
130
|
+
|
|
131
|
+
- 同一个规范化公式只进行一次 MathJax SVG 排版。
|
|
132
|
+
- 每一种精确的字号、颜色、终端单元格和显示区域只生成一次 PNG。
|
|
133
|
+
- 多个 Agent 同时请求相同公式时,通过跨进程文件锁避免重复生成。
|
|
134
|
+
- 同一终端会话中的相同 PNG 只上传一次,不同位置共用图片数据并创建各自的
|
|
135
|
+
placement。
|
|
136
|
+
- 缩放回之前使用过的字号时,直接复用已有 PNG。
|
|
137
|
+
|
|
138
|
+
macOS 默认缓存目录:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
~/Library/Caches/TFormula
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Linux 默认使用:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
$XDG_CACHE_HOME/tformula
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
或:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
~/.cache/tformula
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
可以修改目录或默认的 256 MB 上限:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
TFORMULA_CACHE_DIR=/path/to/cache tformula codex
|
|
160
|
+
TFORMULA_CACHE_MAX_MB=512 tformula claude
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 可识别的公式
|
|
164
|
+
|
|
165
|
+
TFormula 支持这些显式形式:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
\[ ... \]
|
|
169
|
+
$$ ... $$
|
|
170
|
+
\( ... \)
|
|
171
|
+
$ ... $
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
某些 Agent TUI 在渲染 Markdown 时会吃掉 `\[`、`\]` 或 `\(`、`\)` 外层的
|
|
175
|
+
反斜杠。为兼容这种情况,TFormula 也会谨慎识别:
|
|
176
|
+
|
|
177
|
+
- 包含 `\frac`、`\sum`、上下标或花括号参数的裸 `[` / `]` 公式块;
|
|
178
|
+
- 包含明确 TeX 命令或数学结构的 `(\rho)` 等括号表达式;
|
|
179
|
+
- `E=mc^2`、`p=0`、`c^2` 等短方程;
|
|
180
|
+
- `- (\rho):电荷密度` 等符号定义项;
|
|
181
|
+
- 连续符号定义列表,并将其渲染为对齐的两列表格。
|
|
182
|
+
|
|
183
|
+
普通文字括号和 `$12.50` 等价格不会被当作公式。
|
|
184
|
+
|
|
185
|
+
## 命令选项
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
--shell 启动登录 Shell
|
|
189
|
+
--no-math 仅作为透明 PTY 代理,不渲染公式
|
|
190
|
+
--scale <number> 公式相对终端文字的比例,范围 0.5~2.0
|
|
191
|
+
--cell-size <WxH> 手动指定终端单元格像素尺寸
|
|
192
|
+
-C, --cwd <directory> 指定 Agent 的工作目录
|
|
193
|
+
--debug 输出检测与尺寸调试信息
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
当 Agent 参数可能被误认为 TFormula 参数时,使用 `--` 分隔:
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
tformula -- claude --resume
|
|
200
|
+
tformula -- opencode --continue
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 安全与降级行为
|
|
204
|
+
|
|
205
|
+
- MathJax 和字体全部在本地运行,不使用 CDN。
|
|
206
|
+
- 单个公式最长 8192 个字符。
|
|
207
|
+
- 禁止 `\require`、`\href`、`\url` 以及 MathJax HTML/样式命令等可能加载
|
|
208
|
+
外部内容的命令。
|
|
209
|
+
- 解析或渲染失败时保留原始 LaTeX,不阻断 Agent。
|
|
210
|
+
- 终端不支持 Kitty 图形协议时,TFormula 会退化为透明 PTY 代理。
|
|
211
|
+
|
|
212
|
+
## 开发
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
npm run build
|
|
216
|
+
npm test
|
|
217
|
+
npm run check
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
需要端到端诊断时添加 `--debug`。成功渲染会报告终端单元格尺寸、源码位置和
|
|
221
|
+
最终像素矩形,但不会打印图片数据。
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { probeTerminal } from "./probe.js";
|
|
7
|
+
import { runProxy } from "./proxy.js";
|
|
8
|
+
const { version: VERSION } = createRequire(import.meta.url)("../package.json");
|
|
9
|
+
const HELP = `TFormula ${VERSION}
|
|
10
|
+
|
|
11
|
+
Render LaTeX produced by any terminal agent while preserving the original TUI.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
tformula [options] [--] [command ...]
|
|
15
|
+
tformula --shell
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
tformula codex
|
|
19
|
+
tformula claude
|
|
20
|
+
tformula -- gemini --model gemini-2.5-pro
|
|
21
|
+
tformula --shell
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--shell Start the login shell (default when no command is given)
|
|
25
|
+
--no-math Run only as a transparent PTY proxy
|
|
26
|
+
--scale <number> Formula-to-terminal text scale, default 1.0
|
|
27
|
+
--cell-size <WxH> Override terminal cell pixels, for example 9x18
|
|
28
|
+
-C, --cwd <directory> Child working directory
|
|
29
|
+
--debug Print diagnostics
|
|
30
|
+
-h, --help Show help
|
|
31
|
+
-V, --version Show version
|
|
32
|
+
|
|
33
|
+
TFormula queries the terminal's actual cell pixel size. Formulas keep their
|
|
34
|
+
natural MathJax proportions and are only reduced when they do not fit.
|
|
35
|
+
`;
|
|
36
|
+
function fail(message) {
|
|
37
|
+
process.stderr.write(`tformula: ${message}\n`);
|
|
38
|
+
process.exit(2);
|
|
39
|
+
}
|
|
40
|
+
export function parseArgs(argv) {
|
|
41
|
+
let cwd = process.cwd();
|
|
42
|
+
let renderMath = true;
|
|
43
|
+
let debug = false;
|
|
44
|
+
let scale = Number(process.env.TFORMULA_SCALE ?? "1");
|
|
45
|
+
let cellOverride;
|
|
46
|
+
let forceShell = false;
|
|
47
|
+
const commandParts = [];
|
|
48
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
49
|
+
const value = argv[index];
|
|
50
|
+
if (value === "--") {
|
|
51
|
+
commandParts.push(...argv.slice(index + 1));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if (commandParts.length > 0 || !value.startsWith("-")) {
|
|
55
|
+
commandParts.push(value);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (value === "-h" || value === "--help")
|
|
59
|
+
return "help";
|
|
60
|
+
if (value === "-V" || value === "--version")
|
|
61
|
+
return "version";
|
|
62
|
+
if (value === "--shell")
|
|
63
|
+
forceShell = true;
|
|
64
|
+
else if (value === "--no-math")
|
|
65
|
+
renderMath = false;
|
|
66
|
+
else if (value === "--debug")
|
|
67
|
+
debug = true;
|
|
68
|
+
else if (value === "--scale")
|
|
69
|
+
scale = Number(argv[++index]);
|
|
70
|
+
else if (value === "-C" || value === "--cwd")
|
|
71
|
+
cwd = argv[++index] ?? fail(`${value} requires a directory`);
|
|
72
|
+
else if (value === "--cell-size") {
|
|
73
|
+
const raw = argv[++index] ?? fail("--cell-size requires WIDTHxHEIGHT");
|
|
74
|
+
const match = raw.match(/^(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)$/u);
|
|
75
|
+
if (!match)
|
|
76
|
+
fail("--cell-size must look like 9x18");
|
|
77
|
+
cellOverride = { width: Number(match[1]), height: Number(match[2]) };
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
fail(`unknown option ${value}`);
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isFinite(scale) || scale < 0.5 || scale > 2)
|
|
83
|
+
fail("--scale must be between 0.5 and 2");
|
|
84
|
+
if (forceShell && commandParts.length > 0)
|
|
85
|
+
fail("--shell cannot be combined with a command");
|
|
86
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
87
|
+
const command = forceShell || commandParts.length === 0 ? shell : commandParts[0];
|
|
88
|
+
const args = forceShell || commandParts.length === 0 ? ["-l"] : commandParts.slice(1);
|
|
89
|
+
return { command, args, cwd, renderMath, debug, scale, cellOverride };
|
|
90
|
+
}
|
|
91
|
+
async function main() {
|
|
92
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
93
|
+
if (parsed === "help") {
|
|
94
|
+
process.stdout.write(HELP);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (parsed === "version") {
|
|
98
|
+
process.stdout.write(`${VERSION}\n`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const { capabilities, pendingInput } = await probeTerminal(parsed.cellOverride);
|
|
102
|
+
const exitCode = await runProxy(parsed, capabilities, pendingInput);
|
|
103
|
+
process.exitCode = exitCode;
|
|
104
|
+
}
|
|
105
|
+
let invokedDirectly = false;
|
|
106
|
+
if (process.argv[1]) {
|
|
107
|
+
try {
|
|
108
|
+
// npm link invokes this file through a symlink in the global bin folder.
|
|
109
|
+
invokedDirectly = realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
invokedDirectly = false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (invokedDirectly) {
|
|
116
|
+
main().catch((error) => {
|
|
117
|
+
process.stderr.write(`tformula: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { FormulaRegion } from "./types.js";
|
|
2
|
+
declare function mathScore(value: string): number;
|
|
3
|
+
declare function visualColumn(line: string, utf16Index: number): number;
|
|
4
|
+
interface ParenthesizedSegment {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
body: string;
|
|
8
|
+
}
|
|
9
|
+
declare function parenthesizedSegments(line: string): ParenthesizedSegment[];
|
|
10
|
+
declare function looksLikeAsciiMath(value: string): boolean;
|
|
11
|
+
declare function isLikelyMath(value: string): boolean;
|
|
12
|
+
declare function inferredParenthesizedMath(line: string): ParenthesizedSegment[];
|
|
13
|
+
interface DefinitionItem {
|
|
14
|
+
body: string;
|
|
15
|
+
descriptionLatex: string;
|
|
16
|
+
startCol: number;
|
|
17
|
+
lineWidth: number;
|
|
18
|
+
}
|
|
19
|
+
declare function escapeTexText(value: string): string;
|
|
20
|
+
declare function descriptionToLatex(value: string): string;
|
|
21
|
+
declare function definitionItem(line: string): DefinitionItem | undefined;
|
|
22
|
+
declare function inferredDefinitionGroup(lines: string[], startRow: number): FormulaRegion | undefined;
|
|
23
|
+
declare function isStandaloneDisplayLine(line: string): boolean;
|
|
24
|
+
declare function expandStandaloneDisplayRegions(lines: string[], regions: FormulaRegion[]): FormulaRegion[];
|
|
25
|
+
declare function trailingInlineRegion(lines: string[], row: number, line: string, startIndex: number, endIndex: number, latex: string, confidence: FormulaRegion["confidence"]): FormulaRegion;
|
|
26
|
+
/**
|
|
27
|
+
* Detect formulas in the post-ANSI terminal screen. Explicit TeX delimiters are
|
|
28
|
+
* preferred. A conservative inferred form handles TUIs that turn `\[`/`\]`
|
|
29
|
+
* into bare bracket lines while leaving the TeX body visible.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectFormulaRegions(lines: string[]): FormulaRegion[];
|
|
32
|
+
export declare const detectorInternals: {
|
|
33
|
+
descriptionToLatex: typeof descriptionToLatex;
|
|
34
|
+
definitionItem: typeof definitionItem;
|
|
35
|
+
escapeTexText: typeof escapeTexText;
|
|
36
|
+
expandStandaloneDisplayRegions: typeof expandStandaloneDisplayRegions;
|
|
37
|
+
inferredDefinitionGroup: typeof inferredDefinitionGroup;
|
|
38
|
+
inferredParenthesizedMath: typeof inferredParenthesizedMath;
|
|
39
|
+
isLikelyMath: typeof isLikelyMath;
|
|
40
|
+
isStandaloneDisplayLine: typeof isStandaloneDisplayLine;
|
|
41
|
+
looksLikeAsciiMath: typeof looksLikeAsciiMath;
|
|
42
|
+
mathScore: typeof mathScore;
|
|
43
|
+
parenthesizedSegments: typeof parenthesizedSegments;
|
|
44
|
+
trailingInlineRegion: typeof trailingInlineRegion;
|
|
45
|
+
visualColumn: typeof visualColumn;
|
|
46
|
+
};
|
|
47
|
+
export {};
|