texai 0.1.0__tar.gz

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.
Files changed (57) hide show
  1. texai-0.1.0/.claude/settings.local.json +8 -0
  2. texai-0.1.0/.gitignore +19 -0
  3. texai-0.1.0/LICENSE +21 -0
  4. texai-0.1.0/PKG-INFO +338 -0
  5. texai-0.1.0/README.md +311 -0
  6. texai-0.1.0/example/main.tex +81 -0
  7. texai-0.1.0/example/sections/data.tex +48 -0
  8. texai-0.1.0/example/sections/model.tex +78 -0
  9. texai-0.1.0/example/sections/results.tex +77 -0
  10. texai-0.1.0/example/sections/robustness.tex +36 -0
  11. texai-0.1.0/pyproject.toml +56 -0
  12. texai-0.1.0/skills/review-latex-pdf/SKILL.md +109 -0
  13. texai-0.1.0/src/texai/__init__.py +5 -0
  14. texai-0.1.0/src/texai/__main__.py +4 -0
  15. texai-0.1.0/src/texai/agent.py +366 -0
  16. texai-0.1.0/src/texai/build.py +177 -0
  17. texai-0.1.0/src/texai/cli.py +152 -0
  18. texai-0.1.0/src/texai/config.py +103 -0
  19. texai-0.1.0/src/texai/events.py +105 -0
  20. texai-0.1.0/src/texai/hunks.py +120 -0
  21. texai-0.1.0/src/texai/models.py +102 -0
  22. texai-0.1.0/src/texai/navigate.py +61 -0
  23. texai-0.1.0/src/texai/paths.py +122 -0
  24. texai-0.1.0/src/texai/prompt.py +80 -0
  25. texai-0.1.0/src/texai/selection.py +90 -0
  26. texai-0.1.0/src/texai/server.py +396 -0
  27. texai-0.1.0/src/texai/snapshots.py +187 -0
  28. texai-0.1.0/src/texai/static/api.js +41 -0
  29. texai-0.1.0/src/texai/static/app.js +296 -0
  30. texai-0.1.0/src/texai/static/chat.js +574 -0
  31. texai-0.1.0/src/texai/static/index.html +94 -0
  32. texai-0.1.0/src/texai/static/marks.js +193 -0
  33. texai-0.1.0/src/texai/static/styles.css +551 -0
  34. texai-0.1.0/src/texai/static/toast.js +20 -0
  35. texai-0.1.0/src/texai/static/vendor/pdfjs/LICENSE +177 -0
  36. texai-0.1.0/src/texai/static/vendor/pdfjs/VERSION +1 -0
  37. texai-0.1.0/src/texai/static/vendor/pdfjs/pdf.mjs +24293 -0
  38. texai-0.1.0/src/texai/static/vendor/pdfjs/pdf.worker.mjs +58272 -0
  39. texai-0.1.0/src/texai/static/vendor/pdfjs/text_layer.css +127 -0
  40. texai-0.1.0/src/texai/static/viewer.js +403 -0
  41. texai-0.1.0/src/texai/synctex.py +326 -0
  42. texai-0.1.0/src/texai/transcript.py +165 -0
  43. texai-0.1.0/src/texai/turns.py +369 -0
  44. texai-0.1.0/tests/browser/layout.mjs +325 -0
  45. texai-0.1.0/tests/browser/run.sh +62 -0
  46. texai-0.1.0/tests/test_api.py +310 -0
  47. texai-0.1.0/tests/test_build.py +118 -0
  48. texai-0.1.0/tests/test_events.py +139 -0
  49. texai-0.1.0/tests/test_hunks.py +114 -0
  50. texai-0.1.0/tests/test_navigate.py +104 -0
  51. texai-0.1.0/tests/test_paths.py +178 -0
  52. texai-0.1.0/tests/test_selection_write.py +125 -0
  53. texai-0.1.0/tests/test_snapshots.py +141 -0
  54. texai-0.1.0/tests/test_synctex_parse.py +168 -0
  55. texai-0.1.0/tests/test_transcript.py +137 -0
  56. texai-0.1.0/tests/test_turns.py +255 -0
  57. texai-0.1.0/uv.lock +1129 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" https://pypi.org/pypi/texai/json)",
5
+ "Bash(uv build *)"
6
+ ]
7
+ }
8
+ }
texai-0.1.0/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ # texai runtime state
2
+ .texai/
3
+
4
+ # python
5
+ __pycache__/
6
+ *.py[cod]
7
+ .venv/
8
+ .pytest_cache/
9
+ *.egg-info/
10
+
11
+ # latex build output
12
+ *.aux
13
+ *.fdb_latexmk
14
+ *.fls
15
+ *.log
16
+ *.out
17
+ *.synctex.gz
18
+ *.toc
19
+ example/main.pdf
texai-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thibaut Lamadon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
texai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,338 @@
1
+ Metadata-Version: 2.5
2
+ Name: texai
3
+ Version: 0.1.0
4
+ Summary: A canvas for LaTeX: read the compiled PDF, point at a passage, and a coding agent edits the source and rebuilds it.
5
+ Project-URL: Homepage, https://github.com/tlamadon/texai
6
+ Project-URL: Repository, https://github.com/tlamadon/texai
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: agent,claude,editing,latex,pdf,synctex
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: fastapi>=0.115
22
+ Requires-Dist: pydantic>=2.7
23
+ Requires-Dist: uvicorn>=0.30
24
+ Provides-Extra: agent
25
+ Requires-Dist: claude-agent-sdk>=0.2.138; extra == 'agent'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # texai
29
+
30
+ A canvas for LaTeX: read the compiled PDF on the left, talk to a coding agent on
31
+ the right. Cmd/Ctrl-click a passage to attach it to your message; the agent edits
32
+ the source, `texai` rebuilds the document, and the page reloads in place.
33
+
34
+ The bridge that makes it work is SyncTeX — a pixel on page 7 becomes
35
+ `sections/model.tex:143`, which is what the agent actually needs.
36
+
37
+ ## Quick start
38
+
39
+ ```bash
40
+ uv sync
41
+ uv run texai --root ./example --pdf ./example/main.pdf
42
+ ```
43
+
44
+ Then open <http://127.0.0.1:8765/>.
45
+
46
+ The example is not compiled yet; build it first (needs a TeX installation):
47
+
48
+ ```bash
49
+ cd example && latexmk -pdf -synctex=1 -interaction=nonstopmode main.tex && cd ..
50
+ uv run texai --root ./example --pdf ./example/main.pdf --open
51
+ ```
52
+
53
+ On a real project:
54
+
55
+ ```bash
56
+ uv run texai --root /path/to/paper --pdf build/main.pdf --open
57
+ ```
58
+
59
+ ### Turning on the agent
60
+
61
+ The chat panel needs the Claude Agent SDK and the `claude` CLI. Without them the
62
+ viewer, the SyncTeX bridge and the selection file all still work, and the panel
63
+ tells you what is missing.
64
+
65
+ ```bash
66
+ uv sync --extra agent # installs claude-agent-sdk
67
+ ```
68
+
69
+ The agent runs as a normal local Claude Code session and uses whatever
70
+ credentials `claude` is already logged in with — your subscription, if that is
71
+ how you use Claude Code. No API key needed, and turns count against that
72
+ account exactly as they would in the terminal.
73
+
74
+ (The Agent SDK docs do restrict *redistributing* subscription login: shipping
75
+ this to other people who sign in with their own Claude accounts needs approval
76
+ from Anthropic, and API-key auth otherwise. That is about distribution, not
77
+ about running it yourself.)
78
+
79
+ ## The loop
80
+
81
+ 1. **Attach.** Cmd-click (macOS) / Ctrl-click a passage. It becomes a chip on
82
+ the composer with its `file:line` and, if you selected text first, the
83
+ rendered text. Each chip takes its own note, so you can mark up several
84
+ passages and send them as one batch.
85
+ 2. **Send.** The chips plus your message become one prompt.
86
+ 3. **Edit.** The agent reads and edits the source. Its text, tool calls and
87
+ thinking show up live in the panel.
88
+ 4. **Build.** `texai` compiles — not the agent. If the build breaks, the
89
+ errors go back to the agent and it tries again (up to three attempts).
90
+ 5. **See it.** On success the viewer reloads the rebuilt PDF, keeping your page,
91
+ zoom and scroll position. The turn shows which files changed, with a diff you
92
+ can expand and a **Revert** button.
93
+
94
+ If the document still does not compile after the retries, the whole turn is
95
+ rolled back. A half-applied batch on top of a broken document is worse than
96
+ nothing.
97
+
98
+ Turns that only answer a question — no file changed — skip the build entirely.
99
+
100
+ ### Inline changes on the page
101
+
102
+ Toggle **Show changes** in the toolbar (off by default, remembered). After a
103
+ turn, every changed region is forward-mapped through `synctex view` — the click
104
+ direction run backwards — and drawn as a band over the text it produced. Click
105
+ one:
106
+
107
+ ```
108
+ sections/model.tex:9
109
+ - labour. The elasticity $\alpha$ is the parameter a reader is most likely to
110
+ + labor. The elasticity $\alpha$ is the parameter a reader is most likely to
111
+ [ Accept ] [ Reject ]
112
+ ```
113
+
114
+ **Reject** rolls back that one change and leaves the rest of the turn alone, so
115
+ you can keep three edits out of four from a batch. It is a reconstruction, not a
116
+ patch: the file is rebuilt from the turn's snapshot with the rejected region
117
+ taken from the old text, then recompiled. Hunk ids are content-derived rather
118
+ than positional, so rejecting one does not renumber the others.
119
+
120
+ **Accept** just marks it reviewed. Nothing blocks — ignore the markers and keep
121
+ working if you would rather.
122
+
123
+ Where it is approximate, honestly:
124
+
125
+ - Highlights are **line-level**. SyncTeX reports the line box, which is the full
126
+ column width, so a one-word change highlights its whole line.
127
+ - **Deletions** have no place in the new PDF and anchor to the neighbouring line.
128
+ - Changes inside math, floats or `tikz` tend to map to the environment's opening
129
+ line — the same weakness the click direction has.
130
+ - Only the most recent turn's changes are shown; older turns' line numbers have
131
+ moved on.
132
+
133
+ ### Moving the view
134
+
135
+ Any `file:line` in the chat is clickable — a selection you attached, a changed
136
+ file, a chip — and the PDF scrolls there with a brief highlight.
137
+
138
+ The agent can move the view too. It has one in-process tool,
139
+ `show_in_pdf(file, line, why)`, so you can ask for things it has to go and find:
140
+
141
+ > take me to the summary statistics table
142
+
143
+ It greps the source, locates the caption, calls the tool, and the page scrolls
144
+ under you:
145
+
146
+ ```
147
+ ⏵ Grep summary statistics in .
148
+ ⎿ sections/data.tex:33: \caption{Summary statistics for the estimation sample…}
149
+ ⏵ mcp__texai__show_in_pdf file: sections/data.tex, line: 26
150
+ ⎿ Showing sections/data.tex:26 — the view moved to page 3.
151
+ ```
152
+
153
+ The tool only scrolls; it cannot change anything.
154
+
155
+ ### Two views, one stream
156
+
157
+ The panel has a **Chat** tab and a **Transcript** tab over the same underlying
158
+ stream of entries, so the transcript cannot show something the chat quietly
159
+ dropped.
160
+
161
+ - **Chat** — prose in full, everything else as one-line activity: which files
162
+ were touched, whether the build passed, the diff, the revert button, the cost.
163
+ - **Transcript** — the session the way a terminal shows it: your prompt, every
164
+ tool call with its arguments, results, thinking markers, build lines, and a
165
+ per-turn rule with status, line counts and cost.
166
+
167
+ ```
168
+ ──── t0001 ────
169
+ › The user marked 1 passage while reading `main.pdf`: …
170
+
171
+ ● I'll read the file around that line first.
172
+
173
+ ⏵ Read sections/model.tex
174
+ ⎿ 1 \section{Model} (+68 more lines)
175
+
176
+ ⏵ Edit sections/model.tex
177
+ old_string: labour. The elasticity
178
+ new_string: labor. The elasticity
179
+ ⎿ The file … has been updated successfully.
180
+
181
+ ✓ success · 3 turns · 5.6s · $0.0425
182
+
183
+ ⚙ build Build succeeded.
184
+ ──── t0001 applied · 1 file · +1/−1 · $0.0425 ────
185
+ ```
186
+
187
+ Full output is kept only for failures — a successful `Read` would otherwise
188
+ paste the whole file into the transcript, which is exactly what a terminal does
189
+ *not* show you. SDK bookkeeping (init handshakes, token tallies) is filtered.
190
+
191
+ ### Attaching a real terminal
192
+
193
+ The panel is not an emulator, and a real terminal is better at being one. Once a
194
+ turn has run, **Attach terminal** copies:
195
+
196
+ ```bash
197
+ claude --resume <session-id>
198
+ ```
199
+
200
+ Run that in your project directory and Claude Code picks up the *same*
201
+ conversation — full TUI, slash commands, everything. Drive one at a time: the
202
+ browser panel and a terminal both writing to one session will confuse both.
203
+
204
+ ### Options
205
+
206
+ | Flag | Default | Meaning |
207
+ | --- | --- | --- |
208
+ | `--root` | required | project root; everything stays inside it |
209
+ | `--pdf` | required | PDF to review, relative to `--root` or to your shell |
210
+ | `--build-cmd` | `latexmk -pdf -synctex=1 -interaction=nonstopmode <root.tex>` | how to rebuild |
211
+ | `--build-dir` | the root `.tex` file's directory | where to run the build |
212
+ | `--model` | the CLI's default | model for the agent |
213
+ | `--port` | `8765` | port on `127.0.0.1`; the next free one is used if busy |
214
+ | `--synctex` | `synctex` | path to the `synctex` executable |
215
+ | `--open` | off | open the viewer in your browser |
216
+
217
+ `--build-cmd` is split with `shlex` and run without a shell, so `&&` and pipes
218
+ are not supported — point it at a script if you need them.
219
+
220
+ ## Without the agent
221
+
222
+ Everything still works as a plain review tool. Every click is written to
223
+ `<project-root>/.texai/current-selection.json`, atomically, so an agent
224
+ running anywhere else can read it:
225
+
226
+ ```json
227
+ {
228
+ "version": 1,
229
+ "updatedAt": "2026-08-13T18:20:31-05:00",
230
+ "pdf": "build/main.pdf",
231
+ "page": 7,
232
+ "pdfPosition": {"x": 241.3, "y": 418.2},
233
+ "source": {"file": "sections/model.tex", "line": 143, "column": 1},
234
+ "selectedText": null
235
+ }
236
+ ```
237
+
238
+ `pdfPosition` is in PDF points from the **top-left** corner of the page, the
239
+ convention SyncTeX uses. Paths are POSIX and relative to the project root.
240
+ `column` is `1` when SyncTeX reports none, which is usually.
241
+
242
+ There is also a **Copy reference** button:
243
+
244
+ ```text
245
+ PDF selection: sections/model.tex:143
246
+ Rendered text: “the estimated elasticity”
247
+ ```
248
+
249
+ `skills/review-latex-pdf/SKILL.md` teaches an external coding agent to drive
250
+ that flow.
251
+
252
+ ## Requirements
253
+
254
+ - `uv` and Python ≥ 3.10.
255
+ - A TeX installation providing `synctex` and `latexmk`.
256
+ - A PDF compiled with SyncTeX enabled — a `.synctex.gz` next to it.
257
+ - For the chat panel: `claude-agent-sdk` and the `claude` CLI.
258
+
259
+ Missing pieces are reported specifically rather than as a generic failure: the
260
+ CLI warns at startup, and clicking or sending tells you exactly which piece is
261
+ absent.
262
+
263
+ ## Safety
264
+
265
+ - Binds to `127.0.0.1` only and rejects non-loopback `Host` headers.
266
+ - `synctex` and the build command are invoked with argument lists and
267
+ `shell=False`. Nothing from the browser reaches a shell.
268
+ - Every path — the PDF, whatever SyncTeX reports, every selection the browser
269
+ sends back — is resolved through symlinks and must stay inside `--root`.
270
+ - The agent gets `Read`, `Write`, `Edit`, `Glob` and `Grep`. No `Bash`, no web
271
+ access, no subagents. It cannot run the build; the harness does that.
272
+ - The agent gets one tool of our own, `show_in_pdf`, which scrolls your view
273
+ and nothing else.
274
+ - Every turn is snapshotted before the agent starts, so any turn can be undone
275
+ and a turn that breaks the build undoes itself. Snapshots are plain file
276
+ copies in a temp directory keyed by project — deliberately *outside* the
277
+ project, or they would turn up in the agent's own Glob and Grep results. No
278
+ git history is written.
279
+
280
+ ## Third-party code
281
+
282
+ `src/texai/static/vendor/pdfjs/` is a pinned copy of
283
+ [PDF.js](https://github.com/mozilla/pdf.js) 5.4.149 (Apache-2.0), vendored so
284
+ the viewer needs no CDN at runtime. Its licence travels with it in that
285
+ directory. Everything else here is MIT — see `LICENSE`.
286
+
287
+ ## Development
288
+
289
+ ```bash
290
+ uv sync --extra agent
291
+ uv run pytest # unit + orchestration tests
292
+ tests/browser/run.sh # layout regression checks (needs node + Chrome)
293
+ ```
294
+
295
+ The browser checks exist because every layout bug here has been invisible to
296
+ both the Python tests and to DOM-property assertions: a flex child missing
297
+ `min-height: 0` that pushed the page taller than the viewport, a `hidden`
298
+ attribute outranked by `display: flex`, and turn cards squashing to slivers
299
+ instead of scrolling. Only measured geometry catches those. They send no agent
300
+ turns, so they cost nothing to run.
301
+
302
+ ```
303
+ src/texai/
304
+ cli.py argparse + uvicorn on 127.0.0.1
305
+ config.py validated, root-confined paths and build settings
306
+ paths.py containment and project-relative normalization
307
+ synctex.py `synctex edit` invocation + output parsing
308
+ selection.py selection payload + atomic JSON write
309
+ models.py request validation
310
+ events.py pub/sub bus + SSE framing with replay
311
+ agent.py the Claude Agent SDK session
312
+ transcript.py one entry stream, rendered as chat or as a terminal log
313
+ prompt.py passages + instructions -> a prompt
314
+ build.py compiling, and reading errors out of the log
315
+ snapshots.py per-turn source copies, diffing and revert
316
+ hunks.py structured diff hunks: stable ids, per-hunk rollback
317
+ navigate.py locating a source line in the rendered PDF
318
+ turns.py snapshot -> agent -> build -> diff, or revert
319
+ server.py FastAPI routes
320
+ static/ vanilla two-panel UI + vendored PDF.js
321
+ (marks.js draws the inline change markers)
322
+ ```
323
+
324
+ ## Not built yet
325
+
326
+ - **`latexdiff` view.** The diff is textual (`.tex`). Compiling a marked-up
327
+ "changes" PDF and toggling it in the viewer is the natural next step, with the
328
+ textual diff as the fallback when `latexdiff` chokes on a document.
329
+ - **Streaming partial text.** Agent messages appear per block, not per token.
330
+ The SDK supports finer streaming (`include_partial_messages`); the panel does
331
+ not use it yet.
332
+ - **Word-level highlights.** Markers cover the whole line, because that is what
333
+ SyncTeX reports. Narrowing them means matching the diff text against the
334
+ PDF.js text layer.
335
+ - **An embedded interactive terminal.** The Transcript tab is read-only. A real
336
+ in-browser terminal (xterm.js over a PTY) would be a second agent process
337
+ unless it took over the loop entirely — which would cost the snapshot, build
338
+ and revert orchestration. `claude --resume` gets you the real thing instead.