papilot 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +126 -2
- package/README.zh-CN.md +127 -0
- package/bin/papilot.mjs +113 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/Inconsolatazi4-Regular-Dbtq7t_r.otf +0 -0
- package/dist/assets/TeXGyrePagellaX-Bold-CEtYdOXj.otf +0 -0
- package/dist/assets/TeXGyrePagellaX-Italic-CSuYIstn.otf +0 -0
- package/dist/assets/TeXGyrePagellaX-Regular-DQJjZE_L.otf +0 -0
- package/dist/assets/cursor-editorTextHighlight-kG3uUOwL.svg +8 -0
- package/dist/assets/index-BhQFCMTx.js +131 -0
- package/dist/assets/index-CAV3-87U.css +1 -0
- package/dist/assets/pdf-view-DJo0X38w.js +598 -0
- package/dist/assets/pdf.worker.min-CjEcRF4W.mjs +28 -0
- package/dist/favicon.png +0 -0
- package/dist/index.html +4 -0
- package/examples/papilot-paper/.latex-review.json +4 -0
- package/examples/papilot-paper/appendix/proofs.tex +60 -0
- package/examples/papilot-paper/appendix/reference.tex +54 -0
- package/examples/papilot-paper/main.tex +57 -0
- package/examples/papilot-paper/preamble.tex +53 -0
- package/examples/papilot-paper/references.bib +158 -0
- package/examples/papilot-paper/sections/01-introduction.tex +66 -0
- package/examples/papilot-paper/sections/02-using.tex +134 -0
- package/examples/papilot-paper/sections/03-background.tex +97 -0
- package/examples/papilot-paper/sections/04-model.tex +185 -0
- package/examples/papilot-paper/sections/05-conflicts.tex +99 -0
- package/examples/papilot-paper/sections/06-building.tex +70 -0
- package/examples/papilot-paper/sections/07-geometry.tex +193 -0
- package/examples/papilot-paper/sections/08-notes.tex +104 -0
- package/examples/papilot-paper/sections/09-agents.tex +106 -0
- package/examples/papilot-paper/sections/10-usage.tex +56 -0
- package/examples/papilot-paper/sections/11-git.tex +62 -0
- package/examples/papilot-paper/sections/12-security.tex +41 -0
- package/examples/papilot-paper/sections/13-evaluation.tex +106 -0
- package/examples/papilot-paper/sections/14-related.tex +38 -0
- package/examples/papilot-paper/sections/15-conclusion.tex +27 -0
- package/package.json +85 -4
- package/scripts/demo.ts +21 -0
- package/scripts/service.ts +35 -0
- package/src/notes.ts +40 -0
- package/src/server/agents.ts +343 -0
- package/src/server/codex.ts +63 -0
- package/src/server/frontend.ts +15 -0
- package/src/server/main.ts +202 -0
- package/src/server/manuscript.ts +337 -0
- package/src/server/paths.ts +10 -0
- package/src/server/pdf.ts +27 -0
- package/src/server/review.ts +53 -0
- package/src/server/security.ts +15 -0
- package/src/server/store.ts +60 -0
- package/src/server/workspace.ts +529 -0
- package/src/shared.ts +28 -0
- package/vendor/latex-review/LICENSE +21 -0
- package/vendor/latex-review/dist/web/assets/TeXGyrePagellaX-Bold-CEtYdOXj.otf +0 -0
- package/vendor/latex-review/dist/web/assets/TeXGyrePagellaX-Regular-DQJjZE_L.otf +0 -0
- package/vendor/latex-review/dist/web/assets/index-DyzYnIGk.css +1 -0
- package/vendor/latex-review/dist/web/assets/index-HxBK-dls.js +19 -0
- package/vendor/latex-review/dist/web/index.html +18 -0
- package/vendor/latex-review/package-lock.json +3114 -0
- package/vendor/latex-review/package.json +49 -0
- package/vendor/latex-review/src/cli.ts +494 -0
- package/vendor/latex-review/src/client.ts +198 -0
- package/vendor/latex-review/src/core/build.ts +296 -0
- package/vendor/latex-review/src/core/changes.ts +126 -0
- package/vendor/latex-review/src/core/config.ts +270 -0
- package/vendor/latex-review/src/core/diff.ts +54 -0
- package/vendor/latex-review/src/core/files.ts +96 -0
- package/vendor/latex-review/src/core/git.ts +317 -0
- package/vendor/latex-review/src/core/outline.ts +314 -0
- package/vendor/latex-review/src/core/process.ts +111 -0
- package/vendor/latex-review/src/core/publish.ts +339 -0
- package/vendor/latex-review/src/core/review.ts +734 -0
- package/vendor/latex-review/src/core/store.ts +66 -0
- package/vendor/latex-review/src/core/tasks.ts +237 -0
- package/vendor/latex-review/src/render/inline.ts +805 -0
- package/vendor/latex-review/src/render/locate.ts +481 -0
- package/vendor/latex-review/src/render/pdf.ts +281 -0
- package/vendor/latex-review/src/render/pdfdiff.ts +272 -0
- package/vendor/latex-review/src/render/round.ts +236 -0
- package/vendor/latex-review/src/render/worker.ts +10 -0
- package/vendor/latex-review/src/runtime.ts +23 -0
- package/vendor/latex-review/src/server.ts +401 -0
- package/vendor/latex-review/src/types.ts +283 -0
- package/vendor/latex-review/src/web/ChangeMap.tsx +212 -0
- package/vendor/latex-review/src/web/PdfStrip.tsx +77 -0
- package/vendor/latex-review/src/web/fonts/GUST-FONT-LICENSE.txt +29 -0
- package/vendor/latex-review/src/web/fonts/LPPL.txt +415 -0
- package/vendor/latex-review/src/web/fonts/MANIFEST-newpx.txt +20 -0
- package/vendor/latex-review/src/web/fonts/README-newpx.txt +473 -0
- package/vendor/latex-review/src/web/fonts/README.md +12 -0
- package/vendor/latex-review/src/web/fonts/TeXGyrePagellaX-Bold.otf +0 -0
- package/vendor/latex-review/src/web/fonts/TeXGyrePagellaX-Regular.otf +0 -0
- package/vendor/latex-review/src/web/index.html +17 -0
- package/vendor/latex-review/src/web/main.tsx +10 -0
- package/vendor/latex-review/src/web/model.ts +153 -0
- package/vendor/latex-review/src/web/overview.tsx +146 -0
- package/vendor/latex-review/src/web/review.tsx +991 -0
- package/vendor/latex-review/src/web/style.css +1249 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
\section{Notes on the PDF}
|
|
2
|
+
\label{sec:notes}
|
|
3
|
+
|
|
4
|
+
An author can also ask for changes directly on the PDF: select text, or mark a
|
|
5
|
+
region around a figure or an equation, and write a note. All pending notes go
|
|
6
|
+
to the agent as one task. For that task to be useful, each note must name the
|
|
7
|
+
source it refers to.
|
|
8
|
+
|
|
9
|
+
\subsection{From a mark to source lines}
|
|
10
|
+
|
|
11
|
+
A note is a set of rectangles $\{r_1, \dots, r_q\}$ in the normalised
|
|
12
|
+
coordinates of \cref{eq:normalised} on page $P$, together with a comment. For
|
|
13
|
+
every printed line the selection touches, Papilot asks SyncTeX for the source
|
|
14
|
+
position of the first and the last marked character~\cite{laurens2008}:
|
|
15
|
+
\begin{equation}
|
|
16
|
+
\mathcal{S}(P, x, y) = (\text{file}, \text{line}),
|
|
17
|
+
\qquad
|
|
18
|
+
L = \{\, \mathcal{S}(P, c) : c \in C_{\text{first}} \cup C_{\text{last}} \,\},
|
|
19
|
+
\qquad
|
|
20
|
+
[\ell_{-}, \ell_{+}] = [\min L, \max L].
|
|
21
|
+
\label{eq:synctex-range}
|
|
22
|
+
\end{equation}
|
|
23
|
+
|
|
24
|
+
SyncTeX records boxes and glue rather than characters. The space before a word
|
|
25
|
+
that begins a printed line comes from the line break of the \emph{previous}
|
|
26
|
+
source line, so such a word is credited to that line. Papilot therefore
|
|
27
|
+
\emph{settles} each end of the selection by matching its printed context
|
|
28
|
+
against the source near SyncTeX's answer.
|
|
29
|
+
|
|
30
|
+
\subsection{Settling a selection}
|
|
31
|
+
|
|
32
|
+
Let $\letters(\cdot)$ map text to its lowercase letters and digits, after
|
|
33
|
+
Unicode compatibility normalisation, with \TeX{} control sequences and comments
|
|
34
|
+
removed from the source. For a source window $[\ell_{-} - 4, \ell_{+} + 4]$ we
|
|
35
|
+
form the string $S = \letters(\text{lines})$ and remember for each position the
|
|
36
|
+
line it came from, $\pi : \{1, \dots, \abs{S}\} \to \N$. The distance of a
|
|
37
|
+
line from SyncTeX's range is
|
|
38
|
+
\begin{equation}
|
|
39
|
+
\dist(\ell) = \max\bigl(0,\ \ell_{-} - \ell,\ \ell - \ell_{+}\bigr).
|
|
40
|
+
\label{eq:line-distance}
|
|
41
|
+
\end{equation}
|
|
42
|
+
|
|
43
|
+
\begin{algorithm}[t]
|
|
44
|
+
\caption{Settling one end of a selection}
|
|
45
|
+
\label{alg:settle}
|
|
46
|
+
\begin{algorithmic}[1]
|
|
47
|
+
\Require printed line $p$, index $\alpha$ of the marked letter in $\letters(p)$, source string $S$ with line map $\pi$
|
|
48
|
+
\For{$\rho \in (\infty, 24, 10)$} \Comment{whole line first, then shorter context}
|
|
49
|
+
\State $o_0 \gets \max(0, \alpha - \rho)$
|
|
50
|
+
\State $q \gets \letters(p)_{[o_0,\ \alpha + \rho]}$
|
|
51
|
+
\State $O \gets \{ o : S_{[o,\, o + \abs{q})} = q \}$
|
|
52
|
+
\If{$O \neq \emptyset$}
|
|
53
|
+
\State $o^{\star} \gets \argmin_{o \in O} \dist\bigl(\pi(o + \alpha - o_0)\bigr)$
|
|
54
|
+
\State \Return $\pi\bigl(o^{\star} + \alpha - o_0\bigr)$
|
|
55
|
+
\EndIf
|
|
56
|
+
\EndFor
|
|
57
|
+
\State \Return SyncTeX's line \Comment{formulas and other unmatched text}
|
|
58
|
+
\end{algorithmic}
|
|
59
|
+
\end{algorithm}
|
|
60
|
+
|
|
61
|
+
\Cref{alg:settle} runs once for the first and once for the last marked
|
|
62
|
+
character. If the settled range is empty or inverted, Papilot keeps SyncTeX's
|
|
63
|
+
range. Because the comparison uses letters only, subscripts, macros and
|
|
64
|
+
hyphenation do not prevent a match; formulas, whose printed form shares no
|
|
65
|
+
letters with their source, fall back to SyncTeX by design.
|
|
66
|
+
|
|
67
|
+
\subsection{When is a match unique?}
|
|
68
|
+
|
|
69
|
+
\Cref{alg:settle} picks the occurrence nearest to SyncTeX's answer, so a
|
|
70
|
+
spurious occurrence matters only if it is closer than the true one. Model the
|
|
71
|
+
letters of the source as a stationary source with entropy $\mathcal{H}$ bits per letter.
|
|
72
|
+
The expected number of chance occurrences of a context of $\abs{q}$ letters in
|
|
73
|
+
a window of $\abs{S}$ letters is about
|
|
74
|
+
\begin{equation}
|
|
75
|
+
\E[\text{spurious}] \approx \abs{S} \cdot 2^{-\mathcal{H} \abs{q}} .
|
|
76
|
+
\label{eq:spurious}
|
|
77
|
+
\end{equation}
|
|
78
|
+
Estimates of the entropy of English range from about one to four bits per
|
|
79
|
+
letter depending on how much context is used~\cite{shannon1951}. Even with the
|
|
80
|
+
pessimistic $\mathcal{H} = 1$, a context of $\abs{q} = 21$ letters (the shortest one
|
|
81
|
+
\cref{alg:settle} tries) in a window of $\abs{S} = 2000$ letters gives
|
|
82
|
+
$\E[\text{spurious}] \approx 2000 \cdot 2^{-21} \approx 10^{-3}$. Spurious
|
|
83
|
+
matches come instead from genuine repetition, such as a phrase used twice in
|
|
84
|
+
nearby paragraphs, which is exactly the case the distance
|
|
85
|
+
\cref{eq:line-distance} resolves.
|
|
86
|
+
|
|
87
|
+
\subsection{Selecting whole words}
|
|
88
|
+
|
|
89
|
+
Let a drag select characters $[s, t)$ of the per-word text layer of
|
|
90
|
+
\cref{thm:per-word}. Papilot widens the selection to whole words, but only at
|
|
91
|
+
a boundary that cuts through a word:
|
|
92
|
+
\begin{equation}
|
|
93
|
+
s' = \begin{cases}
|
|
94
|
+
\max\{ i \le s : \text{$i = 0$ or $c_{i-1}$ is not a letter} \}, &
|
|
95
|
+
c_s \text{ is a letter},\\
|
|
96
|
+
s, & \text{otherwise},
|
|
97
|
+
\end{cases}
|
|
98
|
+
\label{eq:whole-words}
|
|
99
|
+
\end{equation}
|
|
100
|
+
and symmetrically for $t$. A drag that ends at the start of the next word, as
|
|
101
|
+
browsers often report, therefore keeps exactly the words it covers. The note
|
|
102
|
+
stores the widened quote, one rectangle per printed line, and the source lines
|
|
103
|
+
of \cref{alg:settle} together with an excerpt of their \LaTeX{}, so that the
|
|
104
|
+
agent receives formulas as source rather than as extracted text.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
\section{Agents and scheduling}
|
|
2
|
+
\label{sec:agents}
|
|
3
|
+
|
|
4
|
+
\subsection{One protocol for three agents}
|
|
5
|
+
|
|
6
|
+
Every agent is driven through the same small protocol: a thread is started or
|
|
7
|
+
resumed, a turn is started with the author's text, the agent streams items
|
|
8
|
+
(messages, commands, file changes, questions), and the turn completes, is
|
|
9
|
+
interrupted, or fails. Formally a task moves through the states of
|
|
10
|
+
\cref{fig:states}.
|
|
11
|
+
|
|
12
|
+
\begin{figure}[t]
|
|
13
|
+
\centering
|
|
14
|
+
\begin{tikzpicture}[
|
|
15
|
+
>={Stealth[length=2.2mm]}, node distance=2.1cm and 2.6cm,
|
|
16
|
+
every state/.style={draw=blue!50!black, thick, fill=blue!4, rounded corners=3pt,
|
|
17
|
+
rectangle, minimum width=1.9cm, minimum height=0.8cm, font=\small\sffamily},
|
|
18
|
+
every edge/.style={draw, ->, thick, gray!70!black},
|
|
19
|
+
lbl/.style={font=\scriptsize\sffamily, fill=white, inner sep=1pt}]
|
|
20
|
+
\node[state, initial, initial text={}] (idle) {idle};
|
|
21
|
+
\node[state, right=of idle] (queued) {queued};
|
|
22
|
+
\node[state, right=of queued] (running) {running};
|
|
23
|
+
\node[state, below=1.6cm of running] (building) {building};
|
|
24
|
+
\node[state, left=of building] (reviewing) {reviewing};
|
|
25
|
+
\node[state, above=1.5cm of running] (waiting) {waiting input};
|
|
26
|
+
\node[state, above=1.5cm of idle] (paused) {paused};
|
|
27
|
+
\path (idle) edge node[lbl, above] {instructions} (queued)
|
|
28
|
+
(queued) edge node[lbl, above] {slot free} (running)
|
|
29
|
+
(running) edge[bend right=28] node[lbl, right] {approval} (waiting)
|
|
30
|
+
(waiting) edge[bend right=28] node[lbl, left] {answer} (running)
|
|
31
|
+
(running) edge node[lbl, right] {turn done} (building)
|
|
32
|
+
(building) edge node[lbl, below] {published} (reviewing)
|
|
33
|
+
(reviewing) edge node[lbl, left] {feedback} (queued)
|
|
34
|
+
(running) edge[out=150, in=0, looseness=0.9] node[lbl, above, pos=0.45] {pause} (paused)
|
|
35
|
+
(paused) edge node[lbl, left] {resume} (idle);
|
|
36
|
+
\end{tikzpicture}
|
|
37
|
+
\caption{Task states. A turn that fails or whose outcome is uncertain leaves the
|
|
38
|
+
task in a recoverable state with a \emph{Check status} action (not shown).}
|
|
39
|
+
\label{fig:states}
|
|
40
|
+
\end{figure}
|
|
41
|
+
|
|
42
|
+
Codex speaks this protocol natively. Claude runs through the Claude Agent SDK,
|
|
43
|
+
and Antigravity runs the \texttt{agy} command line once per turn; both are
|
|
44
|
+
translated by small adapters into the same requests and notifications. Each
|
|
45
|
+
agent uses its own login on the host, and Papilot never reads, stores or
|
|
46
|
+
forwards a credential. An agent that cannot ask for permission, such as a
|
|
47
|
+
headless command line, is told in advance which tools it may not use.
|
|
48
|
+
|
|
49
|
+
\subsection{Effort and cost}
|
|
50
|
+
|
|
51
|
+
A reasoning effort $\epsilon$ from the ordered set
|
|
52
|
+
\begin{equation}
|
|
53
|
+
\mathcal{E} = \{\textsf{low} < \textsf{medium} < \textsf{high} <
|
|
54
|
+
\textsf{xhigh} < \textsf{max}\}
|
|
55
|
+
\end{equation}
|
|
56
|
+
is chosen per task and remembered per agent. If a turn at effort $\epsilon$
|
|
57
|
+
consumes on average $\kappa(\epsilon)$ tokens and produces an accepted change
|
|
58
|
+
with probability $q(\epsilon)$, the number of turns until acceptance is
|
|
59
|
+
geometric and the expected cost of an accepted change is
|
|
60
|
+
\begin{equation}
|
|
61
|
+
\operatorname{cost}(\epsilon) = \frac{\kappa(\epsilon)}{q(\epsilon)}
|
|
62
|
+
\cdot \operatorname{price}, \qquad
|
|
63
|
+
\epsilon^{\star} = \argmin_{\epsilon \in \mathcal{E}}
|
|
64
|
+
\operatorname{cost}(\epsilon).
|
|
65
|
+
\label{eq:effort-cost}
|
|
66
|
+
\end{equation}
|
|
67
|
+
A lower effort is cheaper only if
|
|
68
|
+
$\kappa(\epsilon) / \kappa(\epsilon') < q(\epsilon) / q(\epsilon')$: a failed
|
|
69
|
+
turn must be paid for again, and so must the author's review of it.
|
|
70
|
+
|
|
71
|
+
\subsection{Queueing}
|
|
72
|
+
|
|
73
|
+
Tasks arrive at rate $\nu$ and occupy a slot for a mean time $1/\omega$. With
|
|
74
|
+
$c$ slots, exponential times and offered load $A = \nu/\omega < c$, Papilot is
|
|
75
|
+
an $M/M/c$ queue~\cite{kleinrock1975}. The probability that a new task waits is
|
|
76
|
+
given by the Erlang~C formula~\cite{erlang1917}:
|
|
77
|
+
\begin{equation}
|
|
78
|
+
C(c, A) =
|
|
79
|
+
\frac{\dfrac{A^{c}}{c!}\,\dfrac{c}{c - A}}
|
|
80
|
+
{\displaystyle\sum_{k=0}^{c-1} \frac{A^{k}}{k!}
|
|
81
|
+
+ \frac{A^{c}}{c!}\,\frac{c}{c - A}},
|
|
82
|
+
\qquad
|
|
83
|
+
\E[\text{wait}] = \frac{C(c, A)}{c\,\omega - \nu} .
|
|
84
|
+
\label{eq:erlang}
|
|
85
|
+
\end{equation}
|
|
86
|
+
For $c = 2$ this simplifies to $C(2, A) = A^2 / (2 + A)$. An author who starts
|
|
87
|
+
a task every ten minutes, each taking six minutes, has $A = 0.6$ and waits
|
|
88
|
+
with probability about $0.14$, for $0.14 / (2/6 - 1/10) \approx 0.6$ minutes on
|
|
89
|
+
average. By Little's law~\cite{little1961}, the mean number of tasks in the
|
|
90
|
+
system is
|
|
91
|
+
\begin{equation}
|
|
92
|
+
\bar{N} = \nu \Bigl( \E[\text{wait}] + \frac{1}{\omega} \Bigr) .
|
|
93
|
+
\label{eq:little}
|
|
94
|
+
\end{equation}
|
|
95
|
+
|
|
96
|
+
\subsection{Approvals}
|
|
97
|
+
|
|
98
|
+
A turn that asks for approval stops until the author answers. If approvals
|
|
99
|
+
arrive at rate $\zeta$ during a turn of working time $T_w$ and the author
|
|
100
|
+
answers after a mean delay $\Theta$, the expected wall-clock time of the turn is
|
|
101
|
+
\begin{equation}
|
|
102
|
+
\E[T] = T_w \bigl(1 + \zeta \Theta\bigr),
|
|
103
|
+
\label{eq:approvals}
|
|
104
|
+
\end{equation}
|
|
105
|
+
which is why Papilot lets agents edit and run sandboxed commands in the task's
|
|
106
|
+
worktree without asking, and asks only for what leaves it.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
\section{Usage windows}
|
|
2
|
+
\label{sec:usage}
|
|
3
|
+
|
|
4
|
+
\subsection{Utilisation}
|
|
5
|
+
|
|
6
|
+
Agent subscriptions meter usage over rolling windows, typically a five-hour
|
|
7
|
+
window and a weekly one. If $r(\tau)$ is the rate at which a plan's allowance
|
|
8
|
+
is consumed, the utilisation of a window of length $w$ at time $t$ is
|
|
9
|
+
\begin{equation}
|
|
10
|
+
u_w(t) = \frac{1}{B_w} \int_{t-w}^{t} r(\tau)\, d\tau \in [0, 1],
|
|
11
|
+
\label{eq:utilisation}
|
|
12
|
+
\end{equation}
|
|
13
|
+
where $B_w$ is the window's budget. Papilot shows $u_w$ for each window the
|
|
14
|
+
agent reports, coloured by thresholds
|
|
15
|
+
\begin{equation}
|
|
16
|
+
\operatorname{colour}(u) =
|
|
17
|
+
\begin{cases}
|
|
18
|
+
\text{blue}, & u < 0.75,\\
|
|
19
|
+
\text{amber}, & 0.75 \le u < 0.9,\\
|
|
20
|
+
\text{red}, & u \ge 0.9 \ \text{or the limit is reached},
|
|
21
|
+
\end{cases}
|
|
22
|
+
\end{equation}
|
|
23
|
+
together with the reset time: as a countdown when it is less than a day away,
|
|
24
|
+
and as a day and time otherwise.
|
|
25
|
+
|
|
26
|
+
\subsection{Will the limit be reached?}
|
|
27
|
+
|
|
28
|
+
At a constant rate $\bar r$, the time left before the limit is
|
|
29
|
+
\begin{equation}
|
|
30
|
+
t_{\text{left}} = \frac{\bigl(1 - u_w(t)\bigr) B_w}{\bar r} ;
|
|
31
|
+
\end{equation}
|
|
32
|
+
with the weekly window at $u = 0.83$ and an even pace over the week,
|
|
33
|
+
$\bar r = B_w / 7$\,days and about $1.2$ days of work remain.
|
|
34
|
+
|
|
35
|
+
Usage is lumpy, however: it arrives in turns. Model turns as a Poisson process
|
|
36
|
+
of rate $\lambda$ with independent costs $J$ of mean $\mu_J$ and variance
|
|
37
|
+
$s_J^2$. The usage $S(t)$ over the next $t$ is compound Poisson, with
|
|
38
|
+
\begin{equation}
|
|
39
|
+
\E[S(t)] = \lambda t \mu_J, \qquad
|
|
40
|
+
\Var\bigl(S(t)\bigr) = \lambda t \bigl(\mu_J^2 + s_J^2\bigr)
|
|
41
|
+
\label{eq:compound}
|
|
42
|
+
\end{equation}
|
|
43
|
+
by Wald's identity and the law of total variance~\cite{feller1968}. With $t_r$ the time until the window
|
|
44
|
+
resets, the normal approximation gives the probability of reaching the limit
|
|
45
|
+
before the reset:
|
|
46
|
+
\begin{equation}
|
|
47
|
+
\Prob\bigl(S(t_r) > (1 - u) B_w\bigr) \approx
|
|
48
|
+
1 - \Phi\!\left(
|
|
49
|
+
\frac{(1 - u) B_w - \lambda t_r \mu_J}
|
|
50
|
+
{\sqrt{\lambda t_r (\mu_J^2 + s_J^2)}}
|
|
51
|
+
\right),
|
|
52
|
+
\label{eq:limit-probability}
|
|
53
|
+
\end{equation}
|
|
54
|
+
where $\Phi$ is the standard normal distribution function. Large, rare turns
|
|
55
|
+
(high $s_J$) make the limit more likely than the mean alone suggests, which is
|
|
56
|
+
one more reason to prefer a lower effort for routine edits.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
\section{Closing the loop with Git}
|
|
2
|
+
\label{sec:git}
|
|
3
|
+
|
|
4
|
+
Accepted changes land in the working tree without a commit, so that the author
|
|
5
|
+
decides what history to write. The Git panel shows the branch and how it
|
|
6
|
+
compares with its upstream, the last commit, and the uncommitted changes with
|
|
7
|
+
their diffs; from it the author commits chosen files, fetches, pulls and
|
|
8
|
+
pushes.
|
|
9
|
+
|
|
10
|
+
\subsection{Committing chosen files}
|
|
11
|
+
|
|
12
|
+
Write a tree as a map from paths to contents and, for a set of paths $P$, let
|
|
13
|
+
$X|_P$ be the restriction of $X$ to $P$ and $\overline{P}$ the complement. Let
|
|
14
|
+
$\mathrm{HEAD}$ have tree $H$, let $I$ be the index and $W$ the working tree,
|
|
15
|
+
and let $P$ be the set of paths the author ticks.
|
|
16
|
+
|
|
17
|
+
\begin{proposition}[Partial commits]
|
|
18
|
+
\label{prop:partial}
|
|
19
|
+
Committing $P$ produces the tree and leaves the index as
|
|
20
|
+
\begin{equation}
|
|
21
|
+
T_{c} = H|_{\overline{P}} \cup W|_{P},
|
|
22
|
+
\qquad
|
|
23
|
+
I' = I|_{\overline{P}} \cup W|_{P},
|
|
24
|
+
\label{eq:partial}
|
|
25
|
+
\end{equation}
|
|
26
|
+
that is, exactly the chosen files as they are on disk are committed, while
|
|
27
|
+
anything staged for other paths stays staged and out of the commit. A rename
|
|
28
|
+
from $\pi$ to $\pi'$ contributes both $\pi$ and $\pi'$ to $P$.
|
|
29
|
+
\end{proposition}
|
|
30
|
+
|
|
31
|
+
The proof, which follows the semantics of \texttt{git add} followed by
|
|
32
|
+
\texttt{git commit --only}, is in \cref{app:proofs}. Paths are passed to Git
|
|
33
|
+
literally, so that a file name cannot act as a pattern, and new \LaTeX{} build
|
|
34
|
+
output (\texttt{.aux}, \texttt{.log} and similar files) starts unticked.
|
|
35
|
+
|
|
36
|
+
\subsection{Synchronising with the remote}
|
|
37
|
+
|
|
38
|
+
Let $\anc(X)$ be the set of commits reachable from $X$, including $X$. With
|
|
39
|
+
local head $L$ and upstream $U$, the branch is ahead and behind by
|
|
40
|
+
\begin{equation}
|
|
41
|
+
\operatorname{ahead} = \abs{\anc(L) \setminus \anc(U)}, \qquad
|
|
42
|
+
\operatorname{behind} = \abs{\anc(U) \setminus \anc(L)},
|
|
43
|
+
\label{eq:ahead-behind}
|
|
44
|
+
\end{equation}
|
|
45
|
+
the two numbers shown beside the branch name. Pull is a fast-forward only. It
|
|
46
|
+
succeeds exactly when
|
|
47
|
+
\begin{equation}
|
|
48
|
+
\anc(L) \subseteq \anc(U)
|
|
49
|
+
\iff \operatorname{merge-base}(L, U) = L
|
|
50
|
+
\iff \operatorname{ahead} = 0,
|
|
51
|
+
\label{eq:fast-forward}
|
|
52
|
+
\end{equation}
|
|
53
|
+
in which case $L \leftarrow U$; otherwise Papilot reports the divergence and
|
|
54
|
+
never merges or rebases on the author's behalf. Push is the mirror image and
|
|
55
|
+
succeeds when $\operatorname{behind} = 0$. A branch without an upstream is
|
|
56
|
+
published with its first push.
|
|
57
|
+
|
|
58
|
+
\begin{remark}
|
|
59
|
+
Fetch, pull and push use the host's own Git credentials with prompts disabled
|
|
60
|
+
and a time limit, so that a missing credential produces Git's own error message
|
|
61
|
+
instead of a request that hangs.
|
|
62
|
+
\end{remark}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
\section{Security}
|
|
2
|
+
\label{sec:security}
|
|
3
|
+
|
|
4
|
+
Papilot runs on the author's own computer and can be reached from other
|
|
5
|
+
devices over a private network. Its threat model is a web page in another tab
|
|
6
|
+
that tries to act on the author's behalf, and a device on the network that is
|
|
7
|
+
not the author's.
|
|
8
|
+
|
|
9
|
+
\subsection{Requests}
|
|
10
|
+
|
|
11
|
+
Every state-changing request must carry a token $\chi$ of $256$ random bits,
|
|
12
|
+
issued with the page and compared in constant time, which defends against
|
|
13
|
+
cross-site request forgery~\cite{barth2008}. A remote request must also come
|
|
14
|
+
from the configured origin and, over the private network, from the configured
|
|
15
|
+
owner. Writing $\rho$ for a request,
|
|
16
|
+
\begin{equation}
|
|
17
|
+
\operatorname{allow}(\rho) =
|
|
18
|
+
\bigl(\operatorname{safe}(\rho) \lor \operatorname{token}(\rho) = \chi\bigr)
|
|
19
|
+
\land \operatorname{origin}(\rho) \in \mathcal{O}
|
|
20
|
+
\land \operatorname{owner}(\rho) = o ,
|
|
21
|
+
\label{eq:allow}
|
|
22
|
+
\end{equation}
|
|
23
|
+
where $\operatorname{safe}(\rho)$ holds for requests that only read. A guess
|
|
24
|
+
succeeds with probability $2^{-256}$, and a constant-time comparison leaks no
|
|
25
|
+
prefix of $\chi$ through timing.
|
|
26
|
+
|
|
27
|
+
\subsection{Agents}
|
|
28
|
+
|
|
29
|
+
Agents run with the author's own logins but within limits Papilot sets:
|
|
30
|
+
\begin{itemize}
|
|
31
|
+
\item each task's agent works in its own worktree $w_i$ and is told never to
|
|
32
|
+
edit the manuscript checkout $w_0$, other worktrees, or Papilot itself;
|
|
33
|
+
\item commands run in the agent's sandbox, and anything that leaves it is
|
|
34
|
+
either brought to the author for approval or, for an agent that cannot
|
|
35
|
+
ask, declined in advance;
|
|
36
|
+
\item only the author accepts changes into $w_0$, through the review of
|
|
37
|
+
\cref{sec:model}.
|
|
38
|
+
\end{itemize}
|
|
39
|
+
The last point is the one that matters most: even an agent that ignored its
|
|
40
|
+
instructions could not change the manuscript without a decision the author
|
|
41
|
+
made while looking at the typeset result.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
\section{A model-based evaluation}
|
|
2
|
+
\label{sec:evaluation}
|
|
3
|
+
|
|
4
|
+
This section evaluates the models of the previous sections numerically. Every
|
|
5
|
+
number below is computed from a formula in this paper; none is measured. The
|
|
6
|
+
purpose is to show what the formulas imply for realistic parameters, not to
|
|
7
|
+
report an empirical study.
|
|
8
|
+
|
|
9
|
+
\subsection{Rounds per task}
|
|
10
|
+
|
|
11
|
+
\Cref{tab:rounds} evaluates \cref{prop:rounds} and \cref{cor:rounds} for a
|
|
12
|
+
range of acceptance probabilities and numbers of changes. Two patterns stand
|
|
13
|
+
out. The expected number of rounds grows only logarithmically with the number
|
|
14
|
+
of changes, as the harmonic-number approximation predicts. And the
|
|
15
|
+
95\,\% bound $r_{0.05}$ is at most two and a half times the mean in every case
|
|
16
|
+
shown, so that at $p = 0.7$ a task with five changes that is still open after
|
|
17
|
+
four rounds is among the slowest five per cent, and worth rewriting rather than
|
|
18
|
+
reviewing again.
|
|
19
|
+
|
|
20
|
+
\begin{table}[t]
|
|
21
|
+
\centering
|
|
22
|
+
\caption{Expected rounds $\E[R_{\max}]$ from \cref{eq:rounds}, and in
|
|
23
|
+
parentheses the rounds $r_{0.05}$ within which a task ends with probability
|
|
24
|
+
at least $0.95$ from \cref{eq:rounds-confidence}.}
|
|
25
|
+
\label{tab:rounds}
|
|
26
|
+
\begin{tabular}{@{}lccccc@{}}
|
|
27
|
+
\toprule
|
|
28
|
+
& \multicolumn{5}{c}{changes per task $m$} \\
|
|
29
|
+
\cmidrule(l){2-6}
|
|
30
|
+
acceptance $p$ & 1 & 2 & 5 & 10 & 20 \\
|
|
31
|
+
\midrule
|
|
32
|
+
$0.5$ & 2.00 (5) & 2.67 (6) & 3.79 (7) & 4.73 (8) & 5.69 (9) \\
|
|
33
|
+
$0.7$ & 1.43 (3) & 1.76 (4) & 2.39 (4) & 2.93 (5) & 3.49 (5) \\
|
|
34
|
+
$0.9$ & 1.11 (2) & 1.21 (2) & 1.46 (2) & 1.76 (3) & 2.08 (3) \\
|
|
35
|
+
\bottomrule
|
|
36
|
+
\end{tabular}
|
|
37
|
+
\end{table}
|
|
38
|
+
|
|
39
|
+
\subsection{Conflicts}
|
|
40
|
+
|
|
41
|
+
\Cref{tab:conflicts} evaluates \cref{eq:conflict} for two concurrent tasks with
|
|
42
|
+
edits of $\ell = 200$ characters. A $10$-page paper has about
|
|
43
|
+
$3 \times 10^{4}$ characters of source, a $40$-page paper about
|
|
44
|
+
$1.2 \times 10^{5}$ and a thesis about $4.8 \times 10^{5}$. By
|
|
45
|
+
\cref{cor:clustered}, confining both tasks to a quarter of a document moves a
|
|
46
|
+
row up one line.
|
|
47
|
+
|
|
48
|
+
\begin{table}[t]
|
|
49
|
+
\centering
|
|
50
|
+
\caption{Probability that two concurrent tasks with $k$ edits each conflict,
|
|
51
|
+
from \cref{eq:conflict} with $\ell = 200$.}
|
|
52
|
+
\label{tab:conflicts}
|
|
53
|
+
\begin{tabular}{@{}lccc@{}}
|
|
54
|
+
\toprule
|
|
55
|
+
& \multicolumn{3}{c}{edits per task $k$} \\
|
|
56
|
+
\cmidrule(l){2-4}
|
|
57
|
+
source length $n$ & 5 & 10 & 20 \\
|
|
58
|
+
\midrule
|
|
59
|
+
$3.0 \times 10^{4}$ (short paper) & 0.284 & 0.737 & 0.995 \\
|
|
60
|
+
$1.2 \times 10^{5}$ (long paper) & 0.080 & 0.284 & 0.737 \\
|
|
61
|
+
$4.8 \times 10^{5}$ (thesis) & 0.021 & 0.080 & 0.284 \\
|
|
62
|
+
\bottomrule
|
|
63
|
+
\end{tabular}
|
|
64
|
+
\end{table}
|
|
65
|
+
|
|
66
|
+
\subsection{Waiting for a slot}
|
|
67
|
+
|
|
68
|
+
\Cref{fig:erlang} plots the waiting probability \cref{eq:erlang} against the
|
|
69
|
+
offered load for one, two and three slots. At the load $A = 0.6$ of the example
|
|
70
|
+
in \cref{sec:agents}, a second slot lowers the probability of waiting from
|
|
71
|
+
$0.6$ to $0.14$, and a third would lower it to $0.025$. Two slots are a good
|
|
72
|
+
compromise between waiting and the conflicts of \cref{tab:conflicts}, which
|
|
73
|
+
grow with the number of tasks that run at once.
|
|
74
|
+
|
|
75
|
+
\begin{figure}[t]
|
|
76
|
+
\centering
|
|
77
|
+
\begin{tikzpicture}
|
|
78
|
+
\begin{axis}[
|
|
79
|
+
width=0.92\linewidth, height=5.4cm,
|
|
80
|
+
xlabel={offered load $A = \nu/\omega$}, ylabel={$C(c, A)$},
|
|
81
|
+
xmin=0, xmax=1.5, ymin=0, ymax=1,
|
|
82
|
+
legend style={at={(0.02,0.97)}, anchor=north west, draw=none, fill=white, font=\small},
|
|
83
|
+
grid=major, grid style={gray!20}, tick label style={font=\small}, label style={font=\small}]
|
|
84
|
+
\addplot[gray!70!black, thick, dashed, domain=0:0.999, samples=60] {x};
|
|
85
|
+
\addlegendentry{$c = 1$}
|
|
86
|
+
\addplot[blue!60!black, thick, domain=0:1.5, samples=60] {x^2/(2+x)};
|
|
87
|
+
\addlegendentry{$c = 2$}
|
|
88
|
+
\addplot[orange!80!black, thick, domain=0:1.5, samples=60]
|
|
89
|
+
{(x^3/6*3/(3-x))/(1+x+x^2/2+x^3/6*3/(3-x))};
|
|
90
|
+
\addlegendentry{$c = 3$}
|
|
91
|
+
\addplot[only marks, mark=*, mark size=1.6pt, blue!60!black] coordinates {(0.6,0.138)};
|
|
92
|
+
\end{axis}
|
|
93
|
+
\end{tikzpicture}
|
|
94
|
+
\caption{Probability that a new task waits for a slot, from the Erlang~C
|
|
95
|
+
formula \cref{eq:erlang}. The dot marks the example of \cref{sec:agents}.}
|
|
96
|
+
\label{fig:erlang}
|
|
97
|
+
\end{figure}
|
|
98
|
+
|
|
99
|
+
\subsection{Selection error}
|
|
100
|
+
|
|
101
|
+
For the text layer, \cref{fig:drift} already compares the two layouts. In
|
|
102
|
+
numbers: with $v = (0.4\,\mathrm{pt})^2$ per glyph, a line of $80$ glyphs has a
|
|
103
|
+
mid-line standard deviation of $0.4\sqrt{80/4} \approx 1.8$\,pt from
|
|
104
|
+
\cref{eq:bridge} alone, plus the bias of \cref{eq:bias}, which reaches several
|
|
105
|
+
points; per-word runs of seven glyphs bound it by $0.4\sqrt{7/4} \approx
|
|
106
|
+
0.53$\,pt, well under the width of one letter.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
\section{Related work}
|
|
2
|
+
\label{sec:related}
|
|
3
|
+
|
|
4
|
+
\paragraph{Differences between documents.}
|
|
5
|
+
Line-based differences go back to Hunt and McIlroy~\cite{hunt1976} and Myers's
|
|
6
|
+
$O(ND)$ algorithm~\cite{myers1986}, which Git uses by default.
|
|
7
|
+
\texttt{latexdiff}~\cite{latexdiff} marks up the differences between two
|
|
8
|
+
\LaTeX{} sources so that they can be typeset together. Papilot instead
|
|
9
|
+
typesets each version separately and shows the corresponding strips of the
|
|
10
|
+
two PDFs one above the other, which keeps the typeset result of each version
|
|
11
|
+
exact at the cost of a second build.
|
|
12
|
+
|
|
13
|
+
\paragraph{Concurrent editing.}
|
|
14
|
+
Operational transformation~\cite{ellis1989} and conflict-free replicated data
|
|
15
|
+
types~\cite{shapiro2011} let several writers edit a shared document and converge
|
|
16
|
+
without coordination. Papilot's setting is different: proposals are not applied
|
|
17
|
+
until an author accepts them, so convergence is a property of accepted edits
|
|
18
|
+
only, and \cref{lem:tp1} needs to hold only for compatible pairs. Incompatible
|
|
19
|
+
pairs are surfaced to the author rather than transformed.
|
|
20
|
+
|
|
21
|
+
\paragraph{Line breaking and text layers.}
|
|
22
|
+
The Knuth--Plass algorithm~\cite{knuth1981} is the reason \LaTeX{} spaces vary
|
|
23
|
+
from line to line, and with it the drift analysed in \cref{sec:geometry}. PDF
|
|
24
|
+
viewers such as PDF.js~\cite{pdfjs} place an invisible text layer over the
|
|
25
|
+
rendered page; Papilot uses PDF.js for rendering, search and links, and
|
|
26
|
+
replaces only the placement of its text with the per-word runs of
|
|
27
|
+
\cref{thm:per-word}.
|
|
28
|
+
|
|
29
|
+
\paragraph{Source synchronisation.}
|
|
30
|
+
SyncTeX~\cite{laurens2008} maps between typeset positions and source lines and
|
|
31
|
+
underlies forward and inverse search in most \TeX{} editors. Papilot uses its
|
|
32
|
+
reverse queries and corrects their line-boundary error with
|
|
33
|
+
\cref{alg:settle}.
|
|
34
|
+
|
|
35
|
+
\paragraph{Queueing.}
|
|
36
|
+
The waiting-time results of \cref{sec:agents} are classical; we use Erlang's
|
|
37
|
+
formula~\cite{erlang1917}, Little's law~\cite{little1961} and the treatment of
|
|
38
|
+
$M/M/c$ queues in Kleinrock~\cite{kleinrock1975}.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
\section{Discussion and conclusion}
|
|
2
|
+
\label{sec:conclusion}
|
|
3
|
+
|
|
4
|
+
\subsection{Limitations}
|
|
5
|
+
|
|
6
|
+
Three limitations are worth stating. First, \cref{prop:conflict} assumes
|
|
7
|
+
uniformly placed edits; real revisions cluster around the parts of a paper an
|
|
8
|
+
author is working on, which by \cref{cor:clustered} raises the conflict rate
|
|
9
|
+
and argues for fewer, longer tasks over many short ones. Second,
|
|
10
|
+
\cref{alg:settle} cannot settle formulas, since their printed form shares no
|
|
11
|
+
letters with their source; for those, SyncTeX's lines are kept and the excerpt
|
|
12
|
+
shows the whole display. Third, an agent that cannot ask for permission, such
|
|
13
|
+
as a headless command line, must be restricted in advance; Papilot runs such
|
|
14
|
+
agents so that they edit only the task's worktree, which also means they cannot
|
|
15
|
+
compile or test their own changes.
|
|
16
|
+
|
|
17
|
+
\subsection{Conclusion}
|
|
18
|
+
|
|
19
|
+
Papilot treats agent editing as a sequence of reviewable rounds. Isolation
|
|
20
|
+
makes proposals safe, typeset review makes them legible, and explicit
|
|
21
|
+
acceptance keeps the author in charge of the paper. The models in this paper
|
|
22
|
+
make those properties checkable: compatible edits commute and rebase
|
|
23
|
+
(\cref{prop:commute,lem:tp1}), tasks need few rounds
|
|
24
|
+
(\cref{prop:rounds,cor:rounds}), undo never recompiles
|
|
25
|
+
(\cref{prop:undo-build}), per-word text bounds the selection error
|
|
26
|
+
(\cref{thm:per-word}), and partial commits keep unrelated staged work intact
|
|
27
|
+
(\cref{prop:partial}). They also give the reader something worth reviewing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "papilot",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Cursor-like workspace for your papers: annotate and review PDFs directly without ever touching LaTeX.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"latex",
|
|
7
|
+
"pdf",
|
|
8
|
+
"diff",
|
|
9
|
+
"review",
|
|
10
|
+
"ai",
|
|
11
|
+
"agent",
|
|
12
|
+
"codex",
|
|
13
|
+
"claude",
|
|
14
|
+
"antigravity",
|
|
15
|
+
"writing",
|
|
16
|
+
"academic",
|
|
17
|
+
"manuscript"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/cu1ch3n/papilot#readme",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/cu1ch3n/papilot.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/cu1ch3n/papilot/issues"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"author": "Cui Chen",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"bin": {
|
|
31
|
+
"papilot": "bin/papilot.mjs"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"src/server",
|
|
36
|
+
"src/shared.ts",
|
|
37
|
+
"src/notes.ts",
|
|
38
|
+
"dist",
|
|
39
|
+
"scripts/demo.ts",
|
|
40
|
+
"scripts/service.ts",
|
|
41
|
+
"examples/papilot-paper",
|
|
42
|
+
"vendor/latex-review/src",
|
|
43
|
+
"vendor/latex-review/dist",
|
|
44
|
+
"vendor/latex-review/package.json",
|
|
45
|
+
"vendor/latex-review/package-lock.json",
|
|
46
|
+
"vendor/latex-review/LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22.22.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"dev": "tsx watch src/server/main.ts",
|
|
53
|
+
"start": "tsx src/server/main.ts",
|
|
54
|
+
"build": "tsc --noEmit && vite build",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "tsx --test tests/*.test.ts",
|
|
57
|
+
"setup:review": "npm --prefix vendor/latex-review ci && npm --prefix vendor/latex-review run build",
|
|
58
|
+
"demo": "tsx scripts/demo.ts",
|
|
59
|
+
"service:install": "tsx scripts/service.ts install",
|
|
60
|
+
"service:stop": "tsx scripts/service.ts stop",
|
|
61
|
+
"test:ui": "npm run build && node scripts/check-ui.mjs",
|
|
62
|
+
"prepack": "npm run build && npm run setup:review"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@fastify/compress": "^9.2.2",
|
|
66
|
+
"@fastify/static": "^10.1.7",
|
|
67
|
+
"commander": "^15.0.0",
|
|
68
|
+
"diff": "^9.0.0",
|
|
69
|
+
"fastify": "^5.12.7",
|
|
70
|
+
"mupdf": "^1.28.1",
|
|
71
|
+
"proper-lockfile": "^4.1.2",
|
|
72
|
+
"tsx": "^4.23.15"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.311",
|
|
76
|
+
"@openai/codex": "0.164.0",
|
|
77
|
+
"@types/node": "^22.20.5",
|
|
78
|
+
"@types/react": "^19.3.0",
|
|
79
|
+
"@types/react-dom": "^19.3.0",
|
|
80
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
81
|
+
"pdfjs-dist": "^6.4.299",
|
|
82
|
+
"react": "^19.3.1",
|
|
83
|
+
"react-dom": "^19.3.1",
|
|
84
|
+
"typescript": "^7.0.2",
|
|
85
|
+
"vite": "^8.3.5"
|
|
86
|
+
}
|
|
87
|
+
}
|
package/scripts/demo.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { DATA, ROOT } from '../src/server/paths.ts';
|
|
5
|
+
import { Store } from '../src/server/store.ts';
|
|
6
|
+
import { Workspace } from '../src/server/workspace.ts';
|
|
7
|
+
|
|
8
|
+
// The example is Papilot's own README written as a paper, with plenty of
|
|
9
|
+
// mathematics to review; it is copied into its own Git repository.
|
|
10
|
+
const folder = path.join(DATA, 'example-paper');
|
|
11
|
+
if (!fs.existsSync(folder)) {
|
|
12
|
+
fs.cpSync(path.join(ROOT, 'examples/papilot-paper'), folder, { recursive: true });
|
|
13
|
+
execFileSync('git', ['init', '-q', folder]);
|
|
14
|
+
execFileSync('git', ['-C', folder, 'add', '.']);
|
|
15
|
+
execFileSync('git', ['-C', folder, '-c', 'user.name=Papilot Example', '-c', 'user.email=papilot@localhost', 'commit', '-qm', 'Add example paper']);
|
|
16
|
+
}
|
|
17
|
+
const store = new Store(path.join(DATA, 'papilot.sqlite'));
|
|
18
|
+
const workspace = new Workspace(store);
|
|
19
|
+
const project = await workspace.addProject(folder, 'Papilot paper');
|
|
20
|
+
console.log(`Example paper ready: ${project.name} (${folder})`);
|
|
21
|
+
store.close();
|