confluence-space-backup-restore 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. confluence_space_backup_restore-1.0.0/LICENSE +21 -0
  2. confluence_space_backup_restore-1.0.0/PKG-INFO +350 -0
  3. confluence_space_backup_restore-1.0.0/README.md +321 -0
  4. confluence_space_backup_restore-1.0.0/confluence_space_backup_restore.egg-info/PKG-INFO +350 -0
  5. confluence_space_backup_restore-1.0.0/confluence_space_backup_restore.egg-info/SOURCES.txt +23 -0
  6. confluence_space_backup_restore-1.0.0/confluence_space_backup_restore.egg-info/dependency_links.txt +1 -0
  7. confluence_space_backup_restore-1.0.0/confluence_space_backup_restore.egg-info/entry_points.txt +2 -0
  8. confluence_space_backup_restore-1.0.0/confluence_space_backup_restore.egg-info/requires.txt +5 -0
  9. confluence_space_backup_restore-1.0.0/confluence_space_backup_restore.egg-info/top_level.txt +1 -0
  10. confluence_space_backup_restore-1.0.0/confluence_tool/__init__.py +5 -0
  11. confluence_space_backup_restore-1.0.0/confluence_tool/api_client.py +397 -0
  12. confluence_space_backup_restore-1.0.0/confluence_tool/auth.py +69 -0
  13. confluence_space_backup_restore-1.0.0/confluence_tool/backup.py +448 -0
  14. confluence_space_backup_restore-1.0.0/confluence_tool/cli.py +148 -0
  15. confluence_space_backup_restore-1.0.0/confluence_tool/config.py +163 -0
  16. confluence_space_backup_restore-1.0.0/confluence_tool/export.py +158 -0
  17. confluence_space_backup_restore-1.0.0/confluence_tool/macros.py +136 -0
  18. confluence_space_backup_restore-1.0.0/confluence_tool/manifest.py +168 -0
  19. confluence_space_backup_restore-1.0.0/confluence_tool/menu.py +311 -0
  20. confluence_space_backup_restore-1.0.0/confluence_tool/native_export.py +157 -0
  21. confluence_space_backup_restore-1.0.0/confluence_tool/progress.py +139 -0
  22. confluence_space_backup_restore-1.0.0/confluence_tool/restore.py +678 -0
  23. confluence_space_backup_restore-1.0.0/confluence_tool/utils.py +311 -0
  24. confluence_space_backup_restore-1.0.0/pyproject.toml +50 -0
  25. confluence_space_backup_restore-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Malko
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.
@@ -0,0 +1,350 @@
1
+ Metadata-Version: 2.4
2
+ Name: confluence-space-backup-restore
3
+ Version: 1.0.0
4
+ Summary: Backup and restore individual Confluence Cloud spaces via REST API
5
+ Author-email: David Malko <davidmalko87@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/davidmalko87/confluence-space-backup-restore
8
+ Project-URL: Repository, https://github.com/davidmalko87/confluence-space-backup-restore
9
+ Project-URL: Changelog, https://github.com/davidmalko87/confluence-space-backup-restore/blob/master/CHANGELOG.md
10
+ Project-URL: Bug Tracker, https://github.com/davidmalko87/confluence-space-backup-restore/issues
11
+ Keywords: confluence,backup,restore,atlassian,confluence-cloud,atlassian-cloud
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: System :: Archiving :: Backup
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: requests>=2.28
25
+ Requires-Dist: python-dotenv>=1.0
26
+ Provides-Extra: ui
27
+ Requires-Dist: rich>=13.0; extra == "ui"
28
+ Dynamic: license-file
29
+
30
+ # Confluence Space Backup & Restore
31
+
32
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg?logo=python&logoColor=white)](https://www.python.org/downloads/)
33
+ [![Confluence Cloud](https://img.shields.io/badge/Confluence-Cloud-0052CC.svg?logo=confluence&logoColor=white)](https://www.atlassian.com/software/confluence)
34
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
35
+ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](#)
36
+ [![Round-trip](https://img.shields.io/badge/round--trip-verified-success.svg)](#-round-trip-verified)
37
+ [![Last commit](https://img.shields.io/github/last-commit/davidmalko87/confluence-space-backup-restore.svg)](https://github.com/davidmalko87/confluence-space-backup-restore/commits/main)
38
+ [![GitHub issues](https://img.shields.io/github/issues/davidmalko87/confluence-space-backup-restore.svg)](https://github.com/davidmalko87/confluence-space-backup-restore/issues)
39
+
40
+ Backup and restore individual **Confluence Cloud spaces** via REST API β€” pages, hierarchy, attachments, comments, labels, properties, restrictions, and blog posts. Fully resumable, with an interactive menu and CLI mode. The Confluence sibling of [jira-project-backup-restore](https://github.com/davidmalko87/jira-project-backup-restore).
41
+
42
+ > ⚠️ **A REST restore is content-faithful, not forensic.** It rebuilds pages, hierarchy, attachments, comments and labels β€” but **cannot** restore original authors, timestamps, or version history (Confluence Cloud has no API to set them). Read [Known Limitations](#-known-limitations) before relying on this for disaster recovery.
43
+
44
+ ---
45
+
46
+ ## πŸ€” Why?
47
+
48
+ Confluence Cloud has **no supported public API** for native space export or import ([CONFCLOUD-40457](https://jira.atlassian.com/browse/CONFCLOUD-40457), open for years):
49
+
50
+ - **Native import is UI-only** (Settings β†’ Data management β†’ Import spaces, site-admin) and **cannot overwrite** an existing space key β€” there's no way to automate or verify it end-to-end.
51
+ - **Native export** is reachable only through *undocumented* `.action` endpoints that Atlassian can change at any time.
52
+
53
+ So an **automated, verifiable** backup *and restore* must be built on the REST API. That's the backbone here. A native XML export is offered as an *optional, best-effort, off-by-default* high-fidelity artifact for manual import β€” never the primary guarantee.
54
+
55
+ ---
56
+
57
+ ## ✨ Features
58
+
59
+ | Feature | Description |
60
+ |---|---|
61
+ | **Full space backup** | Pages (storage format) + hierarchy, blog posts, attachments, footer/inline comments, labels, content & space properties, restrictions, permissions |
62
+ | **9-phase restore** | Space β†’ pages (parent-first) β†’ blog posts β†’ macro/ID remap β†’ attachments β†’ comments β†’ labels β†’ properties β†’ restrictions |
63
+ | **Two-pass ID remap** | Rewrites `ri:content-id` references after new page IDs are minted, so include/excerpt/pagetree macros don't break |
64
+ | **New-space default** | Restore creates a **new** space; never clobbers a live space without `--overwrite` + typed confirmation |
65
+ | **Homepage adoption** | Reuses the space's auto-created homepage instead of leaving a duplicate |
66
+ | **Multi-space** | Back up several spaces in a single run |
67
+ | **Resumable** | Re-run after interruption β€” completed phases and items are skipped; a phase completes only when fail count is 0 |
68
+ | **Memory efficient** | Pages/comments/attachments stream to disk β€” large spaces won't OOM a small host |
69
+ | **Dry-run mode** | Preview every restore action without making changes |
70
+ | **Rate-limit aware** | Exponential backoff with `429 / Retry-After` detection |
71
+ | **CSV export** | Export space content to CSV for reporting and sharing |
72
+ | **Backup inspection** | Content-type breakdown, page-status counts, disk size |
73
+ | **Integrity validation** | sha256 manifest verification of every backed-up file |
74
+ | **Connection test** | Pre-flight: authentication + space listing |
75
+ | **Interactive menu + CLI** | Guided workflow, or `--backup` / `--restore` / `--export-csv` flags for scripts and cron |
76
+ | **Native XML export** | Optional best-effort high-fidelity ZIP for manual UI import (`--native-export`) |
77
+
78
+ ---
79
+
80
+ ## πŸš€ Quick Start
81
+
82
+ ### 1. Install
83
+
84
+ **Clone for development:**
85
+
86
+ ```bash
87
+ git clone https://github.com/davidmalko87/confluence-space-backup-restore.git
88
+ cd confluence-space-backup-restore
89
+ pip install -r requirements.txt
90
+ python main.py
91
+ ```
92
+
93
+ **Or install as a package** (provides the `confluence-backup` command):
94
+
95
+ ```bash
96
+ pip install .
97
+ confluence-backup
98
+ ```
99
+
100
+ ### 2. Configure
101
+
102
+ ```bash
103
+ cp .env.example .env
104
+ ```
105
+
106
+ Edit `.env` with your Confluence Cloud credentials:
107
+
108
+ ```ini
109
+ CONFLUENCE_URL=https://your-domain.atlassian.net/wiki # must include /wiki
110
+ CONFLUENCE_EMAIL=you@example.com
111
+ CONFLUENCE_API_TOKEN=your-api-token
112
+ ```
113
+
114
+ > Generate an API token at [id.atlassian.com/manage-api-tokens](https://id.atlassian.com/manage-api-tokens). Auth is **Basic (email + token)** β€” no session-cookie refresh toil.
115
+
116
+ ### 3. Run
117
+
118
+ **Interactive menu:**
119
+
120
+ ```bash
121
+ python main.py
122
+ ```
123
+
124
+ ```
125
+ ==============================================================
126
+ Confluence Space Backup & Restore v1.0.0
127
+ ==============================================================
128
+ Site: https://your-domain.atlassian.net Auth: API token Backups: ./backups
129
+ --------------------------------------------------------------
130
+ --- Backup & Restore ---
131
+ 1) Backup space(s)
132
+ 2) Restore space from backup
133
+ --- Browse & Analyze ---
134
+ 3) List existing backups
135
+ 4) Validate backup integrity
136
+ 5) Export backup to CSV
137
+ 6) Inspect backup details
138
+ --- Settings & Tools ---
139
+ 7) Test Confluence connection
140
+ 8) Show current configuration
141
+ 9) Cleanup incomplete backups
142
+ 0) Exit
143
+ ```
144
+
145
+ **CLI β€” backup:**
146
+
147
+ ```bash
148
+ python main.py --backup DOCS
149
+ python main.py --backup DOCS,TEAM
150
+ python main.py --backup DOCS --native-export
151
+ ```
152
+
153
+ **CLI β€” restore:**
154
+
155
+ ```bash
156
+ python main.py --restore backups/DOCS_20260602_091819 --target-key DOCSR --dry-run
157
+ python main.py --restore backups/DOCS_20260602_091819 --target-key DOCSR
158
+ ```
159
+
160
+ **CLI β€” inspect & export:**
161
+
162
+ ```bash
163
+ python main.py --list
164
+ python main.py --validate backups/DOCS_20260602_091819
165
+ python main.py --export-csv backups/DOCS_20260602_091819
166
+ ```
167
+
168
+ Exit codes: `0` success Β· `1` failure Β· `2` bad/insufficient arguments.
169
+
170
+ ---
171
+
172
+ ## πŸ“¦ What Gets Backed Up
173
+
174
+ | File | Contents |
175
+ |---|---|
176
+ | `space.json` | Space metadata + description |
177
+ | `pages.json` | Pages with storage-format body (streamed) |
178
+ | `blogposts.json` | Blog posts (streamed) |
179
+ | `attachments.json` | Attachment metadata index (streamed) |
180
+ | `attachments/<id>/` | Attachment binary files, streamed to disk |
181
+ | `comments/footer.json` | Footer comments |
182
+ | `comments/inline.json` | Inline comments (metadata; see limitations) |
183
+ | `labels.json` | Page, blog, and space labels |
184
+ | `properties/*.json` | Content properties + space properties |
185
+ | `restrictions.json` | Per-page restrictions (v1) |
186
+ | `permissions.json` | Space permissions |
187
+ | `versions/<pageId>.json` | Optional page version-metadata sidecar |
188
+ | `native/<KEY>_native.xml.zip` | Optional native XML export |
189
+ | `manifest.json` | File index + sha256 + `"complete": true` β€” presence marks the backup complete |
190
+
191
+ ---
192
+
193
+ ## πŸ”„ Restore Phases
194
+
195
+ Each phase is resumable via `restore_progress.json`, and is marked complete only when it finishes with zero failures:
196
+
197
+ | # | Phase | What happens | Endpoint |
198
+ |---|---|---|---|
199
+ | 1 | Space | Create the target space (new key by default) | `POST /rest/api/space` |
200
+ | 2 | Pages | Create parent-before-child; record old→new ID map | `POST /wiki/api/v2/pages` |
201
+ | 3 | Blog posts | Create flat blog posts | `POST /wiki/api/v2/blogposts` |
202
+ | 4 | Remap | Rewrite `ri:content-id` macro/link references | `PUT /wiki/api/v2/pages` |
203
+ | 5 | Attachments | Upload binaries (idempotent PUT) | `PUT /rest/api/content/{id}/child/attachment` |
204
+ | 6 | Comments | Footer comments; author/date prepended as text | `POST /wiki/api/v2/footer-comments` |
205
+ | 7 | Labels | Re-apply page/blog labels | `POST /rest/api/content/{id}/label` |
206
+ | 8 | Properties | Recreate content & space properties | `POST /wiki/api/v2/{type}/{id}/properties` |
207
+ | 9 | Restrictions | Re-apply page restrictions (best-effort) | `PUT /rest/api/content/{id}/restriction` |
208
+
209
+ Old→new content-ID mapping is saved in `id_maps.json` inside the backup directory.
210
+
211
+ ---
212
+
213
+ ## ⚠️ Known Limitations
214
+
215
+ These are **Confluence Cloud REST API constraints β€” not tool bugs**. The tool preserves everything it can and records the rest.
216
+
217
+ | Data | Status | Notes / degrades to |
218
+ |---|---|---|
219
+ | Page bodies (storage format) | βœ… Restored | round-trippable |
220
+ | Page hierarchy (parent/child) | βœ… Restored | rebuilt via `parentId`, parent-before-child |
221
+ | Blog posts | βœ… Restored | flat |
222
+ | Attachments (latest version) | βœ… Restored | v1 content download/upload; original filename kept |
223
+ | Footer comments | βœ… Restored | original author/date added as a footer note |
224
+ | Labels (page/blog) | βœ… Restored | v1 |
225
+ | Page restrictions | ⚠️ Best-effort | identities must resolve in the target tenant |
226
+ | Content / space properties | ⚠️ Best-effort | system-managed properties may reject writes |
227
+ | Inline comments | ❌ Backup only | text re-anchoring is unreliable via API; kept in backup |
228
+ | Space labels | ❌ Not restored | no API to set space-level labels |
229
+ | Space permissions | ❌ Manual | cross-tenant identity remap; saved for review |
230
+ | **Original author / creator** | ❌ Not settable | becomes the API user; original β†’ footer note **+** `original_provenance` property |
231
+ | **Original created / updated dates** | ❌ Not settable | become the restore run time |
232
+ | **Version history** | ❌ Not replayed | optional metadata sidecar only |
233
+ | **Page / content IDs** | ♻️ Reassigned | new IDs minted; oldβ†’new map kept |
234
+ | ID-referencing macros (`include`, `excerpt-include`, ID-rooted `children`/`pagetree`) | ⚠️ Remapped | `ri:content-id` rewritten in a 2nd pass; unmapped refs break β€” title+spaceKey refs survive natively |
235
+
236
+ ---
237
+
238
+ ## πŸ›‘οΈ Restore Safety
239
+
240
+ - **Default: a NEW space is created.** The tool refuses to modify an existing space key.
241
+ - **Touching an existing space requires `--overwrite` *and* typing the space key to confirm** (the menu always prompts; non-interactive CLI honors the flag). Even then, restore is **additive** β€” it never deletes content.
242
+ - **Dry-run** (`--dry-run`) prints the full plan and writes nothing.
243
+ - A **trashed (not-yet-purged) space key** is detected β€” restore stops and tells you to purge it (Settings β†’ Data Management β†’ Trashed Spaces) or pick another key.
244
+
245
+ ---
246
+
247
+ ## πŸ—œοΈ Native XML Export (optional, off by default)
248
+
249
+ With `NATIVE_EXPORT=true` / `--native-export`, each backup *also* attempts a native XML space export and stores the ZIP under `native/`. This is a high-fidelity DR artifact (preserves history/authors/timestamps) that you import **manually** via the Confluence UI ("Import a space").
250
+
251
+ > ⚠️ It drives **undocumented** endpoints that Atlassian can change without notice. It is best-effort (failure is logged, never fails the REST backup) and **unverified in this build** β€” confirm it works on a non-prod site before relying on it.
252
+
253
+ ---
254
+
255
+ ## πŸ”’ Data Handling & Security
256
+
257
+ - **Backups are stored UNENCRYPTED** β€” plain JSON plus attachment binaries (and, if enabled, a native XML ZIP). They contain **real space content**; securing/encrypting the backup directory is **your responsibility**.
258
+ - Gitignored by default β€” never commit: `backups/`, `*.log`, `csv_export/`, native `*.zip`/`*.xml`, and `.env`.
259
+ - **Logs can leak content**: the DEBUG file log records truncated API response bodies (page text). Treat log files as sensitive.
260
+ - Credentials live only in `.env` (gitignored). No site, space, email, or token is ever hardcoded.
261
+
262
+ ---
263
+
264
+ ## βœ… Round-trip Verified
265
+
266
+ The REST backup→restore round-trip has been **proven end-to-end against a live Confluence Cloud site**: a space was backed up, restored into a fresh space, and diffed via the API — **page count, hierarchy, and attachment bytes all matched**. A backup is only proven once it has been restored end-to-end and verified; structural checks alone are necessary but not sufficient.
267
+
268
+ To prove it yourself on a non-prod site: `--backup SOURCE` β†’ `--restore <dir> --target-key SCRATCH --dry-run` β†’ `--restore <dir> --target-key SCRATCH`, then compare page count + hierarchy, bodies, attachment count + sizes, comments, and labels.
269
+
270
+ ---
271
+
272
+ ## πŸ—‚οΈ Project Structure
273
+
274
+ ```
275
+ confluence-space-backup-restore/
276
+ β”œβ”€β”€ main.py # Entry point β€” interactive menu + CLI flags
277
+ β”œβ”€β”€ .env.example # Configuration template
278
+ β”œβ”€β”€ requirements.txt # Python dependencies
279
+ β”‚
280
+ β”œβ”€β”€ confluence_tool/
281
+ β”‚ β”œβ”€β”€ config.py # .env loader and validation
282
+ β”‚ β”œβ”€β”€ auth.py # Session builder (API token / cookie auth)
283
+ β”‚ β”œβ”€β”€ api_client.py # HTTP client with retry + rate-limit handling
284
+ β”‚ β”œβ”€β”€ backup.py # BackupManager β€” per-space backup
285
+ β”‚ β”œβ”€β”€ restore.py # RestoreManager β€” 9-phase restore
286
+ β”‚ β”œβ”€β”€ macros.py # Storage-format content-ID remapper
287
+ β”‚ β”œβ”€β”€ native_export.py # Optional native XML export (best-effort)
288
+ β”‚ β”œβ”€β”€ manifest.py # Manifest build/validate (sha256 + complete flag)
289
+ β”‚ β”œβ”€β”€ progress.py # Resumability tracker (oldβ†’new ID maps, phases)
290
+ β”‚ β”œβ”€β”€ export.py # CSV export and backup statistics
291
+ β”‚ β”œβ”€β”€ menu.py # Interactive CLI menu
292
+ β”‚ β”œβ”€β”€ cli.py # Console-script entry point
293
+ β”‚ └── utils.py # Logging, JSON streaming I/O, utilities
294
+ β”‚
295
+ └── backups/ # Backup output directory (gitignored)
296
+ └── DOCS_20260602_091819/
297
+ β”œβ”€β”€ manifest.json # Completion marker + file index
298
+ β”œβ”€β”€ pages.json
299
+ β”œβ”€β”€ attachments/
300
+ └── ...
301
+ ```
302
+
303
+ ---
304
+
305
+ ## βš™οΈ Configuration Reference
306
+
307
+ All settings live in `.env` (copy from `.env.example`):
308
+
309
+ | Variable | Required | Default | Description |
310
+ |---|---|---|---|
311
+ | `CONFLUENCE_URL` | Yes | β€” | Cloud base URL β€” **must include `/wiki`** |
312
+ | `CONFLUENCE_EMAIL` | Yes* | β€” | Account email for API token auth |
313
+ | `CONFLUENCE_API_TOKEN` | Yes* | β€” | API token β€” [generate here](https://id.atlassian.com/manage-api-tokens) |
314
+ | `CONFLUENCE_COOKIE_HEADER` | Alt* | β€” | Full `Cookie:` header value for SSO auth |
315
+ | `CONFLUENCE_VERIFY_SSL` | No | `true` | Set `false` to skip SSL verification |
316
+ | `BACKUP_ROOT` | No | `./backups` | Directory where backups are written |
317
+ | `PAGE_SIZE` | No | `250` | Items per API page (Cloud v2 max 250) |
318
+ | `MAX_RETRIES` | No | `5` | Retry count on transient failures |
319
+ | `READ_TIMEOUT` | No | `30` | HTTP read timeout in seconds |
320
+ | `API_DELAY` | No | `0.2` | Seconds to wait between API calls |
321
+ | `CHUNK_SIZE` | No | `8388608` | Bytes per chunk for streaming downloads |
322
+ | `BODY_FORMAT` | No | `storage` | `storage` (recommended) or `atlas_doc_format` |
323
+ | `INCLUDE_ATTACHMENTS` | No | `true` | Download attachment binary files |
324
+ | `INCLUDE_COMMENTS` | No | `true` | Back up footer + inline comments |
325
+ | `INCLUDE_BLOGPOSTS` | No | `true` | Back up blog posts |
326
+ | `INCLUDE_RESTRICTIONS` | No | `true` | Back up per-page restrictions |
327
+ | `INCLUDE_VERSIONS` | No | `false` | Save version-metadata sidecar (reference only) |
328
+ | `NATIVE_EXPORT` | No | `false` | Also attempt a native XML export (best-effort) |
329
+ | `NATIVE_EXPORT_TIMEOUT` | No | `1800` | Max seconds to wait for a native export |
330
+
331
+ > \* Either `CONFLUENCE_EMAIL` + `CONFLUENCE_API_TOKEN` **or** `CONFLUENCE_COOKIE_HEADER` is required.
332
+
333
+ ---
334
+
335
+ ## 🐍 Requirements
336
+
337
+ - Python **3.10+** (tested on 3.10–3.13)
338
+ - [`requests`](https://pypi.org/project/requests/) >= 2.28
339
+ - [`python-dotenv`](https://pypi.org/project/python-dotenv/) >= 1.0
340
+ - Optional: [`rich`](https://pypi.org/project/rich/) for colored output (`pip install .[ui]`)
341
+
342
+ ---
343
+
344
+ ## πŸ“ Changelog
345
+
346
+ See [CHANGELOG.md](CHANGELOG.md) for the full version history.
347
+
348
+ ## πŸ“„ License
349
+
350
+ [MIT](LICENSE)