@aloth/olcli 0.4.1 → 0.6.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 +228 -5
- package/dist/cli.js +120 -20
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +18 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +126 -21
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +18 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +344 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +16 -3
package/README.md
CHANGED
|
@@ -27,8 +27,10 @@ Work with Overleaf projects directly from your command line. Edit locally with y
|
|
|
27
27
|
- 📄 **Compile** PDFs using Overleaf's remote compiler
|
|
28
28
|
- 📦 **Download** individual files or full project archives
|
|
29
29
|
- 📤 **Upload** files to projects
|
|
30
|
-
- 💬 **Review comments** — list comments with source locations, add, resolve, reopen, and
|
|
30
|
+
- 💬 **Review comments** — list comments with source locations, add, resolve, reopen, delete threads, and **reply** to existing threads
|
|
31
31
|
- 🗂️ **Preserve folder structure** when pushing nested files
|
|
32
|
+
- ⏱️ **Configurable timeout** — global `--timeout <ms>` flag, `OVERLEAF_TIMEOUT` env var, or `config set-timeout` for slow connections
|
|
33
|
+
- 🔑 **Password login** — `olcli auth --email --password` for self-hosted instances (no browser required)
|
|
32
34
|
- ⚙️ **Support self-hosted Overleaf/ShareLaTeX instances** via configurable base URL and session cookie name
|
|
33
35
|
- 📊 **Output** compile artifacts (`.bbl`, `.log`, `.aux` for arXiv submissions)
|
|
34
36
|
|
|
@@ -94,18 +96,24 @@ makepkg -si
|
|
|
94
96
|
|
|
95
97
|
### 1. Authenticate with Overleaf
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
**Option A: Session cookie** (overleaf.com and self-hosted)
|
|
98
100
|
|
|
99
101
|
1. Log into [overleaf.com](https://www.overleaf.com)
|
|
100
102
|
2. Open Developer Tools (F12 or Cmd+Option+I) → Application/Storage → Cookies
|
|
101
103
|
3. Copy the value of `overleaf_session2`
|
|
102
104
|
|
|
103
|
-
Store it with olcli:
|
|
104
|
-
|
|
105
105
|
```bash
|
|
106
106
|
olcli auth --cookie "your_session_cookie_value"
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
**Option B: Email/password** (self-hosted instances without reCAPTCHA)
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
olcli auth --email "you@example.com" --password "your_password"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Credentials are stored securely and the session auto-refreshes on expiry.
|
|
116
|
+
|
|
109
117
|
**Tip:** The cookie stays valid for weeks. Just refresh it when authentication fails.
|
|
110
118
|
|
|
111
119
|
### 2. List Your Projects
|
|
@@ -158,7 +166,7 @@ All commands auto-detect the project when run from a synced directory (contains
|
|
|
158
166
|
|
|
159
167
|
| Command | Description |
|
|
160
168
|
|---------|-------------|
|
|
161
|
-
| `olcli auth` | Set session cookie |
|
|
169
|
+
| `olcli auth` | Set session cookie or login with email/password |
|
|
162
170
|
| `olcli whoami` | Check authentication status |
|
|
163
171
|
| `olcli logout` | Clear stored credentials |
|
|
164
172
|
| `olcli list` | List all projects |
|
|
@@ -171,6 +179,7 @@ All commands auto-detect the project when run from a synced directory (contains
|
|
|
171
179
|
| `olcli comments list [project]` | List comments with source text and file locations (`--status`, `--context`) |
|
|
172
180
|
| `olcli comments add <file> <message> [project]` | Add a comment to selected text |
|
|
173
181
|
| `olcli comments resolve <threadId> [project]` | Resolve a comment thread |
|
|
182
|
+
| `olcli comments reply <threadId> <body> [project]` | Reply to a comment thread |
|
|
174
183
|
| `olcli comments reopen <threadId> [project]` | Reopen a resolved comment thread |
|
|
175
184
|
| `olcli comments delete <threadId> [project]` | Permanently delete a comment thread |
|
|
176
185
|
| `olcli delete <file> [project]` | Delete a remote file or folder by path (alias: `rm`) |
|
|
@@ -182,6 +191,8 @@ All commands auto-detect the project when run from a synced directory (contains
|
|
|
182
191
|
| `olcli output [type]` | Download compile output files |
|
|
183
192
|
| `olcli config set-url <url>` | Set a self-hosted Overleaf base URL |
|
|
184
193
|
| `olcli config set-cookie-name <name>` | Set the session cookie name |
|
|
194
|
+
| `olcli config set-timeout <ms>` | Set default HTTP request timeout |
|
|
195
|
+
| `olcli config get-timeout` | Show current timeout setting |
|
|
185
196
|
| `olcli check` | Show config paths and credential sources |
|
|
186
197
|
|
|
187
198
|
### Review comments
|
|
@@ -191,6 +202,7 @@ olcli comments list "My Paper" --status open --context 2
|
|
|
191
202
|
olcli comments list "My Paper" --status resolved --json
|
|
192
203
|
olcli comments add main.tex "Please clarify this definition" "My Paper" --text "A Skill is"
|
|
193
204
|
olcli comments add main.tex "Check this sentence" "My Paper" --line 42 --column 1 --length 20 --json
|
|
205
|
+
olcli comments reply 6a1a5fedbf90b811e1000001 "Good point, I'll fix this" "My Paper"
|
|
194
206
|
olcli comments resolve 6a1a5fedbf90b811e1000001 "My Paper" --json
|
|
195
207
|
olcli comments reopen 6a1a5fedbf90b811e1000001 "My Paper"
|
|
196
208
|
olcli comments delete 6a1a5fedbf90b811e1000001 "My Paper" --json
|
|
@@ -205,6 +217,7 @@ These flags work with **every** command and may be placed before or after the co
|
|
|
205
217
|
| `--verbose` | Print every HTTP request, status, content-type, and (on errors) a response-body snippet to stderr. Useful for debugging failed compiles, 404s on `pdf`/`output`, auth issues, or unexpected upload behavior. |
|
|
206
218
|
| `--base-url <url>` | Override the Overleaf instance base URL (also `OVERLEAF_BASE_URL` env var or `olcli config set-url`). |
|
|
207
219
|
| `--cookie-name <name>` | Override the session cookie name (default `overleaf_session2`; older instances use `overleaf.sid`). |
|
|
220
|
+
| `--timeout <ms>` | Override the HTTP request timeout in milliseconds (also `OVERLEAF_TIMEOUT` env var or `olcli config set-timeout`). Default: 10000. |
|
|
208
221
|
|
|
209
222
|
Examples:
|
|
210
223
|
|
|
@@ -388,6 +401,24 @@ olcli config set-url https://latex.example.org
|
|
|
388
401
|
olcli config set-cookie-name overleaf.sid
|
|
389
402
|
```
|
|
390
403
|
|
|
404
|
+
### Timeout configuration
|
|
405
|
+
|
|
406
|
+
For slow connections or large projects, increase the HTTP request timeout:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# One-off: pass as a flag
|
|
410
|
+
olcli --timeout 60000 pull "Large Thesis"
|
|
411
|
+
|
|
412
|
+
# Persist as default
|
|
413
|
+
olcli config set-timeout 60000
|
|
414
|
+
|
|
415
|
+
# Or use environment variable
|
|
416
|
+
export OVERLEAF_TIMEOUT=60000
|
|
417
|
+
olcli pull "Large Thesis"
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Precedence: `--timeout` flag > `OVERLEAF_TIMEOUT` env > `config set-timeout` > default (10000ms).
|
|
421
|
+
|
|
391
422
|
## Examples
|
|
392
423
|
|
|
393
424
|
### Work on a thesis
|
|
@@ -438,6 +469,198 @@ olcli output bbl -o main.bbl
|
|
|
438
469
|
olcli zip -o arxiv-submission.zip
|
|
439
470
|
```
|
|
440
471
|
|
|
472
|
+
## Programmatic Usage (Library API)
|
|
473
|
+
|
|
474
|
+
`@aloth/olcli` exposes `OverleafClient` and all public interfaces as a proper library so you can use it in your own scripts, tools, and AI agents.
|
|
475
|
+
|
|
476
|
+
### Install
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
npm install @aloth/olcli
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Basic example
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
import { OverleafClient } from '@aloth/olcli';
|
|
486
|
+
|
|
487
|
+
// Create a client from an Overleaf session cookie
|
|
488
|
+
const client = await OverleafClient.fromSessionCookie(cookie);
|
|
489
|
+
|
|
490
|
+
// List all projects
|
|
491
|
+
const projects = await client.listProjects();
|
|
492
|
+
console.log(projects);
|
|
493
|
+
|
|
494
|
+
// Get detailed info (file tree) for a project
|
|
495
|
+
const info = await client.getProjectInfo(projectId);
|
|
496
|
+
|
|
497
|
+
// Download project as a zip buffer
|
|
498
|
+
const zipBuf = await client.downloadProject(projectId);
|
|
499
|
+
|
|
500
|
+
// Compile and download PDF
|
|
501
|
+
const pdfBuf = await client.downloadPdf(projectId);
|
|
502
|
+
|
|
503
|
+
// Upload a file
|
|
504
|
+
import { readFileSync } from 'node:fs';
|
|
505
|
+
await client.uploadFile(projectId, null, 'main.tex', readFileSync('main.tex'));
|
|
506
|
+
|
|
507
|
+
// List review comments
|
|
508
|
+
const comments = await client.listComments(projectId, { status: 'open' });
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Available exports
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
import {
|
|
515
|
+
// Core client
|
|
516
|
+
OverleafClient,
|
|
517
|
+
|
|
518
|
+
// Types / interfaces
|
|
519
|
+
Project, ProjectInfo, FolderEntry, DocEntry, FileEntry,
|
|
520
|
+
CommentMessage, ProjectComment, CommentContext, CommentStatus,
|
|
521
|
+
ListCommentsOptions, AddCommentOptions, Credentials, SessionCookiePair,
|
|
522
|
+
|
|
523
|
+
// Configuration utilities
|
|
524
|
+
getBaseUrl, setBaseUrl, getSessionCookie, setSessionCookie,
|
|
525
|
+
getSessionCookieName, setSessionCookieName, getCsrf, setCsrf,
|
|
526
|
+
getLastProject, setLastProject, clearConfig, getConfigPath, saveOlAuth,
|
|
527
|
+
getTimeout, setTimeout, getPasswordCredentials, setPasswordCredentials,
|
|
528
|
+
clearPasswordCredentials, type PasswordCredentials,
|
|
529
|
+
|
|
530
|
+
// Ignore utilities
|
|
531
|
+
DEFAULT_IGNORE_PATTERNS, loadIgnore, shouldIgnore, buildTexSiblingSet,
|
|
532
|
+
IgnoreContext, LoadIgnoreOptions,
|
|
533
|
+
} from '@aloth/olcli';
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## MCP Server
|
|
539
|
+
|
|
540
|
+
`@aloth/olcli` ships an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server so AI assistants like **Claude Desktop**, **Cursor**, and **Windsurf** can interact with your Overleaf projects directly.
|
|
541
|
+
|
|
542
|
+
### MCP tools
|
|
543
|
+
|
|
544
|
+
| Tool | Description |
|
|
545
|
+
|------|-------------|
|
|
546
|
+
| `list_projects` | List all Overleaf projects |
|
|
547
|
+
| `get_project_info` | Get file tree and metadata for a project |
|
|
548
|
+
| `pull_project` | Download and extract a project to a local directory |
|
|
549
|
+
| `push_file` | Upload a local file to a project |
|
|
550
|
+
| `compile` | Compile a project and get the PDF URL |
|
|
551
|
+
| `download_pdf` | Compile a project and save the PDF locally |
|
|
552
|
+
| `list_comments` | List review comments (filter: all / open / resolved) |
|
|
553
|
+
| `get_entities` | Get a flat list of all files in a project |
|
|
554
|
+
| `download_file` | Download a specific file by its remote path |
|
|
555
|
+
| `add_comment` | Add a review comment to a document |
|
|
556
|
+
| `reply_to_comment` | Reply to an existing comment thread |
|
|
557
|
+
| `resolve_comment` | Mark a comment thread as resolved |
|
|
558
|
+
| `delete_entity` | Delete a file or document by path |
|
|
559
|
+
| `rename_entity` | Rename a file or document |
|
|
560
|
+
| `compile_with_outputs` | Compile and return all output files (PDF, BBL, logs…) |
|
|
561
|
+
|
|
562
|
+
### Authentication
|
|
563
|
+
|
|
564
|
+
The MCP server reads credentials in this order:
|
|
565
|
+
|
|
566
|
+
1. **`OVERLEAF_SESSION` environment variable** — set in your MCP config (recommended)
|
|
567
|
+
2. **`OVERLEAF_EMAIL` + `OVERLEAF_PASSWORD` environment variables** — for password login (self-hosted)
|
|
568
|
+
3. **`.olauth` file in cwd** — written by `olcli auth`
|
|
569
|
+
4. **Stored config** — written by `olcli auth` (including saved password credentials)
|
|
570
|
+
|
|
571
|
+
When a session cookie expires and password credentials are available, the MCP server automatically re-authenticates.
|
|
572
|
+
|
|
573
|
+
### Claude Desktop
|
|
574
|
+
|
|
575
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
576
|
+
|
|
577
|
+
```json
|
|
578
|
+
{
|
|
579
|
+
"mcpServers": {
|
|
580
|
+
"overleaf": {
|
|
581
|
+
"command": "npx",
|
|
582
|
+
"args": ["-y", "@aloth/olcli-mcp"],
|
|
583
|
+
"env": {
|
|
584
|
+
"OVERLEAF_SESSION": "<your-overleaf-session-cookie>"
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Or if you have olcli installed globally (`npm install -g @aloth/olcli`):
|
|
592
|
+
|
|
593
|
+
```json
|
|
594
|
+
{
|
|
595
|
+
"mcpServers": {
|
|
596
|
+
"overleaf": {
|
|
597
|
+
"command": "olcli-mcp",
|
|
598
|
+
"env": {
|
|
599
|
+
"OVERLEAF_SESSION": "<your-overleaf-session-cookie>"
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Cursor
|
|
607
|
+
|
|
608
|
+
Add to your Cursor MCP settings (`~/.cursor/mcp.json` or project `.cursor/mcp.json`):
|
|
609
|
+
|
|
610
|
+
```json
|
|
611
|
+
{
|
|
612
|
+
"mcpServers": {
|
|
613
|
+
"overleaf": {
|
|
614
|
+
"command": "npx",
|
|
615
|
+
"args": ["-y", "@aloth/olcli-mcp"],
|
|
616
|
+
"env": {
|
|
617
|
+
"OVERLEAF_SESSION": "<your-overleaf-session-cookie>"
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Windsurf
|
|
625
|
+
|
|
626
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
627
|
+
|
|
628
|
+
```json
|
|
629
|
+
{
|
|
630
|
+
"mcpServers": {
|
|
631
|
+
"overleaf": {
|
|
632
|
+
"command": "npx",
|
|
633
|
+
"args": ["-y", "@aloth/olcli-mcp"],
|
|
634
|
+
"env": {
|
|
635
|
+
"OVERLEAF_SESSION": "<your-overleaf-session-cookie>"
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Getting your session cookie
|
|
643
|
+
|
|
644
|
+
1. Open Overleaf in your browser and log in
|
|
645
|
+
2. Open DevTools → Application (Chrome) or Storage (Firefox) → Cookies
|
|
646
|
+
3. Find `overleaf_session2` (or `sharelatex.sid` for self-hosted)
|
|
647
|
+
4. Copy the value — that's your `OVERLEAF_SESSION`
|
|
648
|
+
|
|
649
|
+
Or run `olcli auth` and then the MCP server will pick it up automatically.
|
|
650
|
+
|
|
651
|
+
### Self-hosted Overleaf
|
|
652
|
+
|
|
653
|
+
Set `OVERLEAF_BASE_URL` in your MCP env:
|
|
654
|
+
|
|
655
|
+
```json
|
|
656
|
+
"env": {
|
|
657
|
+
"OVERLEAF_SESSION": "<cookie>",
|
|
658
|
+
"OVERLEAF_BASE_URL": "https://overleaf.yourcompany.com"
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
441
664
|
## Troubleshooting
|
|
442
665
|
|
|
443
666
|
### Session expired
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { loadIgnore, shouldIgnore, buildTexSiblingSet, } from './ignore.js';
|
|
|
17
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
19
19
|
const VERSION = pkg.version;
|
|
20
|
-
import { getSessionCookie, setSessionCookie, setLastProject, getConfigPath, saveOlAuth, clearConfig, getBaseUrl, setBaseUrl, getSessionCookieName, setSessionCookieName } from './config.js';
|
|
20
|
+
import { getSessionCookie, setSessionCookie, setLastProject, getConfigPath, saveOlAuth, clearConfig, getBaseUrl, setBaseUrl, getSessionCookieName, setSessionCookieName, getTimeout, setTimeout, getPasswordCredentials, setPasswordCredentials } from './config.js';
|
|
21
21
|
const program = new Command();
|
|
22
22
|
program
|
|
23
23
|
.name('olcli')
|
|
@@ -25,26 +25,57 @@ program
|
|
|
25
25
|
.version(VERSION)
|
|
26
26
|
.option('--base-url <url>', 'Overleaf instance base URL (overrides OVERLEAF_BASE_URL and config)')
|
|
27
27
|
.option('--cookie-name <name>', 'Session cookie name (default: overleaf_session2, use overleaf.sid for older instances)')
|
|
28
|
+
.option('--timeout <ms>', 'HTTP request timeout in milliseconds', parseInt)
|
|
28
29
|
.option('--verbose', 'Print every HTTP request, status, and error response body to stderr');
|
|
29
30
|
/**
|
|
30
31
|
* Helper to get authenticated client
|
|
31
32
|
*/
|
|
32
33
|
async function getClient(cookieOpt, baseUrlOpt) {
|
|
33
|
-
const cookie = cookieOpt || getSessionCookie();
|
|
34
|
-
if (!cookie) {
|
|
35
|
-
console.error(chalk.red('No session cookie found.'));
|
|
36
|
-
console.error('Set one with: olcli auth --cookie <session_cookie>');
|
|
37
|
-
console.error('Or set OVERLEAF_SESSION environment variable');
|
|
38
|
-
console.error('Or create .olauth file in current directory');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
34
|
const baseUrl = baseUrlOpt || program.opts().baseUrl || getBaseUrl();
|
|
42
35
|
const cookieName = program.opts().cookieName || getSessionCookieName();
|
|
43
|
-
const
|
|
36
|
+
const cookie = cookieOpt || getSessionCookie();
|
|
37
|
+
const passwordCredentials = cookieOpt ? undefined : getPasswordCredentials();
|
|
38
|
+
if (cookie) {
|
|
39
|
+
try {
|
|
40
|
+
const client = await OverleafClient.fromSessionCookie(cookie, baseUrl, cookieName);
|
|
41
|
+
if (program.opts().verbose)
|
|
42
|
+
client.setVerbose(true);
|
|
43
|
+
const timeout = program.opts().timeout || getTimeout();
|
|
44
|
+
client.setGlobalTimeout(timeout);
|
|
45
|
+
return client;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (!passwordCredentials)
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (passwordCredentials) {
|
|
53
|
+
return loginWithSavedPassword(passwordCredentials, baseUrl, cookieName);
|
|
54
|
+
}
|
|
55
|
+
console.error(chalk.red('No session cookie or password credentials found.'));
|
|
56
|
+
console.error('Set one with: olcli auth --cookie <session_cookie>');
|
|
57
|
+
console.error('Or use: olcli auth --email <email> --password <password>');
|
|
58
|
+
console.error('Or set OVERLEAF_SESSION environment variable');
|
|
59
|
+
console.error('Or create .olauth file in current directory');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
async function loginWithSavedPassword(credentials, baseUrl, cookieName) {
|
|
63
|
+
const client = await OverleafClient.fromPasswordLogin(credentials.email, credentials.password, baseUrl);
|
|
64
|
+
persistClientSession(client, cookieName);
|
|
44
65
|
if (program.opts().verbose)
|
|
45
66
|
client.setVerbose(true);
|
|
67
|
+
const timeout = program.opts().timeout || getTimeout();
|
|
68
|
+
client.setGlobalTimeout(timeout);
|
|
46
69
|
return client;
|
|
47
70
|
}
|
|
71
|
+
function persistClientSession(client, preferredCookieName) {
|
|
72
|
+
const sessionCookie = client.getSessionCookiePair(preferredCookieName);
|
|
73
|
+
if (!sessionCookie) {
|
|
74
|
+
throw new Error('Password login succeeded, but no session cookie was returned.');
|
|
75
|
+
}
|
|
76
|
+
setSessionCookieName(sessionCookie.name);
|
|
77
|
+
setSessionCookie(sessionCookie.value);
|
|
78
|
+
}
|
|
48
79
|
async function resolveProject(client, projectArg, dir = '.') {
|
|
49
80
|
// If project argument provided, use it
|
|
50
81
|
if (projectArg) {
|
|
@@ -76,12 +107,15 @@ async function resolveProject(client, projectArg, dir = '.') {
|
|
|
76
107
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
108
|
program
|
|
78
109
|
.command('auth')
|
|
79
|
-
.description('Authenticate with Overleaf using session cookie')
|
|
110
|
+
.description('Authenticate with Overleaf using a session cookie or email/password')
|
|
80
111
|
.option('--cookie <session>', 'Session cookie (overleaf_session2 value)')
|
|
112
|
+
.option('--email <email>', 'Account email for password login')
|
|
113
|
+
.option('--password <password>', 'Account password for password login')
|
|
114
|
+
.option('--no-save-password', 'Do not persist email/password credentials')
|
|
81
115
|
.option('--save-local', 'Save to .olauth in current directory')
|
|
82
116
|
.action(async (options) => {
|
|
83
|
-
if (!options.cookie) {
|
|
84
|
-
console.log(chalk.yellow('To authenticate, provide
|
|
117
|
+
if (!options.cookie && !options.email && !options.password) {
|
|
118
|
+
console.log(chalk.yellow('To authenticate, provide a session cookie:'));
|
|
85
119
|
console.log();
|
|
86
120
|
console.log('1. Log into overleaf.com in your browser');
|
|
87
121
|
console.log('2. Open Developer Tools (F12) → Application → Cookies');
|
|
@@ -90,22 +124,46 @@ program
|
|
|
90
124
|
console.log();
|
|
91
125
|
console.log(chalk.cyan(' olcli auth --cookie "your_session_cookie_value"'));
|
|
92
126
|
console.log();
|
|
127
|
+
console.log('Or log in with email/password:');
|
|
128
|
+
console.log(chalk.cyan(' olcli auth --email "you@example.com" --password "your_password"'));
|
|
129
|
+
console.log();
|
|
93
130
|
console.log('Or set OVERLEAF_SESSION environment variable');
|
|
94
131
|
return;
|
|
95
132
|
}
|
|
133
|
+
if (options.cookie && (options.email || options.password)) {
|
|
134
|
+
console.error(chalk.red('Use either --cookie or --email/--password, not both.'));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
if (!options.cookie && (!options.email || !options.password)) {
|
|
138
|
+
console.error(chalk.red('Both --email and --password are required for password login.'));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
96
141
|
const spinner = ora('Verifying session...').start();
|
|
97
142
|
try {
|
|
98
143
|
const baseUrl = program.opts().baseUrl || getBaseUrl();
|
|
99
144
|
const cookieName = program.opts().cookieName || getSessionCookieName();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
145
|
+
if (options.cookie) {
|
|
146
|
+
const client = await OverleafClient.fromSessionCookie(options.cookie, baseUrl, cookieName);
|
|
147
|
+
const projects = await client.listProjects();
|
|
148
|
+
setSessionCookie(options.cookie);
|
|
149
|
+
if (options.saveLocal) {
|
|
150
|
+
saveOlAuth(options.cookie);
|
|
151
|
+
spinner.succeed(`Authenticated! Found ${projects.length} projects. Saved to .olauth`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
spinner.succeed(`Authenticated! Found ${projects.length} projects.`);
|
|
155
|
+
}
|
|
106
156
|
}
|
|
107
157
|
else {
|
|
108
|
-
spinner.
|
|
158
|
+
spinner.text = 'Logging in with email/password...';
|
|
159
|
+
const client = await OverleafClient.fromPasswordLogin(options.email, options.password, baseUrl);
|
|
160
|
+
const projects = await client.listProjects();
|
|
161
|
+
persistClientSession(client, cookieName);
|
|
162
|
+
setBaseUrl(baseUrl);
|
|
163
|
+
if (options.savePassword !== false) {
|
|
164
|
+
setPasswordCredentials(options.email, options.password);
|
|
165
|
+
}
|
|
166
|
+
spinner.succeed(`Authenticated! Found ${projects.length} projects. Password login saved.`);
|
|
109
167
|
}
|
|
110
168
|
console.log(chalk.dim(`Config saved to: ${getConfigPath()}`));
|
|
111
169
|
}
|
|
@@ -303,6 +361,30 @@ commentsCmd
|
|
|
303
361
|
process.exit(1);
|
|
304
362
|
}
|
|
305
363
|
});
|
|
364
|
+
commentsCmd
|
|
365
|
+
.command('reply <threadId> <body> [project]')
|
|
366
|
+
.description('Reply to a comment thread with a message')
|
|
367
|
+
.option('--json', 'Output as JSON')
|
|
368
|
+
.option('--cookie <session>', 'Session cookie override')
|
|
369
|
+
.action(async (threadId, body, project, options) => {
|
|
370
|
+
const spinner = ora('Posting reply...').start();
|
|
371
|
+
try {
|
|
372
|
+
const client = await getClient(options.cookie);
|
|
373
|
+
const proj = await resolveProject(client, project);
|
|
374
|
+
const message = await client.postCommentMessage(proj.id, threadId, body);
|
|
375
|
+
if (options.json) {
|
|
376
|
+
spinner.stop();
|
|
377
|
+
console.log(JSON.stringify({ replied: true, message }, null, 2));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
spinner.succeed(`Replied to ${threadId}`);
|
|
381
|
+
setLastProject(proj.id);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
spinner.fail(`Failed: ${error.message}`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
306
388
|
commentsCmd
|
|
307
389
|
.command('resolve <threadId> [project]')
|
|
308
390
|
.description('Resolve a comment thread')
|
|
@@ -1261,6 +1343,24 @@ configCmd
|
|
|
1261
1343
|
.action(() => {
|
|
1262
1344
|
console.log(getSessionCookieName());
|
|
1263
1345
|
});
|
|
1346
|
+
configCmd
|
|
1347
|
+
.command('set-timeout <ms>')
|
|
1348
|
+
.description('Set the default HTTP request timeout in milliseconds')
|
|
1349
|
+
.action((ms) => {
|
|
1350
|
+
const timeout = parseInt(ms, 10);
|
|
1351
|
+
if (isNaN(timeout)) {
|
|
1352
|
+
console.error(chalk.red('Invalid timeout value. Must be a number.'));
|
|
1353
|
+
process.exit(1);
|
|
1354
|
+
}
|
|
1355
|
+
setTimeout(timeout);
|
|
1356
|
+
console.log(chalk.green(`Default timeout set to: ${timeout}ms`));
|
|
1357
|
+
});
|
|
1358
|
+
configCmd
|
|
1359
|
+
.command('get-timeout')
|
|
1360
|
+
.description('Get the current default HTTP request timeout')
|
|
1361
|
+
.action(() => {
|
|
1362
|
+
console.log(`${getTimeout()}ms`);
|
|
1363
|
+
});
|
|
1264
1364
|
program
|
|
1265
1365
|
.command('ignored [dir]')
|
|
1266
1366
|
.description('Show ignore patterns currently in effect for a project directory')
|