Marketplace
The Marketplace tab is mnml’s shopfront for integrations. It’s federated by design: mnml doesn’t run a central registry, and no repo name is baked into the query path. Two sources ship as defaults, both fully overridable, and anyone can point mnml at their own launcher-manifest repo to run a private catalog.
This page covers the mechanics — sources, cache, provenance, config schema, and how installs actually land on disk. For the “what is an integration in the first place” model see Integrations overview; for hand-authoring a launcher TOML see Launcher manifests.
What the tab shows
Section titled “What the tab shows”Open the activity-bar Integrations panel, then click the Marketplace tab (or press / to filter). Two kinds of row appear:
[app] mnml-db-postgres (crates.io) PostgreSQL browser for mnml — connection tabs, query playground
[launcher] htop (chris-mclennan/mnml-integrations) Interactive process viewer — shells to `htop`[app]rows (orange tag) — crates published with themnml-integrationkeyword. These have a binary that gets installed viacargo install.[launcher]rows (cyan tag) — TOML manifests under a configured GitHub folder. These have no binary; the manifest itself is the whole integration.
Left-click any row to install. See Install actions below for what each kind does.
The tab also shows any enabled = false integrations you already have on disk — those render above the marketplace rows so “already-installed but not on the rail” doesn’t get lost.
Default sources
Section titled “Default sources”Out of the box, mnml queries two sources (defined in src/marketplace.rs::default_sources):
| Source id | Type | What it queries |
|---|---|---|
crates.io |
crates_keyword |
https://crates.io/api/v1/crates?keyword=mnml-integration&per_page=100 |
chris-mclennan/mnml-integrations |
github_launcher_folder |
https://api.github.com/repos/chris-mclennan/mnml-integrations/contents/launchers |
Both are fully configurable. Turning off use_defaults replaces them entirely; leaving it on merges any additional [[marketplace.source]] entries you configure alongside them.
Provenance — Official vs Community
Section titled “Provenance — Official vs Community”Every entry in the tab carries a Provenance field, tagged at fetch time by matching the entry’s source_id against the shipped defaults:
- Official — entry comes from a source whose id appears in
default_sources(). Today that’scrates.io(any crate with themnml-integrationkeyword) andchris-mclennan/mnml-integrations. - Community — everything else. A user-added source with a custom id, an internal crates-io keyword, a third-party launcher-folder repo.
The gatekeeper isn’t a manifest field — any author could set one — it’s who has write access to the source repo or crates-io keyword. That’s exactly what the default-sources list catalogs.
The tag survives cache round-trips (~/.cache/mnml/marketplace.json). Old cache entries from before provenance shipped deserialize as Community, and any future variant this build doesn’t recognize also lands there — the fallback is deliberately the safer under-count (no false-Official labels on unknown data).
Visible badging and Official-first sorting in the Marketplace tab are the next follow-up; today the tag is populated and cached, and the API is stable for consumers reading the JSON directly. To confirm what’s Official on the current source list:
cat ~/.cache/mnml/marketplace.json | jq '.entries[] | select(.provenance == "official") | .id'Config schema
Section titled “Config schema”Everything below is optional — an empty [marketplace] block is the same as no block at all.
[marketplace]enabled = true # master switch. false → tab is empty, no fetches happen.cache_ttl_secs = 3600 # cache lifetime in seconds. Default 3600 (1h).use_defaults = true # merge shipping defaults with user sources. # false = user sources are the entire list.
# Additional sources — appended when use_defaults = true,# or the entire source list when use_defaults = false.[[marketplace.source]]type = "crates_keyword"id = "my-org-crates"keyword = "my-org-integration"
[[marketplace.source]]type = "github_launcher_folder"id = "my-org-launchers"repo = "my-org/mnml-tools"path = "launchers"Two source types are supported today:
crates_keyword
Section titled “crates_keyword”Queries https://crates.io/api/v1/crates?keyword=<keyword>&per_page=100. Every result that comes back becomes an [app] row. mnml renders the crate name as the label, description as the subtitle, and the returned downloads / updated_at as sort metadata.
There’s nothing special about the shipping mnml-integration keyword — any keyword works. If your organization publishes crates under a shared tag, add a source for it and those crates show up alongside the shipping defaults. Their Provenance will be Community (your source id isn’t in default_sources()) — that’s the correct labeling; only crates.io’s mnml-integration keyword is gatekept as Official.
github_launcher_folder
Section titled “github_launcher_folder”Fetches https://api.github.com/repos/<repo>/contents/<path>, filters to files ending in .toml, and downloads each one via download_url. Each successful download is parsed as an IntegrationManifest and rendered as a [launcher] row.
Files that fail to parse are skipped with a stderr message — one bad manifest in a folder doesn’t fail the whole source. If you host launcher manifests in a private repo, gh auth token acceleration picks up the credential automatically.
Adding a private catalog
Section titled “Adding a private catalog”Point mnml at any GitHub repo + folder path and it appears alongside the reference launchers:
[[marketplace.source]]type = "github_launcher_folder"id = "acme"repo = "acme-corp/mnml-tools"path = "launchers"Then run marketplace.refresh. Every .toml file under launchers/ in acme-corp/mnml-tools now renders as an installable row, tagged (acme) and with Provenance::Community.
The id field is just a display tag — it can be anything, it doesn’t need to match the repo name. Keep it short (renders in the tab as (acme) next to each row) so users can tell your entries apart from the reference catalog. Note that even if you point a user-added source at the exact same repo as the reference catalog under a different id, its entries will still tag as Community — the provenance check is purely on the source id, not the underlying URL.
Marketplace fetches are cached at ~/.cache/mnml/marketplace.json. The cache file carries:
- Every entry from every source’s last successful fetch (including the per-entry
Provenance). - The Unix timestamp of the last write (
fetched_at). - The TTL that was in effect when the cache was written (
ttl_secs).
On launch mnml loads the cache into marketplace_entries — so the tab renders instantly, even before the first fetch completes. On marketplace.refresh a fresh fetch runs on background threads (one per source) and results merge in as each source completes. Sources that fail are logged to stderr; the cache falls back to the previous entries for that source.
Cache TTL is advisory. mnml doesn’t auto-refresh when the TTL expires — you drive it explicitly via marketplace.refresh. The TTL is metadata for future stale-while-revalidate behavior and for tools that inspect the file offline.
To force a full re-fetch, run marketplace.refresh after deleting the cache:
rm ~/.cache/mnml/marketplace.jsonMissing / malformed cache files are silent no-ops — mnml renders empty and waits for the next refresh.
Install actions
Section titled “Install actions”Left-click on a Marketplace row dispatches based on the entry kind:
[app] rows — cargo install
Section titled “[app] rows — cargo install”mnml spawns a Pty pane running:
cargo install --force <crate-name> && $HOME/.cargo/bin/<crate-name> --installYou watch the build live. When cargo exits cleanly, the binary lands in ~/.cargo/bin (which mnml’s PATH detection covers even under Finder-launched .app bundles) and the sibling’s own --install subcommand runs immediately after, writing ~/.config/mnml/integrations/<id>.toml.
Two footguns this shape closes:
--force— without it,cargo installskips silently when the crate is already installed at any version. That made “click Install to upgrade” a no-op.--forcereinstalls unconditionally so a fresh build always lands.$HOME/.cargo/bin/<name>explicit path — without the full path, the--installshell resolve runs whichever<name>PATH finds first. A stale copy in~/.local/bin/or another PATH entry could win and write its old manifest, leaving you with a fresh binary in~/.cargo/bin/but an outdated manifest on disk. Targeting the cargo-bin path directly bypasses PATH order.
If a sibling doesn’t ship an --install subcommand, the second shell fails (or the && chain short-circuits) — fall back to hand-authoring the manifest per Launcher manifests.
For existing stale copies elsewhere on PATH — installed before --force shipped, or copied around by hand — see Installing → Diagnostics for the integrations.audit_shadowed_binaries command that quarantines them.
[launcher] rows — download TOML
Section titled “[launcher] rows — download TOML”mnml does one blocking HTTP GET against the launcher’s download_url (from the GitHub Contents API), parses the response as an IntegrationManifest, verifies the id matches the row you clicked, and writes the whole file to:
~/.config/mnml/integrations/<id>.tomlBlocking is fine here — launcher TOMLs are kilobytes and the request completes in ~200ms. On success mnml toasts installed <id> → <path> and immediately re-scans the integrations folder so the chip appears on the Installed tab without a restart.
Rejection cases:
- Malformed TOML — mnml refuses to write. Toast:
install failed: parse toml: <error>. idmismatch — the row’sidand the fetched manifest’sidfield disagree. Toast:install failed: manifest id "foo" doesn't match expected "bar". Safety net against a GitHub folder being renamed mid-fetch.- HTTP error — GitHub returned non-2xx. Toast:
install failed: fetch: <error>.
GitHub rate limits
Section titled “GitHub rate limits”Unauthenticated GitHub API calls are capped at 60 requests/hour per IP. That’s tight when a source has many launcher files (one API call for the folder listing + one download per file). mnml’s fetcher auto-detects a gh CLI token to lift the ceiling to 5000/hour:
gh auth login # one-timeIf gh auth token returns a token, every GitHub request adds Authorization: Bearer <token>. Neither path fails — the cache absorbs the rate-limit difference. If you don’t have gh and you’re hitting the 60/hr ceiling, install gh and log in once; refresh will pick up the token automatically.
Palette commands
Section titled “Palette commands”| Command | What it does |
|---|---|
marketplace.refresh |
Fire a fresh fetch against every configured source. Non-blocking — results merge in as each source completes. |
integrations.refresh |
Re-scan the two integrations/ folders. Run this after a launcher install to see the new chip. |
integrations.refresh_binary_cache |
Drop cached is_binary_installed lookups so a freshly-cargo-installed sibling resolves without restart. |
launcher.add_local |
Skip the marketplace and hand-author a local launcher via the edit overlay. See Launcher manifests. |
Contributing to the reference launcher catalog
Section titled “Contributing to the reference launcher catalog”The chris-mclennan/mnml-integrations repo is a plain Git repo with launcher manifests under launchers/. To add a launcher:
- Fork the repo.
- Add
launchers/<your-id>.tomlfollowing the Launcher manifests schema. - Open a PR.
The bar is deliberately low — the launcher should parse as a valid IntegrationManifest, have a working run command against a CLI most users have installed, and not be a wrapper for something malicious. There’s no code review beyond “does this manifest make sense” — the catalog is discoverability, not an audit.
Merged manifests appear in every mnml install’s Marketplace tab on the next marketplace.refresh, and (because they came in via the default source) they carry Provenance::Official.
- Installing integrations — Marketplace clicks,
--installfor binary siblings, sidecar overrides. - Launcher manifests — the schema every marketplace launcher speaks.
- Integrations overview — the “two flavors + one on-disk shape” model.
- Building integrations — publishing your own launcher or binary sibling.
- Community integrations — where to find and share what others have published.