Metadata-Version: 2.4
Name: refaie
Version: 1.0.0
Summary: Local content encryption + key sync CLI for Refaie Academy (MMR v1 container, AES-256-GCM).
Author: Refaie Academy
License: MIT
Project-URL: Homepage, https://github.com/refaieacademy/refaie-cli
Project-URL: Issues, https://github.com/refaieacademy/refaie-cli/issues
Keywords: encryption,aes-gcm,cli,mmr,content-protection,refaie
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Multimedia
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=41.0
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# refaie

Local content encryption + key-sync CLI for Refaie Academy.

`refaie` runs entirely on the admin's own device (a phone via Termux, or any
machine with Python 3.8+). It needs **nothing** from the server except an
admin bearer token — no `master_key`, no database copy, no server secrets of
any kind.

## What it does

1. Reads a plaintext video / PDF / image / audio file.
2. Generates a brand-new random AES-256 key and two AES-GCM nonces,
   **locally**.
3. Encrypts the file into the exact **MMR v1** binary container format that
   the Android app's `CryptoEngine.kt` (`parseMmrHeader()` /
   `decryptHeader()` / `decryptPayload()`) already knows how to decrypt.
4. Runs a **self-check**: re-parses and decrypts the container it just built
   and compares it byte-for-byte against the original plaintext, *before*
   any network call is made.
5. Writes the encrypted `.mmr` file to an output folder (upload this file to
   Telegram / wherever you distribute content to students).
6. Calls the real, already-existing backend endpoint
   `POST /api/admin/file-key/register` over HTTPS to register the key +
   nonces + metadata against the production database.
7. If the server call fails for any reason (offline, timeout, non-2xx
   response), the key material is written to a `<key_id>.pending.json`
   sidecar next to the `.mmr` file so it is **never lost** — retry later
   with `refaie retry-pending`.

Nothing here is a guess: the container layout matches
`CryptoEngine.parseMmrHeader()` / `decryptHeader()` / `decryptPayload()`
exactly, and the API call matches the backend's
`app/api/admin_file_key.py` + `key_upload_service.py`.

## Package layout

```text
refaie/
├── pyproject.toml
├── README.md
├── LICENSE
├── MANIFEST.in
├── .gitignore
├── src/
│   └── refaie/
│       ├── __init__.py     # package version
│       ├── cli.py          # argparse entrypoint: `refaie encrypt`, `refaie retry-pending`
│       ├── crypto.py        # MMR v1 packing/unpacking, encryption, self-check
│       ├── client.py        # backend registration call, pending-sidecar write/retry
│       └── storage.py       # EncryptZ folder scaffolding, file scan, selection parsing, MIME maps
└── tests/
    ├── test_crypto.py       # offline round-trip / tamper-detection tests
    └── test_storage.py      # selection-parser / extension-map tests
```

| Module | Responsibility |
|---|---|
| `refaie.crypto` | The MMR v1 binary container format: AES-256-GCM encryption, header/payload packing, and the pre-upload self-check decrypt. This is the module that must stay byte-for-byte compatible with `CryptoEngine.kt`. |
| `refaie.client` | All network I/O: `POST /api/admin/file-key/register`, plus the offline `.pending.json` sidecar fallback and `retry-pending` logic. |
| `refaie.storage` | The interactive "EncryptZ" folder mode: creating `input/{videos,audios,pdfs,images}` + `output`, scanning for files, parsing selections like `1,3,5` or `1-4` or `all`, and extension → kind/MIME lookup tables. |
| `refaie.cli` | The `refaie` console entrypoint. Wires the three modules together behind two subcommands, `encrypt` and `retry-pending`. |

All cryptographic logic, the MMR v1 container layout, the self-check
verification, the backend API payload shape, and the pending-sidecar
write/retry mechanism are carried over from the original script
**unchanged** — this refactor only reorganizes the code into a proper
installable package with a `refaie` console command.

## Installation

### From PyPI (once published)

```bash
pip install refaie
```

### On Termux (Android)

`cryptography` needs a Rust toolchain to build from source on ARM/Termux.
This is a one-time setup step:

```bash
pkg install python rust binutils
pip install refaie
```

## Usage

### `refaie encrypt` — single-file mode

```bash
refaie encrypt \
    --input /path/to/lesson1.mp4 \
    --course MATH101 \
    --lesson L01 \
    --api-host https://api.refaieacademy.site \
    --admin-token "PASTE_YOUR_ADMIN_TOKEN" \
    --output-dir ./encrypted_output
```

The admin token can also be supplied via the `REFAIE_ADMIN_TOKEN`
environment variable instead of `--admin-token`. It is never written to disk
by this tool.

Useful flags:

- `--no-upload` — encrypt and self-check only, skip the server call
  (prints the `key_id` and base64 file key to stdout instead).
- `--notes "..."` — free-text notes attached to the key registration.

### `refaie encrypt` — interactive "EncryptZ" folder mode

Run with no `--input` to launch interactive mode:

```bash
refaie encrypt
```

This scaffolds (or reuses) a folder tree:

```text
EncryptZ/
├── input/
│   ├── videos/
│   ├── audios/
│   ├── pdfs/
│   └── images/
└── output/
```

Drop plaintext files into the matching subfolder, then re-run `refaie
encrypt`. You'll be prompted for the admin token (if not already supplied),
a course ID, a lesson ID, and a file selection (`1,3,5`, `1-4`, or `all`).
Every selected file is encrypted, self-checked, written to `output/`, and
registered with the backend — failures are reported per-file and don't stop
the batch.

Default root folder is `/storage/emulated/0/EncryptZ` (override with
`--base-dir`).

### `refaie retry-pending` — recover from a failed registration

If a registration call fails (server down, network blip, bad response), the
key material is saved to `<key_id>.pending.json` next to the `.mmr` file —
nothing is lost. Once the server is reachable again:

```bash
refaie retry-pending ./encrypted_output/<key_id>.pending.json
```

The sidecar is deleted automatically on success.

### Full CLI reference

```bash
refaie --help
refaie encrypt --help
refaie retry-pending --help
```

## Local development

Clone the repo and install in editable mode with the dev extras
(`build`, `twine`, `pytest`):

```bash
git clone https://github.com/refaieacademy/refaie-cli.git
cd refaie-cli
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
```

`refaie` is now on your `PATH` inside the virtualenv, backed by your local
source tree — edits to `src/refaie/*.py` take effect immediately without
reinstalling.

Run the test suite (offline, no network calls):

```bash
pytest
```

## Building distributable packages

With the `build` package installed (included in the `dev` extra above):

```bash
python -m build
```

This produces two files in `dist/`:

- `refaie-<version>-py3-none-any.whl` — the wheel (binary distribution)
- `refaie-<version>.tar.gz` — the sdist (source distribution)

Sanity-check the wheel in a clean virtualenv before publishing:

```bash
python3 -m venv /tmp/refaie-check
/tmp/refaie-check/bin/pip install dist/refaie-*-py3-none-any.whl
/tmp/refaie-check/bin/refaie --version
/tmp/refaie-check/bin/refaie encrypt --help
```

## Publishing to PyPI

1. **Create accounts** on [TestPyPI](https://test.pypi.org/account/register/)
   and [PyPI](https://pypi.org/account/register/) if you don't have them,
   and enable 2FA.

2. **Create an API token** for each (PyPI account settings → API tokens →
   scope it to the `refaie` project once it exists, or to your whole
   account for the first upload). Store them somewhere safe — you'll paste
   one in as your password when `twine` prompts you (with username
   `__token__`).

3. **Bump the version** in `pyproject.toml` (`[project] version = "..."`)
   before every new upload — PyPI rejects re-uploading an existing version.

4. **Build fresh artifacts** (remove any stale `dist/` first):

   ```bash
   rm -rf dist/ build/ src/refaie.egg-info
   python -m build
   ```

5. **Upload to TestPyPI first** and confirm it installs cleanly:

   ```bash
   twine upload --repository testpypi dist/*
   python3 -m venv /tmp/refaie-testpypi
   /tmp/refaie-testpypi/bin/pip install \
       --index-url https://test.pypi.org/simple/ \
       --extra-index-url https://pypi.org/simple/ \
       refaie
   /tmp/refaie-testpypi/bin/refaie --version
   ```

   (`--extra-index-url` is needed because `cryptography` and `requests`
   themselves aren't published on TestPyPI.)

6. **Upload to the real PyPI** once TestPyPI looks correct:

   ```bash
   twine upload dist/*
   ```

7. **Verify the public install** works exactly as end users will run it:

   ```bash
   pip install refaie
   refaie encrypt --help
   ```

From then on, publishing a new version is just: bump the version number,
rebuild, `twine upload dist/*`.

## Security notes

- The admin token is only ever passed as a CLI flag or environment variable
  — it is never written to disk.
- The AES-256 file key is generated fresh, locally, for every file. It only
  touches disk if server registration fails, in the base64-encoded
  `.pending.json` sidecar — treat that file as sensitive and delete it once
  `retry-pending` succeeds (it's removed automatically on success).
- Every encrypted container is self-checked (decrypted and compared against
  the original plaintext) locally before any network call is made, so a
  corrupt or non-decryptable `.mmr` file is never uploaded or registered.

## Requirements

- Python 3.8+
- [`cryptography`](https://pypi.org/project/cryptography/) >= 41.0
- [`requests`](https://pypi.org/project/requests/) >= 2.31

## License

MIT — see [LICENSE](LICENSE).
