@aloth/olcli 0.10.0 → 0.12.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 +47 -4
- package/dist/cli.js +151 -12
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +70 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +97 -4
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +18 -0
- package/dist/mcp.js.map +1 -1
- package/dist/prompt.d.ts +63 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +146 -0
- package/dist/prompt.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Command-line interface for Overleaf** — Sync, manage, and compile LaTeX projects from your terminal.
|
|
4
4
|
|
|
5
|
+
[](https://github.com/aloth/olcli/actions/workflows/ci.yml)
|
|
5
6
|
[](https://www.npmjs.com/package/@aloth/olcli)
|
|
6
7
|
[](https://www.npmjs.com/package/@aloth/olcli)
|
|
7
8
|
[](https://github.com/aloth/olcli)
|
|
@@ -17,6 +18,7 @@ Work with Overleaf projects directly from your command line. Edit locally with y
|
|
|
17
18
|
## Features
|
|
18
19
|
|
|
19
20
|
- 📋 **List** all your Overleaf projects
|
|
21
|
+
- ✨ **Create** blank or example projects
|
|
20
22
|
- ⬇️ **Pull** project files to local directory for offline editing
|
|
21
23
|
- ⬆️ **Push** local changes back to Overleaf
|
|
22
24
|
- 🔄 **Sync** bidirectionally with smart conflict detection
|
|
@@ -82,9 +84,16 @@ olcli auth --cookie "your_session_cookie_value"
|
|
|
82
84
|
**Email/password** (self-hosted without reCAPTCHA):
|
|
83
85
|
|
|
84
86
|
```bash
|
|
85
|
-
olcli auth --email "you@example.com"
|
|
87
|
+
olcli auth --email "you@example.com"
|
|
88
|
+
# prompts for the password, so it stays out of your shell history
|
|
86
89
|
```
|
|
87
90
|
|
|
91
|
+
The password is **not stored** unless you pass `--save-password`. A session
|
|
92
|
+
cookie is saved either way and is what later commands use; the password only
|
|
93
|
+
buys an automatic re-login once that cookie expires. For scripts, set
|
|
94
|
+
`OVERLEAF_EMAIL` and `OVERLEAF_PASSWORD` — every command reads them, so a
|
|
95
|
+
scripted run never needs `olcli auth` at all.
|
|
96
|
+
|
|
88
97
|
### 2. List Projects
|
|
89
98
|
|
|
90
99
|
```bash
|
|
@@ -127,7 +136,7 @@ All commands auto-detect the project when run from a synced directory (contains
|
|
|
127
136
|
|---------|-------------|
|
|
128
137
|
| `olcli auth` | Set session cookie or login with email/password |
|
|
129
138
|
| `olcli whoami` | Check authentication status |
|
|
130
|
-
| `olcli logout` | Clear
|
|
139
|
+
| `olcli logout` | Clear the global config and the local `.olauth`, reporting each |
|
|
131
140
|
| `olcli list` | List all projects |
|
|
132
141
|
| `olcli info [project]` | Show project details and file list |
|
|
133
142
|
| `olcli pull [project] [dir]` | Download project files to local directory |
|
|
@@ -138,6 +147,7 @@ All commands auto-detect the project when run from a synced directory (contains
|
|
|
138
147
|
| `olcli download <file> [project]` | Download a single file |
|
|
139
148
|
| `olcli delete <file> [project]` | Delete a remote file or folder (alias: `rm`) |
|
|
140
149
|
| `olcli rename <old> <new> [project]` | Rename a remote file or folder (alias: `mv`) |
|
|
150
|
+
| `olcli project create <name>` | Create a blank or example project (`--template blank\|example`) |
|
|
141
151
|
| `olcli project rename <new> [project]` | Rename the project itself (`--dry-run`) |
|
|
142
152
|
| `olcli project rename-bulk` | Rename many projects by pattern (dry-run unless `--apply`) |
|
|
143
153
|
| `olcli compile [project]` | Trigger PDF compilation |
|
|
@@ -272,7 +282,32 @@ Credentials are checked in order:
|
|
|
272
282
|
|
|
273
283
|
1. `OVERLEAF_SESSION` environment variable
|
|
274
284
|
2. `.olauth` file in current directory
|
|
275
|
-
3. Global config
|
|
285
|
+
3. Global config — run `olcli check` to see the exact path
|
|
286
|
+
|
|
287
|
+
The global config path is platform-dependent (`conf` resolves it), so it is not
|
|
288
|
+
hardcoded here: on macOS it lands under `~/Library/Preferences/`, on Linux under
|
|
289
|
+
`~/.config/`. `olcli auth` prints the path it wrote to.
|
|
290
|
+
|
|
291
|
+
⚠️ `olcli auth --save-local` writes `.olauth` into the **current directory**,
|
|
292
|
+
which is usually your LaTeX project. Add it to that project's `.gitignore`
|
|
293
|
+
before committing.
|
|
294
|
+
|
|
295
|
+
### What is stored, and how to clear it
|
|
296
|
+
|
|
297
|
+
Everything is stored in plaintext, so it is worth knowing what is on disk:
|
|
298
|
+
|
|
299
|
+
| Credential | Stored by default | Where |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| Session cookie | yes | global config, or `.olauth` with `--save-local` |
|
|
302
|
+
| Email + password | **no** — only with `--save-password` | global config |
|
|
303
|
+
|
|
304
|
+
`olcli check` reports what exists without printing any secret.
|
|
305
|
+
|
|
306
|
+
`olcli logout` clears the global config **and** the `.olauth` file in the
|
|
307
|
+
current directory, then lists what it removed. It cannot unset environment
|
|
308
|
+
variables, so if `OVERLEAF_SESSION` or `OVERLEAF_EMAIL`/`OVERLEAF_PASSWORD` are
|
|
309
|
+
set, it says so instead of implying you are logged out — those take precedence
|
|
310
|
+
over anything on disk.
|
|
276
311
|
|
|
277
312
|
### Self-hosted Overleaf
|
|
278
313
|
|
|
@@ -341,6 +376,7 @@ import { OverleafClient } from '@aloth/olcli';
|
|
|
341
376
|
|
|
342
377
|
const client = await OverleafClient.fromSessionCookie(cookie);
|
|
343
378
|
|
|
379
|
+
const created = await client.createProject('My Paper');
|
|
344
380
|
const projects = await client.listProjects();
|
|
345
381
|
const info = await client.getProjectInfo(projectId);
|
|
346
382
|
const zipBuf = await client.downloadProject(projectId);
|
|
@@ -357,7 +393,8 @@ const comments = await client.listComments(projectId, { status: 'open' });
|
|
|
357
393
|
import {
|
|
358
394
|
OverleafClient,
|
|
359
395
|
// Types
|
|
360
|
-
Project, ProjectInfo,
|
|
396
|
+
Project, ProjectInfo, ProjectTemplate, CreateProjectOptions, CreatedProject,
|
|
397
|
+
FolderEntry, DocEntry, FileEntry,
|
|
361
398
|
CommentMessage, ProjectComment, CommentContext, CommentStatus,
|
|
362
399
|
ListCommentsOptions, AddCommentOptions, Credentials, SessionCookiePair,
|
|
363
400
|
// Config utilities
|
|
@@ -387,6 +424,12 @@ import {
|
|
|
387
424
|
|
|
388
425
|
Contributions are welcome! Please open an issue or submit a pull request.
|
|
389
426
|
|
|
427
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, which tests need a real
|
|
428
|
+
Overleaf account, and what to expect from CI on a pull request.
|
|
429
|
+
|
|
430
|
+
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) explains how the client works —
|
|
431
|
+
there is no public Overleaf API, so it authenticates as a browser session.
|
|
432
|
+
|
|
390
433
|
## License
|
|
391
434
|
|
|
392
435
|
MIT © [Alexander Loth](https://alexloth.com)
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,8 @@ import { loadIgnore } from './ignore.js';
|
|
|
21
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
23
23
|
const VERSION = pkg.version;
|
|
24
|
-
import { getSessionCookie, setSessionCookie, setLastProject, getConfigPath, saveOlAuth, clearConfig, getBaseUrl, setBaseUrl, getSessionCookieName, setSessionCookieName, getTimeout, setTimeout, getPasswordCredentials, setPasswordCredentials } from './config.js';
|
|
24
|
+
import { getSessionCookie, setSessionCookie, setLastProject, getConfigPath, saveOlAuth, clearConfig, getBaseUrl, setBaseUrl, getSessionCookieName, setSessionCookieName, getTimeout, setTimeout, getPasswordCredentials, setPasswordCredentials, clearOlAuth, inspectStoredCredentials } from './config.js';
|
|
25
|
+
import { promptHidden, PromptCancelled, NotATerminal } from './prompt.js';
|
|
25
26
|
const program = new Command();
|
|
26
27
|
program
|
|
27
28
|
.name('olcli')
|
|
@@ -114,9 +115,18 @@ program
|
|
|
114
115
|
.description('Authenticate with Overleaf using a session cookie or email/password')
|
|
115
116
|
.option('--cookie <session>', 'Session cookie (overleaf_session2 value)')
|
|
116
117
|
.option('--email <email>', 'Account email for password login')
|
|
117
|
-
.option('--password <password>', 'Account password
|
|
118
|
-
.option('--
|
|
118
|
+
.option('--password <password>', 'Account password (omit to be prompted; see warning below)')
|
|
119
|
+
.option('--save-password', 'Persist the password in the config file, in plaintext')
|
|
120
|
+
.option('--no-save-password', 'Do not persist the password (the default; kept for existing scripts)')
|
|
119
121
|
.option('--save-local', 'Save to .olauth in current directory')
|
|
122
|
+
.addHelpText('after', `
|
|
123
|
+
The password is not stored unless you ask for it with --save-password. A
|
|
124
|
+
session cookie is stored either way, and that is what later commands use; the
|
|
125
|
+
password only buys an automatic re-login once the cookie expires.
|
|
126
|
+
|
|
127
|
+
Passing --password puts the password in your shell history. Omit it to be
|
|
128
|
+
prompted instead, or set OVERLEAF_EMAIL and OVERLEAF_PASSWORD, which every
|
|
129
|
+
command reads without needing 'auth' at all.`)
|
|
120
130
|
.action(async (options) => {
|
|
121
131
|
if (!options.cookie && !options.email && !options.password) {
|
|
122
132
|
console.log(chalk.yellow('To authenticate, provide a session cookie:'));
|
|
@@ -129,7 +139,8 @@ program
|
|
|
129
139
|
console.log(chalk.cyan(' olcli auth --cookie "your_session_cookie_value"'));
|
|
130
140
|
console.log();
|
|
131
141
|
console.log('Or log in with email/password:');
|
|
132
|
-
console.log(chalk.cyan(' olcli auth --email "you@example.com"
|
|
142
|
+
console.log(chalk.cyan(' olcli auth --email "you@example.com"'));
|
|
143
|
+
console.log(chalk.dim(' (prompts for the password, so it stays out of your shell history)'));
|
|
133
144
|
console.log();
|
|
134
145
|
console.log('Or set OVERLEAF_SESSION environment variable');
|
|
135
146
|
return;
|
|
@@ -138,10 +149,41 @@ program
|
|
|
138
149
|
console.error(chalk.red('Use either --cookie or --email/--password, not both.'));
|
|
139
150
|
process.exit(1);
|
|
140
151
|
}
|
|
141
|
-
if (!options.cookie &&
|
|
142
|
-
console.error(chalk.red('
|
|
152
|
+
if (!options.cookie && !options.email) {
|
|
153
|
+
console.error(chalk.red('--email is required for password login.'));
|
|
143
154
|
process.exit(1);
|
|
144
155
|
}
|
|
156
|
+
// Resolve the password before the spinner starts: a prompt and a spinner
|
|
157
|
+
// both own the terminal, and ora would redraw over the prompt line.
|
|
158
|
+
let password = options.password;
|
|
159
|
+
if (!options.cookie) {
|
|
160
|
+
if (password) {
|
|
161
|
+
console.log(chalk.yellow('⚠ --password is now in your shell history.'));
|
|
162
|
+
console.log(chalk.dim(' Omit it to be prompted, or set OVERLEAF_EMAIL/OVERLEAF_PASSWORD.'));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
try {
|
|
166
|
+
password = await promptHidden(`Password for ${options.email}: `);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof NotATerminal) {
|
|
170
|
+
console.error(chalk.red('No terminal available to prompt for a password.'));
|
|
171
|
+
console.error('Set OVERLEAF_EMAIL and OVERLEAF_PASSWORD instead — every command reads them,');
|
|
172
|
+
console.error("so a scripted run does not need 'olcli auth' at all.");
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
if (error instanceof PromptCancelled) {
|
|
176
|
+
console.error(chalk.red('Cancelled.'));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
if (!password) {
|
|
182
|
+
console.error(chalk.red('Password must not be empty.'));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
145
187
|
const spinner = ora('Verifying session...').start();
|
|
146
188
|
try {
|
|
147
189
|
const baseUrl = program.opts().baseUrl || getBaseUrl();
|
|
@@ -160,14 +202,29 @@ program
|
|
|
160
202
|
}
|
|
161
203
|
else {
|
|
162
204
|
spinner.text = 'Logging in with email/password...';
|
|
163
|
-
const client = await OverleafClient.fromPasswordLogin(options.email,
|
|
205
|
+
const client = await OverleafClient.fromPasswordLogin(options.email, password, baseUrl);
|
|
164
206
|
const projects = await client.listProjects();
|
|
165
207
|
persistClientSession(client, cookieName);
|
|
166
208
|
setBaseUrl(baseUrl);
|
|
167
|
-
|
|
168
|
-
|
|
209
|
+
// Opt-in, not opt-out. The session cookie persisted just above is what
|
|
210
|
+
// later commands actually use; the password only buys an automatic
|
|
211
|
+
// re-login after that cookie expires, and it is stored in plaintext.
|
|
212
|
+
// A cookie is scoped to olcli and rotates; a password is reusable
|
|
213
|
+
// everywhere and cannot be revoked without changing it. See issue #50.
|
|
214
|
+
const savePassword = options.savePassword === true;
|
|
215
|
+
if (savePassword) {
|
|
216
|
+
setPasswordCredentials(options.email, password);
|
|
217
|
+
}
|
|
218
|
+
// The old message said "Password login saved." unconditionally - even
|
|
219
|
+
// under --no-save-password, which had just prevented exactly that.
|
|
220
|
+
spinner.succeed(`Authenticated! Found ${projects.length} projects.`);
|
|
221
|
+
if (savePassword) {
|
|
222
|
+
console.log(chalk.yellow('Password stored in plaintext in the config file.'));
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
console.log(chalk.dim('Session cookie stored. The password was not saved; re-run'));
|
|
226
|
+
console.log(chalk.dim('olcli auth when the session expires, or use --save-password.'));
|
|
169
227
|
}
|
|
170
|
-
spinner.succeed(`Authenticated! Found ${projects.length} projects. Password login saved.`);
|
|
171
228
|
}
|
|
172
229
|
console.log(chalk.dim(`Config saved to: ${getConfigPath()}`));
|
|
173
230
|
}
|
|
@@ -200,9 +257,44 @@ program
|
|
|
200
257
|
program
|
|
201
258
|
.command('logout')
|
|
202
259
|
.description('Clear stored credentials')
|
|
260
|
+
.addHelpText('after', `
|
|
261
|
+
Clears the global config and the .olauth file in the current directory, and
|
|
262
|
+
reports each one separately. Environment variables cannot be cleared by a
|
|
263
|
+
child process, so OVERLEAF_SESSION and OVERLEAF_EMAIL/OVERLEAF_PASSWORD are
|
|
264
|
+
reported instead of silently ignored - both take precedence over anything on
|
|
265
|
+
disk.`)
|
|
203
266
|
.action(() => {
|
|
267
|
+
// Read before clearing: afterwards there is nothing left to report on.
|
|
268
|
+
const before = inspectStoredCredentials();
|
|
204
269
|
clearConfig();
|
|
205
|
-
|
|
270
|
+
const removedOlAuth = clearOlAuth();
|
|
271
|
+
const cleared = [];
|
|
272
|
+
if (before.sessionCookie)
|
|
273
|
+
cleared.push('session cookie (global config)');
|
|
274
|
+
if (before.password)
|
|
275
|
+
cleared.push('saved password (global config)');
|
|
276
|
+
if (removedOlAuth)
|
|
277
|
+
cleared.push(removedOlAuth);
|
|
278
|
+
if (cleared.length === 0) {
|
|
279
|
+
console.log('Nothing stored to clear.');
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.log(chalk.green('Cleared:'));
|
|
283
|
+
for (const item of cleared)
|
|
284
|
+
console.log(` ${item}`);
|
|
285
|
+
}
|
|
286
|
+
// The reason this command was wrong before: it announced success while a
|
|
287
|
+
// higher-precedence source kept the user authenticated. Anything olcli
|
|
288
|
+
// cannot clear has to be said out loud, or the message is a lie again.
|
|
289
|
+
if (before.envSession || before.envPassword) {
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(chalk.yellow('Still authenticated in this shell:'));
|
|
292
|
+
if (before.envSession)
|
|
293
|
+
console.log(' OVERLEAF_SESSION is set');
|
|
294
|
+
if (before.envPassword)
|
|
295
|
+
console.log(' OVERLEAF_EMAIL and OVERLEAF_PASSWORD are set');
|
|
296
|
+
console.log(chalk.dim(' These outrank anything on disk. Unset them to finish logging out.'));
|
|
297
|
+
}
|
|
206
298
|
});
|
|
207
299
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
208
300
|
// PROJECT COMMANDS
|
|
@@ -701,7 +793,39 @@ program
|
|
|
701
793
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
702
794
|
const projectCmd = program
|
|
703
795
|
.command('project')
|
|
704
|
-
.description('Operate on projects themselves (rename, bulk rename)');
|
|
796
|
+
.description('Operate on projects themselves (create, rename, bulk rename)');
|
|
797
|
+
projectCmd
|
|
798
|
+
.command('create <name>')
|
|
799
|
+
.description('Create a new project')
|
|
800
|
+
.option('-t, --template <type>', 'Project template: blank or example', 'blank')
|
|
801
|
+
.option('--json', 'Output as JSON')
|
|
802
|
+
.option('--cookie <session>', 'Session cookie override')
|
|
803
|
+
.action(async (name, options) => {
|
|
804
|
+
const template = options.template;
|
|
805
|
+
if (template !== 'blank' && template !== 'example') {
|
|
806
|
+
console.error(chalk.red(`Unsupported project template: ${template}`));
|
|
807
|
+
console.error('Supported templates: blank, example');
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
const spinner = ora('Creating project...').start();
|
|
811
|
+
try {
|
|
812
|
+
const client = await getClient(options.cookie);
|
|
813
|
+
const created = await client.createProject(name, { template });
|
|
814
|
+
setLastProject(created.id);
|
|
815
|
+
if (options.json) {
|
|
816
|
+
spinner.stop();
|
|
817
|
+
console.log(JSON.stringify(created, null, 2));
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
spinner.succeed(`Created project: ${created.name}`);
|
|
821
|
+
console.log(` ${chalk.cyan(created.id)}`);
|
|
822
|
+
console.log(` ${chalk.cyan(created.url)}`);
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
spinner.fail(`Failed: ${error.message}`);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
705
829
|
projectCmd
|
|
706
830
|
.command('rename <newname> [project]')
|
|
707
831
|
.description('Rename a project')
|
|
@@ -1732,6 +1856,21 @@ program
|
|
|
1732
1856
|
else {
|
|
1733
1857
|
console.log(chalk.yellow('✗ No session cookie found'));
|
|
1734
1858
|
}
|
|
1859
|
+
// A stored password is a credential source this command used to omit,
|
|
1860
|
+
// which made it impossible to answer "is my password on disk?" without
|
|
1861
|
+
// opening the config file. Never print the value - only whether it exists
|
|
1862
|
+
// and which source it came from.
|
|
1863
|
+
const stored = inspectStoredCredentials();
|
|
1864
|
+
if (stored.envPassword) {
|
|
1865
|
+
console.log(chalk.yellow('⚠ Password set via OVERLEAF_EMAIL/OVERLEAF_PASSWORD'));
|
|
1866
|
+
}
|
|
1867
|
+
else if (stored.password) {
|
|
1868
|
+
console.log(chalk.yellow('⚠ Password stored in plaintext in the config file'));
|
|
1869
|
+
console.log(chalk.dim(" Remove it with 'olcli logout', then re-auth without --save-password."));
|
|
1870
|
+
}
|
|
1871
|
+
if (stored.olAuthPath) {
|
|
1872
|
+
console.log(chalk.dim(` .olauth present: ${stored.olAuthPath}`));
|
|
1873
|
+
}
|
|
1735
1874
|
});
|
|
1736
1875
|
program.parse(process.argv);
|
|
1737
1876
|
//# sourceMappingURL=cli.js.map
|