Metadata-Version: 2.5
Name: mal-plugin-cli
Version: 0.1.1
Summary: Scaffold and publish MyAppsLibrary plugins from the terminal
Project-URL: Homepage, https://marketplace.rodolphe-augusto.fr
Project-URL: Repository, https://github.com/rodolphe37/my-apps-library-plugins-marketplace
Author: rodolphe37
License-Expression: MIT
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# mal-plugin — CLI for MyAppsLibrary plugins

Scaffold and publish [MyAppsLibrary](../../my-apps-library) plugins to the [marketplace](..) from the terminal — no browser needed except for the one-time GitHub sign-in.

```bash
mal-plugin login              # sign in with GitHub (Device Flow)
mal-plugin new                # scaffold a plugin project (feature or translation)
cd my-plugin-id
mal-plugin publish            # validate, package, submit, and wait for the scan result
```

## Why a separate identity from the web session

An admin login is a wholly separate identity in this marketplace (see the [root README](../README.md)) — the CLI isn't that, it's the same GitHub-authenticated developer identity the web app uses, just reached a different way. GitHub's OAuth **Device Authorization Flow** (the same mechanism `gh auth login` uses) lets the CLI sign in without a browser-embedded redirect or a locally-listening HTTP server: it shows a short code, you enter it at a URL GitHub gives you, and the CLI polls until you've approved it. The CLI then exchanges the resulting GitHub token for the marketplace's own long-lived bearer token (`POST /api/auth/cli/exchange` — see `backend/app/routers/auth.py`), stored locally and never touching a browser cookie.

That bearer token is visible and revocable from the web at any time — sign in on the marketplace site and go to **Account → Active CLI sessions**.

## Install

```bash
pip install mal-plugin-cli   # or: pipx install mal-plugin-cli (recommended — isolated environment)
```

(Not published yet while this is under active development — for now, install from source: see "Development setup" below.)

## Configuration

Two environment variables, both optional (defaults point at `localhost` for local development against this repo's own `backend`/`frontend`):

| Variable | Default | Purpose |
|---|---|---|
| `MAL_PLUGIN_API_BASE_URL` | `http://localhost:8000` | The marketplace backend to talk to |
| `MAL_PLUGIN_MARKETPLACE_URL` | `http://localhost:5173` | Used only to print the live plugin URL after `publish` |
| `MAL_PLUGIN_GITHUB_CLIENT_ID` | *(none)* | **Required for `login`** — see below |

### `mal-plugin login` requires a GitHub OAuth App with Device Flow enabled

A GitHub OAuth App's client id is not a secret (it's meant to be embedded in a public/native client like this one — see [GitHub's Device Flow docs](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow)), but it does have to correspond to a real App:

1. [github.com/settings/developers](https://github.com/settings/developers) → **New OAuth App**. The callback URL field is irrelevant to Device Flow — anything valid works.
2. In the App's settings, check **"Enable Device Flow"**.
3. `export MAL_PLUGIN_GITHUB_CLIENT_ID=<the App's Client ID>`

This is a one-time setup step, independent of the backend's own `GITHUB_CLIENT_ID`/`GITHUB_CLIENT_SECRET` (used for the *web* Authorization Code flow, see `backend/README.md`) — the CLI never needs a client secret, since Device Flow's token exchange doesn't require one.

## Commands

| Command | What it does |
|---|---|
| `mal-plugin login` | Sign in with GitHub (Device Flow) |
| `mal-plugin whoami` | Show the currently signed-in account |
| `mal-plugin logout` | Sign out and revoke this CLI session |
| `mal-plugin new [--dir PATH]` | Scaffold a new plugin project (prompts for type/id/name/description/category) |
| `mal-plugin publish [--dir PATH]` | Validate, package, submit, and wait for the scan result — **requires `mal-plugin login` first** |

Only `publish` requires being signed in (it's the only command that talks to an authenticated `/api/dev/*` endpoint) — `new` doesn't, since fetching categories and downloading a boilerplate are both public endpoints. You can scaffold a plugin, poke around, edit `plugin.py`, all before ever running `login`; you just won't be able to `publish` until you have.

`new` downloads the exact same boilerplate the web `/tutorial`/`/docs` pages link to (`GET /api/boilerplate/download`) — there's a single source of truth for what a starter plugin looks like. It also writes a `.mal-plugin.json` sidecar file next to `plugin.toml` — marketplace-only bookkeeping (category, short description, the draft's id once created) kept **out** of `plugin.toml` itself, since that file's schema is the desktop app's contract, not the marketplace's.

`publish` runs a local pre-check (a hand-synced subset of the backend's real scan — see `mal_plugin/validate.py`'s docstring) before ever making a network call, then walks the same steps the web wizard does: create-or-reuse draft → set presentation → upload version → submit → poll the automated scan to a result.

## Development setup

```bash
cd cli
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

Point it at your local backend (see `../backend/README.md` for getting that running):

```bash
export MAL_PLUGIN_API_BASE_URL=http://localhost:8000
export MAL_PLUGIN_MARKETPLACE_URL=http://localhost:5173
```

Run the tests (no server or network needed — the API client and device flow are mocked):

```bash
pytest
```

Lint:

```bash
ruff check mal_plugin tests
```

## Distribution

**Not published yet** — `mal-plugin-cli` is confirmed available on PyPI (checked, not registered by anyone else) but no release has been built or uploaded. Until then, install from source (see "Development setup" above).

The intended path once it's ready: build and publish to PyPI as `mal-plugin-cli` — a separate, public registry unrelated to this repo's visibility (`cli/`'s own scoped `pyproject.toml` means only this directory's contents would ever leave the repo, regardless of whether the monorepo itself is public or private):

```bash
cd cli
python -m build          # generates dist/*.whl + dist/*.tar.gz
twine upload dist/*      # requires a PyPI account + API token
```
