Metadata-Version: 2.4
Name: qls-mgw-cli
Version: 0.1.1
Summary: Command line sign-in and Python library for the API Gateway for Cisco Meraki (not affiliated with Cisco)
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: httpx>=0.27
Dynamic: license-file

# qls-mgw-cli

Command line sign-in and a small Python library for the API Gateway for Cisco Meraki.
Install it from PyPI as `qls-mgw-cli` (not `mgw`, which is an unrelated package). Your gateway
also serves the same wheel, for machines without PyPI access (the "My access" page shows the
exact commands).
It was called `mgw-cli` before: remove that first (`uv tool uninstall mgw-cli` or
`pip uninstall mgw-cli`). The command (`mgw`) and the import (`mgw_cli`) are unchanged.
The version is always the same as the gateway's version, so pin it to your gateway's
(the "My access" page and `GET /gw/v1/cli` show it).

## Command line

    uv tool install "qls-mgw-cli==<version>"
    mgw login --gateway https://<gateway>
    mgw run -- python my_script.py

## Python

Install it into your project's environment, next to the `meraki` SDK:

    pip install meraki "qls-mgw-cli==<version>"

```python
from mgw_cli import Gateway

gw = Gateway("https://<gateway>")            # uses `mgw login` or MGW_TOKEN
# gw = Gateway.login("https://<gateway>")    # or sign in from Python (browser, MFA)

dashboard = gw.dashboard()                   # meraki.DashboardAPI through the gateway
print(dashboard.organizations.getOrganizations())

with gw.change("CHG-1234"):                  # change reference for these writes
    dashboard.networks.updateNetwork(network_id, name="Store 12")

with gw.temporary_dashboard(hours=8) as dashboard:   # long job: temporary API key, revoked afterwards
    ...

# Automation without a person (service account, client credentials):
gw = Gateway.from_client_credentials("https://<gateway>", client_id, client_secret)
```

### Sign-in token, personal API key or service account

- **Sign-in token** (`mgw login`, `mgw token`, `Gateway.login`): lives about an hour and is not
  renewed (sign in again); it carries your own levels from the access policy.
  `gw.dashboard()` uses it by default.
- **Personal API key** (`create_api_key()`, `temporary_dashboard()`): the lifetime you choose
  (5 minutes up to the gateway's maximum, typically 24 hours) and level `read` (the default) or
  `write` (only with write access). The key is shown once and can be revoked. Creating one needs
  a sign-in; an API key or a service account cannot create keys.
- **Service account** (`Gateway.from_client_credentials`): client credentials from the identity
  provider; the token is renewed automatically. Its access comes from grants that name it.

### Method reference

Signing in:

| Method | What it does |
|---|---|
| `Gateway(url, *, token=None, profile="default")` | Uses `token`, else `MGW_TOKEN`, else the still valid token `mgw login` stored for `profile`; otherwise raises `GatewayError`. `url` is the gateway origin (https). |
| `Gateway.login(url, *, profile="default", open_browser=True, say=print)` | Signs in interactively (browser, MFA, like `mgw login`), stores the token for `profile` like `mgw login` and returns a `Gateway`. |
| `Gateway.from_client_credentials(url, client_id, client_secret, *, scope=None, audience=None, token_url=None)` | A service account: gets a new token from the identity provider whenever the current one is about to expire. `audience` defaults to the gateway's (Auth0); Entra ID needs `scope="<Application ID URI>/.default"`, Okta your custom scope. |
| `gw.token` | Property: a valid token for `Authorization: Bearer` (renewed for service accounts). `gw.url` is the gateway origin. |

All three also take `http=`, an `httpx.Client` of your own.

The gateway's own API:

| Method | What it does |
|---|---|
| `call(method, path, body=None, *, token=None)` | Calls any gateway path (e.g. `/gw/v1/...`) with a JSON body and returns the JSON answer (`None` for an empty answer). |
| `whoami()` | Who the gateway sees (`GET /gw/v1/whoami`). |
| `organizations()` | Your organizations with your levels (read, write, sensitive). |
| `create_api_key(description="", *, hours=1, level="read")` | Creates a personal API key; the answer has its `id` and the key itself in `token` (shown only this once). |
| `api_keys()` | Your active API keys (without the keys themselves). |
| `revoke_api_key(key_id)` | Revokes one of your API keys. |
| `revoke_all_api_keys()` | Revokes all your API keys (lost laptop) and returns how many. |
| `set_change_reference(ref)` | Sets the active change reference for your following writes (also those made with the Meraki SDK). |
| `clear_change_reference()` | Clears the active change reference. |
| `check_change_reference(ref, org_id)` | Would `ref` be accepted for writes in `org_id` (format and ticket system)? Check before you start. |
| `with gw.change(ref):` | Writes inside the block carry `ref`; it is cleared afterwards, also after an error. |

The Meraki SDK and errors:

| Method | What it does |
|---|---|
| `dashboard(*, api_key=None, **kwargs)` | A `meraki.DashboardAPI` with `base_url` `<gateway>/api/v1` and `api_key` (default: `gw.token`, taken when it is created). Other arguments go to `DashboardAPI`. |
| `with gw.temporary_dashboard(*, hours=4, level="read", description="Python job", **kwargs) as dashboard:` | For jobs longer than a sign-in token lives: creates a personal API key, gives a `DashboardAPI` using it and revokes the key when the block ends. |
| `GatewayError` | Raised when a call is refused, the gateway cannot be reached or signing in fails. `.status` is the HTTP status, or `None`. |

Not affiliated with, endorsed by or sponsored by Cisco Systems, Inc.
