@roman-16/proton-cli 1.9.7

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roman
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.
package/README.md ADDED
@@ -0,0 +1,625 @@
1
+ # proton-cli
2
+
3
+ [![Release](https://img.shields.io/github/v/release/roman-16/proton-cli?sort=semver)](https://github.com/roman-16/proton-cli/releases/latest)
4
+ [![License: MIT](https://img.shields.io/github/license/roman-16/proton-cli)](LICENSE)
5
+ [![Go](https://img.shields.io/github/go-mod/go-version/roman-16/proton-cli)](go.mod)
6
+
7
+ > **Disclaimer:** This is an unofficial, community-built tool and is not endorsed by or affiliated with Proton AG. Use at your own risk.
8
+
9
+ An unofficial command-line tool for [Proton](https://proton.me) - Mail, Drive, Calendar, Pass, and Contacts from your terminal, with real end-to-end encryption.
10
+
11
+ proton-cli implements the same authentication and encryption as the [Proton web client](https://github.com/ProtonMail/WebClients): SRP login, the PGP key hierarchy, and full end-to-end encryption, using [go-srp](https://github.com/ProtonMail/go-srp) and [gopenpgp](https://github.com/ProtonMail/gopenpgp).
12
+
13
+ ## Contents
14
+
15
+ - [Features](#features)
16
+ - [Install](#install)
17
+ - [Quick start](#quick-start)
18
+ - [Core concepts](#core-concepts)
19
+ - [Configuration](#configuration)
20
+ - [Usage](#usage)
21
+ - [Scripting](#scripting)
22
+ - [How it works](#how-it-works)
23
+ - [Human verification (CAPTCHA)](#human-verification-captcha)
24
+ - [Security](#security)
25
+ - [Limitations](#limitations)
26
+ - [Development](#development)
27
+ - [API reference](#api-reference)
28
+ - [License](#license)
29
+
30
+ ## Features
31
+
32
+ - **Mail** - list, search, read, send (attachments, HTML, scheduled, self-destruct), organize, and manage labels, folders, and Sieve filters.
33
+ - **Drive** - upload/download (streaming and recursive), move, copy, revisions, public links, member sharing, trash, and photos.
34
+ - **Calendar** - calendars and events, recurrence, reminders, and attendees.
35
+ - **Pass** - vaults, items (login, note, card, wifi, ssh key, identity, custom), aliases, and TOTP.
36
+ - **Contacts** - full CRUD, pinned encryption keys, and contact groups.
37
+ - **Real E2EE** - SRP login and the full PGP key hierarchy, decrypting and signing exactly like the web client.
38
+ - **Built for scripts** - `--output json`, meaningful exit codes, streaming I/O, and `stdout = new ID` on create.
39
+
40
+ ## Install
41
+
42
+ ### Download a binary (recommended)
43
+
44
+ Grab the latest binary for your platform from [**GitHub Releases**](https://github.com/roman-16/proton-cli/releases/latest).
45
+
46
+ | Platform | Binary |
47
+ |---|---|
48
+ | Linux (x86_64) | `proton-cli_linux_amd64` |
49
+ | Linux (ARM64) | `proton-cli_linux_arm64` |
50
+ | macOS (Apple Silicon) | `proton-cli_darwin_arm64` |
51
+ | macOS (Intel) | `proton-cli_darwin_amd64` |
52
+ | Windows (x86_64) | `proton-cli_windows_amd64.exe` |
53
+
54
+ **Linux / macOS:**
55
+
56
+ ```bash
57
+ curl -LO https://github.com/roman-16/proton-cli/releases/latest/download/proton-cli_linux_amd64
58
+ chmod +x proton-cli_linux_amd64
59
+ sudo mv proton-cli_linux_amd64 /usr/local/bin/proton-cli
60
+ ```
61
+
62
+ **Windows:** download the `.exe` from the [releases page](https://github.com/roman-16/proton-cli/releases/latest) and add it to your PATH.
63
+
64
+ ### Install on Arch Linux (AUR)
65
+
66
+ ```bash
67
+ yay -S proton-cli-bin # or: paru -S proton-cli-bin
68
+ ```
69
+
70
+ ### Install on Debian, Ubuntu, Linux Mint (APT)
71
+
72
+ ```bash
73
+ sudo install -d -m 0755 /etc/apt/keyrings
74
+ curl -fsSL https://roman-16.github.io/proton-cli/gpg.key | sudo tee /etc/apt/keyrings/proton-cli.asc >/dev/null
75
+ echo "deb [signed-by=/etc/apt/keyrings/proton-cli.asc] https://roman-16.github.io/proton-cli stable main" | sudo tee /etc/apt/sources.list.d/proton-cli.list
76
+ sudo apt update && sudo apt install proton-cli
77
+ ```
78
+
79
+ ### Install on Fedora, RHEL, Alpine
80
+
81
+ `.rpm` and `.apk` packages are attached to each [release](https://github.com/roman-16/proton-cli/releases/latest). Download the one for your platform and install it directly:
82
+
83
+ ```bash
84
+ sudo dnf install ./proton-cli_*.rpm # Fedora / RHEL
85
+ sudo apk add --allow-untrusted ./proton-cli_*.apk # Alpine
86
+ ```
87
+
88
+ ### Install with Nix
89
+
90
+ Available in [nixpkgs](https://search.nixos.org/packages?query=proton-cli) as the `proton-cli` package.
91
+
92
+ ### Install with Nix flake
93
+
94
+ ```nix
95
+ inputs = {
96
+ proton-cli = {
97
+ url = "github:roman-16/proton-cli";
98
+ inputs.nixpkgs.follows = "nixpkgs";
99
+ };
100
+ };
101
+
102
+ # in a NixOS module
103
+ environment.systemPackages = [
104
+ proton-cli.packages.${pkgs.stdenv.hostPlatform.system}.default
105
+ ];
106
+ ```
107
+
108
+ ### Install on Windows (winget)
109
+
110
+ ```powershell
111
+ winget install Roman.ProtonCLI
112
+ ```
113
+
114
+
115
+ ### Install with Homebrew (macOS)
116
+
117
+ ```bash
118
+ brew install --cask roman-16/tap/proton-cli
119
+ ```
120
+
121
+ ### Install with Go
122
+
123
+ ```bash
124
+ go install github.com/roman-16/proton-cli@latest
125
+ ```
126
+
127
+ > **Note:** `go install` builds do **not** embed the CAPTCHA helper that release binaries include. If Proton demands human verification at login, install a release binary instead. See [Human verification](#human-verification-captcha).
128
+
129
+ ### Install with npm
130
+
131
+ ```bash
132
+ npm install -g @roman-16/proton-cli
133
+ ```
134
+
135
+ ### Build from source
136
+
137
+ ```bash
138
+ git clone https://github.com/roman-16/proton-cli.git
139
+ cd proton-cli
140
+ go build .
141
+ ```
142
+
143
+ ## Quick start
144
+
145
+ ### 1. Set your credentials
146
+
147
+ ```bash
148
+ export PROTON_USER=alice@proton.me
149
+ export PROTON_PASSWORD=your-password
150
+ # export PROTON_TOTP=123456 # if 2FA is enabled
151
+ ```
152
+
153
+ The session is saved to `~/.config/proton-cli/sessions/<profile>.json` after the first login and reused automatically.
154
+
155
+ ### 2. Try it
156
+
157
+ ```bash
158
+ proton-cli mail messages list
159
+ proton-cli drive items list
160
+ proton-cli --help # every command and subcommand accepts --help
161
+ proton-cli --version
162
+ ```
163
+
164
+ ## Core concepts
165
+
166
+ These apply across every command.
167
+
168
+ - **REF** - anywhere you see `REF`, pass either a full Proton ID or a search term (subject / name / URL / title, depending on the command). Ambiguous matches print candidates to stderr and exit `4`.
169
+ - **Short IDs** - in an interactive terminal, list commands shorten Proton IDs to 8 characters. proton-cli caches the IDs you have seen at `~/.config/proton-cli/idcache/<profile>.json`, so you can paste an 8-char prefix into any command that takes an ID. Pipes, redirection, and `--output json|yaml` always emit full IDs. Pass `--full-ids` to disable shortening. See [Short IDs](#short-ids) below.
170
+ - **Output** - `--output text|json|yaml` (default `text`). JSON/YAML use `snake_case` keys.
171
+ - **Exit codes** - `0` success · `1` user error · `2` auth · `3` not-found · `4` conflict / ambiguous · `5` network / server · `130` cancelled.
172
+ - **Create = ID on stdout** - creating commands print the new ID to stdout and `✓ …` to stderr, so `ID=$(proton-cli ... create ...)` works. `mail messages send` follows suit: it prints the message ID, and scheduled sends confirm the resolved local time.
173
+ - **Streaming I/O** - `-` means stdin (inputs) or stdout (outputs), e.g. `mail messages send --body -`, `drive items upload - /path`, `drive items download /path --output -`.
174
+ - **Dry run** - `--dry-run` on any mutating command previews without applying.
175
+ - **Cancellation** - `Ctrl+C` aborts in-flight operations.
176
+
177
+ ### Short IDs
178
+
179
+ ```
180
+ $ proton-cli mail messages list
181
+ ID FROM SUBJECT DATE ⚑
182
+ ──────── ────────────── ─────────────── ──────────────── ─
183
+ NWM5AYGx alice@a.com Hello 2026-04-15 14:32
184
+ ```
185
+
186
+ The short prefix pastes straight back into any command:
187
+
188
+ ```
189
+ $ proton-cli mail messages read NWM5AYGx
190
+ Subject: Hello
191
+ ...
192
+ ```
193
+
194
+ Pipes and `--output json|yaml` always emit full IDs:
195
+
196
+ ```
197
+ $ proton-cli mail messages list --output json | jq -r '.messages[].id'
198
+ NWM5AYGx_FIHWT2_QbBr-whe-bIE8rbZunzr5RhXGaihvQ43z2qcxcqFgVRwi7A5C-ADmohv7TjXfYbDEIHZPQ==
199
+ ```
200
+
201
+ If a prefix isn't in your local cache (e.g. copied from another machine), run the matching list command first or use the full ID. Ambiguous prefixes (two cached IDs share the first 8 chars) exit `4` with both candidates listed.
202
+
203
+ ## Configuration
204
+
205
+ Credentials and connection settings resolve in this order: **a flag overrides the profile-scoped env var (`PROTON_<PROFILE>_X`), which overrides the plain env var (`PROTON_X`).** See [Profiles](#profiles-multi-account).
206
+
207
+ ### Environment variables
208
+
209
+ | Variable | Description |
210
+ |---|---|
211
+ | `PROTON_USER` | Proton account email |
212
+ | `PROTON_PASSWORD` | Account password (required for encrypted operations) |
213
+ | `PROTON_TOTP` | TOTP code (if 2FA is enabled) |
214
+ | `PROTON_API_URL` | API base URL (default: `https://mail.proton.me/api`) |
215
+ | `PROTON_APP_VERSION` | App version header (default: `Other`) |
216
+
217
+ ### Profiles (multi-account)
218
+
219
+ A profile is just a name. Each profile has its own session file (`~/.config/proton-cli/sessions/<profile>.json`) and its own set of profile-scoped environment variables. Select the active profile with the `--profile` flag or the `PROTON_PROFILE` environment variable (the flag wins; both fall back to `default`):
220
+
221
+ ```bash
222
+ proton-cli --profile work mail messages list
223
+ # or make it the default for your shell session:
224
+ export PROTON_PROFILE=work
225
+ proton-cli mail messages list
226
+ ```
227
+
228
+ For every setting, the active profile is consulted **scoped-first, then unscoped**: `PROTON_<PROFILE>_X` takes precedence over the plain `PROTON_X`. `<PROFILE>` is the profile name upper-cased, with any non-alphanumeric character replaced by `_` (so `work` becomes `WORK`, `my-work` becomes `MY_WORK`). This applies to every env-backed setting (`USER`, `PASSWORD`, `TOTP`, `API_URL`, `APP_VERSION`):
229
+
230
+ ```bash
231
+ export PROTON_PROFILE=work
232
+ export PROTON_WORK_USER=alice@company.com
233
+ export PROTON_WORK_PASSWORD=work-password
234
+ proton-cli mail messages list # uses PROTON_WORK_*, falling back to PROTON_*
235
+ ```
236
+
237
+ Only the per-profile session file is written to disk; all other wiring lives in flags and the environment.
238
+
239
+ ## Usage
240
+
241
+ The examples below are representative, not exhaustive. Every command lists its full flags with `--help`, e.g. `proton-cli mail messages send --help`.
242
+
243
+ | Area | Subcommands |
244
+ |---|---|
245
+ | `mail messages` | list, search, read, send, unschedule, trash, delete, move, mark, star, unstar |
246
+ | `mail conversations` | list, search, read, trash, delete, move, mark, star, unstar, attachments |
247
+ | `mail attachments` | list, download |
248
+ | `mail labels` | list, create, update, delete |
249
+ | `mail filters` | list, create, update, enable, disable, delete |
250
+ | `mail addresses` | list |
251
+ | `drive items` | list, info, upload, download, rename, move, copy, trash, delete, revisions |
252
+ | `drive folders` | create |
253
+ | `drive share` | status, link, unlink, add, remove |
254
+ | `drive invitations` | list, accept, reject |
255
+ | `drive trash` | list, restore, empty |
256
+ | `drive photos` | list, upload, download, trash, delete, favorite, unfavorite, albums |
257
+ | `calendar calendars` | list, create, rename, delete |
258
+ | `calendar events` | list, get, create, update, respond, delete |
259
+ | `contacts` | list, get, create, update, delete, pin-key, unpin-key, groups |
260
+ | `pass items` | list, get, create, edit, trash, restore, delete |
261
+ | `pass vaults` | list, create, rename, delete |
262
+ | `pass alias` | options, create |
263
+ | `settings` | get, mail, set |
264
+ | `api` | any endpoint (GET / POST / PUT / DELETE) |
265
+
266
+ <details>
267
+ <summary><b>Mail</b></summary>
268
+
269
+ ```bash
270
+ # Messages
271
+ proton-cli mail messages list --folder inbox --unread
272
+ proton-cli mail messages search --keyword "invoice"
273
+ proton-cli mail messages search --from "amazon" --after 2026-01-01
274
+ proton-cli mail messages read REF # body + attachments footer
275
+ proton-cli mail messages read --format text|html|raw REF
276
+ proton-cli mail messages read --body-only REF > body.txt
277
+ proton-cli mail messages send --to a@ex.com --cc c@ex.com --subject Hi --body Hello
278
+ proton-cli mail messages send --to to@ex.com --subject Hi --body "<b>Hi</b>" --html
279
+ proton-cli mail messages send --to to@ex.com --subject Hi --body Hi --attach ./report.pdf
280
+ proton-cli mail messages send --to to@ex.com --subject Hi --body "<b>Hi</b>" --html --attach-inline ./logo.png # embed an image inline in the HTML body
281
+ proton-cli mail messages send --to to@ex.com --subject Hi --body Hi --send-at 2026-05-01T09:00 # local time; prints the message ID + confirms the schedule
282
+ proton-cli mail messages list --folder scheduled # queued scheduled sends
283
+ proton-cli mail messages unschedule REF # cancel a scheduled send (moves it back to Drafts)
284
+ proton-cli mail messages send --to to@ex.com --subject Hi --body Hi --expires 7d
285
+ proton-cli mail messages send --to bob@gmail.com --subject Hi --body secret --eo-password hunter2 # password-protect for non-Proton recipients
286
+ echo "body" | proton-cli mail messages send --to foo --subject bar --body -
287
+ proton-cli mail messages trash REF...
288
+ proton-cli mail messages delete REF... # permanent
289
+ proton-cli mail messages move --dest archive REF...
290
+ proton-cli mail messages mark read|unread REF
291
+ proton-cli mail messages star REF
292
+ proton-cli mail messages unstar REF
293
+
294
+ # Batch filters (union with any explicit REFs)
295
+ proton-cli mail messages trash --unread --older-than 30d
296
+ proton-cli mail messages move --dest archive --from "newsletter@" --older-than 7d
297
+ proton-cli mail messages delete --folder spam --all
298
+ # Combine filters, --limit, --all: proton-cli mail messages trash --help
299
+
300
+ # Conversations (full threads; same verbs as messages)
301
+ proton-cli mail conversations list --folder sent --unread
302
+ proton-cli mail conversations read CONV_ID # full thread, chronological
303
+ proton-cli mail conversations read --summary CONV_ID # one line per message
304
+ proton-cli mail conversations read --strip-quotes CONV_ID
305
+ proton-cli mail conversations attachments list CONV_ID
306
+ proton-cli mail conversations attachments download CONV_ID --all --output-dir ./atts/
307
+
308
+ # Attachments
309
+ proton-cli mail attachments list MESSAGE_ID
310
+ proton-cli mail attachments download MESSAGE_ID ATT_ID --output ./file.pdf
311
+ proton-cli mail attachments download MESSAGE_ID --all --output-dir ./atts/
312
+ proton-cli mail attachments download MESSAGE_ID ATT_ID --output - # stdout
313
+ # --force, --include-inline, auto-suffix on collision: proton-cli mail attachments download --help
314
+
315
+ # Labels and folders
316
+ proton-cli mail labels list
317
+ proton-cli mail labels create --name "Important" --color "#8080FF"
318
+ proton-cli mail labels create --name "Projects" --folder --parent PARENT_LABEL_ID
319
+ proton-cli mail labels update LABEL_ID --name "Renamed" --color "#DB60D6"
320
+ proton-cli mail labels delete LABEL_ID
321
+
322
+ # Filters (Sieve)
323
+ proton-cli mail filters list
324
+ proton-cli mail filters create --name "Archive invoices" \
325
+ --sieve 'require ["fileinto"]; if header :contains "Subject" "invoice" { fileinto "Archive"; }'
326
+ proton-cli mail filters enable|disable FILTER_ID
327
+ proton-cli mail filters delete FILTER_ID
328
+
329
+ # Addresses
330
+ proton-cli mail addresses list
331
+ ```
332
+
333
+ </details>
334
+
335
+ <details>
336
+ <summary><b>Drive</b></summary>
337
+
338
+ ```bash
339
+ # Items
340
+ proton-cli drive items list /Documents
341
+ proton-cli drive items info /Documents/report.pdf # type, size, checksum, sharing
342
+ proton-cli drive items upload ./report.pdf /Documents
343
+ proton-cli drive items upload --recursive ./folder /Backup
344
+ proton-cli drive items upload - /Notes/note.txt # from stdin
345
+ proton-cli drive items download /Documents/report.pdf --output ./report.pdf
346
+ proton-cli drive items download /Documents/report.pdf --output-dir ./out/ # keep original name
347
+ proton-cli drive items download /Photos/pic.jpg --output - # to stdout
348
+ proton-cli drive items rename /Documents/old.txt new.txt
349
+ proton-cli drive items move /Documents/report.pdf /Archive
350
+ proton-cli drive items copy /Documents/report.pdf /Archive
351
+ proton-cli drive items trash /Documents/old.pdf # to trash (reversible)
352
+ proton-cli drive items delete /Documents/secret.txt # permanent
353
+ proton-cli drive items revisions list /Documents/report.pdf
354
+ proton-cli drive items revisions restore /Documents/report.pdf REVISION_ID
355
+
356
+ # Batch filters
357
+ proton-cli drive items trash --pattern "*.tmp" --scope / --recursive
358
+ proton-cli drive items delete --larger-than 100MB --scope /Backups --recursive # permanent
359
+ proton-cli drive items trash --older-than 90d --scope /Logs --recursive
360
+
361
+ # Folders
362
+ proton-cli drive folders create /Documents/NewFolder
363
+
364
+ # Sharing - public links
365
+ proton-cli drive share status /Documents/report.pdf # who has access + public link
366
+ proton-cli drive share link /Documents/report.pdf # create/show the public link
367
+ proton-cli drive share link /Documents/report.pdf --edit --expires 7d --password hunter2
368
+ proton-cli drive share unlink /Documents/report.pdf
369
+
370
+ # Sharing - members (invite Proton users)
371
+ proton-cli drive share add /Documents/report.pdf bob@proton.me --edit
372
+ proton-cli drive share remove /Documents/report.pdf bob@proton.me
373
+
374
+ # Incoming share invitations
375
+ proton-cli drive invitations list
376
+ proton-cli drive invitations accept|reject INVITATION_ID
377
+
378
+ # Trash
379
+ proton-cli drive trash list
380
+ proton-cli drive trash restore LINK_ID...
381
+ proton-cli drive trash empty # across all volumes
382
+
383
+ # Photos
384
+ proton-cli drive photos list
385
+ proton-cli drive photos list --tags favorites # filter by tag (favorites, screenshots, videos, …)
386
+ proton-cli drive photos upload ./IMG_0001.jpg
387
+ proton-cli drive photos download PHOTO_LINK_ID --output-dir ./pics/
388
+ proton-cli drive photos trash PHOTO_LINK_ID... # to trash (reversible)
389
+ proton-cli drive photos delete PHOTO_LINK_ID... # permanent
390
+ proton-cli drive photos favorite PHOTO_LINK_ID... # mark as favorite (album-only photos are copied to your timeline)
391
+ proton-cli drive photos unfavorite PHOTO_LINK_ID... # remove from favorites
392
+ proton-cli drive photos albums list
393
+ proton-cli drive photos albums create --name "Holiday"
394
+ proton-cli drive photos albums add ALBUM_LINK_ID PHOTO_LINK_ID...
395
+ proton-cli drive photos albums items ALBUM_LINK_ID
396
+ ```
397
+
398
+ </details>
399
+
400
+ <details>
401
+ <summary><b>Calendar</b></summary>
402
+
403
+ ```bash
404
+ # Calendars
405
+ proton-cli calendar calendars list
406
+ proton-cli calendar calendars create --name "Work" --color "#8080FF"
407
+ proton-cli calendar calendars rename CALENDAR_ID --name "Personal" --color "#DB60D6"
408
+ proton-cli calendar calendars delete CALENDAR_ID # requires PROTON_PASSWORD
409
+
410
+ # Events
411
+ proton-cli calendar events list --calendar "Work" --start 2026-04-15 --end 2026-04-20
412
+ proton-cli calendar events get CALENDAR_ID EVENT_ID
413
+ proton-cli calendar events get "Meeting" # search by title
414
+ proton-cli calendar events create \
415
+ --title "Meeting" --location "Vienna" --description "Quarterly sync" \
416
+ --start "2026-04-16T14:00" --duration 1h
417
+ proton-cli calendar events create --title "Standup" --start 2026-04-16T09:00 \
418
+ --rrule "FREQ=WEEKLY;COUNT=10" --remind 15m --remind 1h # recurrence + reminders
419
+ proton-cli calendar events create --title "Review" --start 2026-04-16T14:00 \
420
+ --attendee alice@proton.me --attendee bob@example.com # externals get an emailed invite
421
+ proton-cli calendar events update CALENDAR_ID EVENT_ID --title "Updated"
422
+ proton-cli calendar events respond CALENDAR_ID EVENT_ID --status accept # reply to an invitation (accept|tentative|decline)
423
+ proton-cli calendar events respond "Meeting" --status decline # by title; emails the organizer a REPLY
424
+ proton-cli calendar events delete CALENDAR_ID EVENT_ID
425
+ ```
426
+
427
+ </details>
428
+
429
+ <details>
430
+ <summary><b>Contacts</b></summary>
431
+
432
+ ```bash
433
+ proton-cli contacts list
434
+ proton-cli contacts get REF # ID or search
435
+ proton-cli contacts create --name "John Doe" --email john@example.com --phone "+1234567890"
436
+ proton-cli contacts create --name "Jane" --email a@ex.com --email b@ex.com \
437
+ --title "CTO" --birthday 1990-01-01 --address "Vienna" --url https://jane.example
438
+ proton-cli contacts update --email "new@example.com" REF
439
+ proton-cli contacts delete REF
440
+
441
+ # Pinned keys - encrypt mail to a specific PGP key you trust for a contact
442
+ proton-cli contacts pin-key REF --key bob-pubkey.asc # pin & auto-encrypt to it
443
+ proton-cli contacts pin-key REF --email bob@ex.com --key - # armored key from stdin; pick which email
444
+ proton-cli contacts unpin-key REF
445
+
446
+ # Contact groups
447
+ proton-cli contacts groups list
448
+ proton-cli contacts groups create --name "Team" --color "#8080FF"
449
+ proton-cli contacts groups add GROUP_ID REF...
450
+ proton-cli contacts groups remove GROUP_ID REF...
451
+ proton-cli contacts groups delete GROUP_ID
452
+ ```
453
+
454
+ </details>
455
+
456
+ <details>
457
+ <summary><b>Pass</b></summary>
458
+
459
+ ```bash
460
+ # Items
461
+ proton-cli pass items list --vault "Work"
462
+ proton-cli pass items get SHARE_ID ITEM_ID
463
+ proton-cli pass items get "github.com" # search
464
+ proton-cli pass items create --type login --name "GitHub" --username me --password secret \
465
+ --url github.com --totp "otpauth://..."
466
+ proton-cli pass items create --type note --name "My Note" --note "Some text"
467
+ proton-cli pass items create --type credit-card --name "Visa" --holder "Roman" --number "4111..." --expiry "2028-12"
468
+ proton-cli pass items create --type wifi --name "Home" --ssid MyNet --password pw --security WPA2
469
+ proton-cli pass items create --type ssh-key --name "laptop" --public-key "ssh-ed25519 ..." \
470
+ --private-key "$(cat id_ed25519)"
471
+ proton-cli pass items create --type identity --name "Me" --full-name "Jane Roe" --email jane@ex.com
472
+ proton-cli pass items create --type custom --name "Server" --field "Host=1.2.3.4" --hidden "Root PW=secret"
473
+ proton-cli pass items create --type login --name X --field "Recovery=abc" # custom fields on any type
474
+ proton-cli pass items edit REF --password "new-secret"
475
+ proton-cli pass items trash REF
476
+ proton-cli pass items restore REF
477
+ proton-cli pass items delete REF
478
+
479
+ # Batch filters
480
+ proton-cli pass items trash --vault "Old" --type login
481
+ proton-cli pass items trash --older-than 1y --type login
482
+ proton-cli pass items delete --vault "Temporary" --all
483
+
484
+ # Vaults
485
+ proton-cli pass vaults list
486
+ proton-cli pass vaults create --name "Work"
487
+ proton-cli pass vaults rename SHARE_ID --name "Personal"
488
+ proton-cli pass vaults delete SHARE_ID
489
+
490
+ # Aliases
491
+ proton-cli pass alias options
492
+ proton-cli pass alias create --prefix my-alias --mailbox my-mailbox@proton.me
493
+ ```
494
+
495
+ </details>
496
+
497
+ <details>
498
+ <summary><b>Settings</b></summary>
499
+
500
+ ```bash
501
+ proton-cli settings get # account settings
502
+ proton-cli settings mail # mail settings
503
+ proton-cli settings set # list the writable mail-setting keys
504
+ proton-cli settings set view-mode 1 # 0=conversations, 1=messages
505
+ proton-cli settings set draft-type text/html
506
+ proton-cli settings set hide-remote-images 1
507
+ ```
508
+
509
+ </details>
510
+
511
+ <details>
512
+ <summary><b>Raw API</b></summary>
513
+
514
+ For any endpoint not covered by a high-level command:
515
+
516
+ ```bash
517
+ proton-cli api GET /drive/volumes
518
+ proton-cli api POST /calendar/v1 --body '{"Name":"Work",...}'
519
+ proton-cli api GET /mail/v4/messages --query Page=0 --query PageSize=10
520
+ proton-cli api GET /calendar/v1 --output json | jq '.Calendars[].ID'
521
+ ```
522
+
523
+ See [API reference](#api-reference) for the full endpoint spec.
524
+
525
+ </details>
526
+
527
+ ## Scripting
528
+
529
+ proton-cli is built to compose in shell pipelines.
530
+
531
+ ```bash
532
+ # stdout is the new ID on create - capture it directly
533
+ LABEL=$(proton-cli mail labels create --name Work --color "#8080FF")
534
+
535
+ # JSON + jq for full IDs and fields
536
+ proton-cli mail messages list --output json | jq -r '.messages[].id'
537
+
538
+ # exit codes drive control flow (0 ok · 3 not-found · 4 ambiguous)
539
+ if ! proton-cli contacts get "jane"; then
540
+ echo "no unique match (exit $?)"
541
+ fi
542
+
543
+ # streaming: pipe a Drive file straight into another tool
544
+ proton-cli drive items download /report.pdf --output - | gpg --encrypt --recipient me ...
545
+
546
+ # preview any mutation first
547
+ proton-cli mail messages trash --unread --older-than 30d --dry-run
548
+ ```
549
+
550
+ ## How it works
551
+
552
+ 1. **Session creation** - creates an unauthenticated session via `POST /auth/v4/sessions`.
553
+ 2. **SRP authentication** - Secure Remote Password login with [go-srp](https://github.com/ProtonMail/go-srp), with 2FA/TOTP support.
554
+ 3. **Session persistence** - per profile, saves the auth tokens plus the salted key password **encrypted** with a random client key held server-side. The key password is never written to disk in cleartext, and revoking the session makes the saved blob undecryptable. See [Security](#security).
555
+ 4. **Key hierarchy** - unlocks User key → Address keys → per-service keys (Calendar, Drive, Contacts).
556
+ 5. **End-to-end encryption** - encrypts/decrypts using [gopenpgp](https://github.com/ProtonMail/gopenpgp).
557
+ 6. **Auto-refresh** - refreshes expired tokens automatically.
558
+
559
+ ### Encryption details
560
+
561
+ | Service | Encrypt with | Sign with |
562
+ |---|---|---|
563
+ | Calendar events | Calendar key (session key) | Address key |
564
+ | Drive files | Node key (session key per block) | Address key |
565
+ | Drive names | Parent node key | Address key |
566
+ | Contacts | User key | User key |
567
+ | Mail | Session key | Address key |
568
+ | Pass items | AES-256-GCM (item key) | N/A (symmetric) |
569
+ | Pass vaults | AES-256-GCM (vault key) | N/A (symmetric) |
570
+
571
+ ## Human verification (CAPTCHA)
572
+
573
+ Proton's anti-bot may demand a CAPTCHA at login. proton-cli opens a small webview window via an embedded helper, you solve it, and the original command retries automatically. No extra install is needed - the helper is `//go:embed`-ded into release binaries.
574
+
575
+ - **Linux desktop** needs `libwebkit2gtk-4.1` and `libgtk-3` installed.
576
+ - **macOS / Windows** need nothing (system WebKit / WebView2).
577
+ - **Headless** environments (server, container, no GUI) can't display the webview, so proton-cli exits with an error - run the command on a desktop machine instead.
578
+ - **`go install` builds** don't embed the helper. Install a [release binary](#install) if you hit a CAPTCHA.
579
+
580
+ ## Security
581
+
582
+ proton-cli saves a per-profile session file at `~/.config/proton-cli/sessions/<profile>.json` (mode `0600`). The salted key password that unlocks your PGP keys is stored **encrypted** with a random 256-bit client key that lives server-side, so:
583
+
584
+ - the key password is never written to disk in cleartext, and
585
+ - revoking the session (from the Proton apps) makes a leaked copy of the file undecryptable.
586
+
587
+ The file still contains the session refresh token, so treat it as a secret. proton-cli is unaudited; see [`SECURITY.md`](SECURITY.md) for the full storage model, the vulnerability-reporting process, and hardening recommendations.
588
+
589
+ ## Limitations
590
+
591
+ A few constraints are inherent to Proton's design or platform:
592
+
593
+ - **Colors** - labels, folders, calendars, and contact groups accept only Proton's 20 fixed accent colors; the CLI validates `--color` and lists the allowed values on error.
594
+ - **Calendar deletion** - `calendar calendars delete` is password-scoped and needs `PROTON_PASSWORD`.
595
+ - **Search lag** - `search` and `list` read Proton's eventually-consistent server-side index; a just-sent or just-deleted message can take a few seconds to appear or disappear. Confirm a mutation by ID with `read` rather than re-searching a subject.
596
+ - **CAPTCHA** - can't be solved headlessly, and `go install` builds don't embed the helper (see [Human verification](#human-verification-captcha)).
597
+
598
+ See [`docs/limitations.md`](docs/limitations.md) for the full list, including features not yet implemented.
599
+
600
+ ## Development
601
+
602
+ Requires [devbox](https://www.jetify.com/devbox) and [direnv](https://direnv.net/):
603
+
604
+ ```bash
605
+ direnv allow
606
+ go build . # quick build
607
+ just lint # gofmt + golangci-lint
608
+ just build # release-shaped binary (embeds the CAPTCHA helper)
609
+ ```
610
+
611
+ Tests are integration tests that run against the live Proton API and require `PROTON_USER` / `PROTON_PASSWORD`.
612
+
613
+ ## API reference
614
+
615
+ See [`openapi.yaml`](openapi.yaml) for the complete API spec covering ~740 endpoints. To regenerate from the latest Proton source:
616
+
617
+ ```bash
618
+ cd scripts && npm install && npm run generate-openapi
619
+ ```
620
+
621
+ See [`scripts/README.md`](scripts/README.md) for details on the generator.
622
+
623
+ ## License
624
+
625
+ MIT
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const { spawnSync } = require("node:child_process");
4
+ const PKGS = {
5
+ "linux-x64": "@roman-16/proton-cli-linux-x64",
6
+ "linux-arm64": "@roman-16/proton-cli-linux-arm64",
7
+ "darwin-x64": "@roman-16/proton-cli-darwin-x64",
8
+ "darwin-arm64": "@roman-16/proton-cli-darwin-arm64",
9
+ "win32-x64": "@roman-16/proton-cli-win32-x64",
10
+ };
11
+ const key = process.platform + "-" + process.arch;
12
+ const pkg = PKGS[key];
13
+ if (!pkg) {
14
+ console.error("proton-cli: no prebuilt binary for " + key + ". See https://github.com/roman-16/proton-cli");
15
+ process.exit(1);
16
+ }
17
+ const exe = process.platform === "win32" ? "proton-cli.exe" : "proton-cli";
18
+ let binPath;
19
+ try {
20
+ binPath = require.resolve(pkg + "/bin/" + exe);
21
+ } catch {
22
+ console.error("proton-cli: platform package " + pkg + " is not installed. See https://github.com/roman-16/proton-cli");
23
+ process.exit(1);
24
+ }
25
+ const res = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
26
+ if (res.error) throw res.error;
27
+ process.exit(res.status === null ? 1 : res.status);
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@roman-16/proton-cli",
3
+ "version": "1.9.7",
4
+ "description": "An unofficial command-line tool for Proton - Mail, Drive, Calendar, Pass, and Contacts from your terminal, with real end-to-end encryption.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/roman-16/proton-cli",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/roman-16/proton-cli.git"
10
+ },
11
+ "bin": {
12
+ "proton-cli": "bin/proton-cli.js"
13
+ },
14
+ "files": [
15
+ "bin"
16
+ ],
17
+ "optionalDependencies": {
18
+ "@roman-16/proton-cli-linux-x64": "1.9.7",
19
+ "@roman-16/proton-cli-linux-arm64": "1.9.7",
20
+ "@roman-16/proton-cli-darwin-x64": "1.9.7",
21
+ "@roman-16/proton-cli-darwin-arm64": "1.9.7",
22
+ "@roman-16/proton-cli-win32-x64": "1.9.7"
23
+ }
24
+ }