Metadata-Version: 2.4
Name: navig-msstore
Version: 0.7.2
Summary: navig mstore -- Microsoft Store publishing verbs for NAVIG, wrapping the msstore-publish block.
Author: navig
License: MIT
Project-URL: Homepage, https://navig.run
Project-URL: Documentation, https://navig.run/docs
Keywords: navig,cli,agent,automation,plugin,microsoft-store,msix,publishing,windows
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: navig-sdk>=2.7.0
Requires-Dist: typer>=0.9
Dynamic: license-file

# navig-msstore

<!-- navig:plugin-card:start -->
<!-- Generated by scripts/gen_plugin_catalog.py — edit the plugin's pyproject.toml, not this block. -->

> Microsoft Store publishing verbs for NAVIG, wrapping the msstore-publish block.

**Works on its own** — its own CLI, no navig required · v0.7.2 · [PyPI](https://pypi.org/project/navig-msstore/)

**On its own** (no navig needed)

```bash
pip install navig-msstore
navig-msstore --help   # no navig needed
```

**With navig**

```bash
pip install navig-msstore
navig mstore --help
```

<details><summary>Commands</summary>

| Command | What it does |
|---|---|
| `navig mstore auth` | Configure the `msstore` CLI from credentials in the vault (or env). |
| `navig mstore configure` | Write the Store identity (store/identity.json) for an app -- interactive. |
| `navig mstore creds` | Show which Store credentials resolve, and from where (values never shown). |
| `navig mstore doctor` | Preflight the whole Store chain: tooling, credentials, block, and (per app) readiness. |
| `navig mstore info` | Explain how `navig mstore` relates to the menu and the block. |
| `navig mstore open` | Open the Microsoft Partner Center dashboard in a browser. |
| `navig mstore package` | Build the MSIX by driving the app's scripts/create-msix.ps1. |
| `navig mstore publish` | Upload + commit a Store submission by applying the msstore-publish block. |
| `navig mstore sales` | Paid sales (paid apps + add-on purchases) from Partner Center. Read-only. |
| `navig mstore show` | Show an app's Store identity + latest built MSIX. |
| `navig mstore status` | Show the current submission status via the Microsoft Store Developer CLI. |

</details>

<!-- navig:plugin-card:end -->

`navig mstore` — the full Microsoft Store toolkit for NAVIG.

A **standalone first-party plugin** (lives in `plugins/`, **not** in core). It is the
single source of truth for Store operations — configure identity, resolve credentials,
package the MSIX, publish, and check status — for **any** app (via `--dir`). It wraps the
same `msstore-publish` **block** (so publishing stays verifiable) without bloating core.

## How it mounts (no core edit)

NAVIG's CLI auto-mounts every `navig.commands` entry point as a top-level `navig <verb>`
(`core/navig/cli/registration.py`). This package declares:

```toml
[project.entry-points."navig.commands"]
mstore = "navig_msstore:app"
```

so once installed, `navig mstore` exists. Disable it like any plugin
(`navig store disable plugin:navig-msstore`) — degrade-never-blocks-boot.

## Install

```
pip install -e plugins/navig-msstore      # from the repo root
navig mstore --help
```

## Credentials (never hardcoded)

`msstore` authenticates with an Azure AD service principal. This plugin resolves each
value with a deliberate precedence:

1. **explicit env** — `NAVIG_MSSTORE_TENANT_ID` / `_CLIENT_ID` / `_CLIENT_SECRET` (a per-tool override)
2. **the vault** — provider `partner_center` (the Microsoft Partner Center App-Only credential
   from the navig-harbor connector), then `azure`; profile `connector`, then `default`. Read via
   the official `navig vault get <provider>/<field> --raw` seam, which handles the
   `provider/data_key` path **and** the credential profile.
3. **generic Azure env** — `AZURE_TENANT_ID` / `_CLIENT_ID` / `_CLIENT_SECRET`, **last on purpose**:
   `AZURE_*` is shared by az-cli / Terraform / etc. and may be a *different* Azure app, so the
   deliberate vault creds win over it.

```
navig mstore creds     # what resolves + from where (partner_center@connector) -- never reveals a value
navig mstore auth      # resolve -> msstore reconfigure
```

Vault fields expected: `partner_center/tenant_id`, `partner_center/client_id`,
`partner_center/client_secret` (profile `connector`). Override the first-tried source with
`NAVIG_MSSTORE_VAULT_PROVIDER` / `NAVIG_MSSTORE_VAULT_PROFILE`. `creds` shows only the source
(no values); `auth` reveals via `--raw` only to hand the secret to `msstore` once, never logging it.

## Verbs (every one accepts `--dir <app>`, default: cwd)

| Verb | What it does |
|---|---|
| `configure` | write `store/identity.json` (Store Product ID + MSIX identity) — interactive |
| `auth` | resolve creds (env/vault) → `msstore reconfigure` |
| `creds` | show what resolves + from where (never reveals a value) |
| `package` | build the MSIX (drives the app's `scripts/create-msix.ps1`; `--build` tauri-builds first, `--dry-run` stages) |
| `publish` | apply the `msstore-publish` block (auto product_id from identity.json + latest `.msix`) |
| `status` | `msstore submission status` |
| `show` | identity + latest built MSIX |
| `open` | Partner Center dashboard |
| `doctor` | preflight the whole chain — tooling (msstore, MakeAppx), creds, block, and per-app readiness (`--dir`) |

```
# full flow for an app (Anchor shown; -C/--dir points at any app)
navig mstore configure -C apps/anchor      # once, from Partner Center
navig mstore auth                          # creds -> msstore
navig mstore package  -C apps/anchor --build
navig mstore publish  -C apps/anchor       # auto product_id + latest .msix
navig mstore status   -C apps/anchor
```

`publish` stays sugar over `navig apply msstore-publish … --approve publish` — the block does
the real, receipt-backed, `--approve`-gated work.

## Sale alerts (the cash-register ring)

`navig mstore sales [--days 30] [--json]` lists paid sales (paid apps and add-on
purchases) from Partner Center's analytics API. It is read-only.

Inside navig's daemon the plugin also checks the same data **every hour** and sends a
**Sales** notification for each new paid sale (Telegram + the deck feed by default);
NAVIG OS plays a cash-register ring for it (Settings → Notifications → Sounds, with a
Play button). Free downloads never ring. The first check only records what already
exists, so switching it on does not ring for past sales.

- Microsoft reports sales as **daily totals**, so a ring means "new in your report",
  not the second someone paid.
- The analytics API is rate-limited hard; calls are spaced ~2.5 s apart, so a check
  over 20 apps takes about two minutes.
- `navig config set plugins.msstore.sales_enabled false` turns it off;
  `plugins.msstore.sales_interval_min` sets the pace (minutes, at least 15).
- State: `~/.navig/msstore/sales_state.json` (what was already counted).
- Deck routes: `GET /api/deck/msstore/sales` (status), `POST …/sales/check` (check now),
  `POST …/sales/test` (a pretend sale, to hear the ring), `POST …/sales/digest` (the
  daily summary now; it does not count as the day's).

**The daily summary.** Once a day, at the first check after 09:00 local time, the daemon
sends a **Daily sales summary** (Telegram + the deck feed by default): yesterday's sales
by product and the total of the last 7 full days. It is built from the data the hourly
check already read, so it costs no extra Partner Center calls, and it never rings. A day
with no sales still gets one, so a missing summary means the watcher stopped.

- `navig config set plugins.msstore.sales_digest false` turns it off;
  `plugins.msstore.sales_digest_hour` moves it (0-23).
- Quiet hours apply: a summary hour inside them reaches the deck feed only.
- State: `~/.navig/msstore/digest_state.json` (the day the last one went out, and the
  per-day totals each summary reported), so a restart never sends a second one.
- **Late sales.** Microsoft sometimes fills a day in after the fact. Each summary
  compares the last 7 days with what earlier summaries reported and adds a line such as
  `Arrived late: 4 sales · $26.00 for Wed 7 Oct`, so a morning "no sales" is corrected
  the next day instead of staying wrong.

**The Store sales app.** The plugin also registers a free **Store sales** desktop app
in NAVIG OS (Money category): a daily chart of sales or revenue (7, 30 or 90 days),
per-app and per-add-on totals, every daily summary (late sales marked), and how the
hourly check is doing. Each check merges its 7-day window into
`~/.navig/msstore/sales_history.json` (kept 400 days, never in the ring's state, so
nothing old ever rings); the first time, the daemon also loads 90 days in the
background so the chart starts full. Routes: `GET /api/deck/msstore/sales/history?days=30`
and `POST /api/deck/msstore/sales/history/backfill` (`{"days": 90}`).

**When the check itself fails.** After 3 failed checks in a row (about three hours) the
daemon sends one **Sales check failing** alert (Telegram + the deck feed). It says why in
plain words — Microsoft refused the sign-in (an expired secret), access was refused,
credentials are missing, or Microsoft could not be reached — and what to do. It never
forwards Microsoft's raw response. When a check succeeds again it sends one "working
again" message; outages shorter than 7 days lose nothing, because every check re-reads
the last 7 days.

## The three surfaces

| Surface | Role |
|---|---|
| **Block** `msstore-publish` | the portable, verifiable publish primitive (receipt) |
| **Plugin** `navig mstore` | the operator surface — all Store logic; wraps the block + workers |
| **Menu** `npm run menu` (per app) | a **thin interactive front-end** that just calls `navig mstore` verbs |

Core stays clean — none of this is a hardcoded core command.
