Marketplace — install runnable packages
The Marketplace tab lists runspec runnable packages you can install into
the console's venvs and into the venvs of your connected fleet hosts — the
same packages you would otherwise pip install by hand (runspec-linux,
runspec-windows, runspec-jira, a private acme-ops, …), as cards with a
description, tags, links, and the per-venv install state.
Nothing is curated by hand. The catalogue is generated from package
indexes: the console reads the indexes its own venv's pip is configured
with (plus any you declare) and lists every distribution that depends on
runspec.
What you see
Each card is one package:
- name · latest version and the repo(s) it came from;
- the package's summary, keywords as tags, and links to its
Docs / Home / Source / Changelog (from the package's
Project-URLmetadata) — Details opens the long description (README) and the dependency list; - where it is installed: one chip per venv — green when current, orange
with
↑when the index has a newer version, purple when the venv is ahead; - Install… / Update… / Install / reinstall… and Uninstall….
The toolbar filters by text, by repo, and by scope (All / Installed / Updates / Not installed). Refresh re-reads the indexes and re-probes every venv; the status line shows when each repo was last read and how many venvs were reachable.
Targets — which venvs
A package can be installed into:
- the console's own venv and every venv matched by
[local] venv_globs(local); - each fleet host's
runspec_pathsvenvs, over the host's pooled SSH connection — the active profile's working set, like the sidebar.
Disconnected hosts are listed but cannot be picked. The install picker shows each venv's Python version and current install, pre-selects the sensible set (the outdated venvs for an update; nothing when the package is already current everywhere), and lets you choose a specific version or force reinstall.
Run as — service-account venvs
A venv on a fleet host often belongs to a service account (created with
runspec-linux's create-venv --run-as). The picker's Run as column is
pre-filled with the venv's owner whenever it differs from your SSH login
user, and the install runs escalated — sudo -n -u <owner> (or su) —
exactly as the install-into-venv runnable does. Leave it blank to run as
the login user.
The command a target runs is the plain
<venv>/bin/python -m pip install [-U] [--force-reinstall] <package> (or
pip uninstall -y), so a host needs no runspec-linux to be managed
from the Marketplace. Only named package specs are ever passed — a URL,
path or option is refused — so the Marketplace can only install what the
configured indexes publish.
pip's output streams live per target; a finished install kicks a discovery refresh so the new runnables appear in Forms and in the agent's toolset without a restart.
Where the packages come from
Automatically — your pip configuration
With Settings → Marketplace → "Also read the indexes this venv's pip is
configured with" on (the default), the console reads pip's own
configuration for the venv it runs in — pip.ini / pip.conf at the
global, user and venv scopes, plus PIP_INDEX_URL / PIP_EXTRA_INDEX_URL —
and turns index-url and every extra-index-url into a repo. A console
whose venv is pointed at a private Nexus / Artifactory / devpi index gets a
marketplace for that index with no further configuration; pip's default
pypi.org counts when nothing sets an index. A user:pass@ embedded in the
URL is used for reading and never shown.
These repos appear read-only on the Settings tab. To customise one (name, credential, explicit packages…), declare it — a declared repo with the same index URL takes over.
Declared repos
[[marketplace.repos]] in config.toml (Settings → Marketplace → Add
repo) declares an index explicitly:
[[marketplace.repos]]
name = "Nexus"
url = "https://nexus.example.com/repository/pypi-all/simple/"
credential = "nexus-svc" # a Credentials-tab id → HTTP Basic auth
packages = ["acme-ops"] # read even when not matching the patterns
tags = ["internal"]
[[marketplace.repos]]
name = "PyPI"
url = "https://pypi.org/simple/"
| Key | Default | Meaning |
|---|---|---|
name |
(from the URL) | Label on cards and in the repo filter |
url |
— | The simple-API root (…/simple/) or the index base URL — the same value as pip's index-url |
enabled |
true |
Read this repo |
scan |
true |
List the whole index and keep names matching patterns (see below) |
packages |
[] |
Package names to read regardless of scan / patterns |
patterns |
["runspec-*"] |
Name globs a scanned project must match (overrides the section default) |
credential |
— | A Credentials id (username + password, or a token) sent as HTTP Basic auth when reading and carried to pip on install — only the id is stored |
pip_arg |
auto |
How the target's pip is pointed at the repo on install: auto (a pip-configured primary index → --index-url, an extra → --extra-index-url, pypi.org → nothing), none (rely on the target's own pip config), index-url, extra-index-url |
trusted_host |
— | Pass --trusted-host for this host (pip skips TLS verification for it) |
tags |
[] |
Free-form labels |
Config lives in config.toml, so declared repos ride
Config Sync and package config seeds —
one versioned list works fleet-wide.
Scanning vs naming — is pypi.org fast enough?
A scan lists the index's root page and keeps the names that match the
patterns (runspec-* by default). For a private index that is a few
kilobytes. pypi.org's root listing is ~45 MB (≈10 MB compressed, ~900 000
projects); the console streams it through a name filter without building the
full list, so a scan takes a few seconds and is done once per cache period
(cache_ttl, default 24 h) — opening the tab afterwards is instant. Set
scan = false and list packages explicitly if you would rather not.
Reading a candidate's metadata then costs one small request per package —
pypi.org's JSON API where available (the richest source: README, project
URLs, keywords), else the PEP 658 .metadata sidecar of the newest wheel,
else the wheel itself (bounded by max_wheel_bytes), which also proves a
bundled runspec.toml. Candidates are read in parallel.
A scanned name that turns out to be a registered-but-empty project — no
releases, or a name that 404s — is skipped silently (it's discovery noise
you can't install or fix, e.g. a squatted/typo runspec-* name someone
registered on PyPI with no uploads). Only a name you list explicitly in a
repo's packages shows up in the top "N packages skipped" note when it can't be
read, so a typo you typed is still flagged.
What counts as a runnable package
A distribution is listed when its Requires-Dist names runspec outside
any extra == marker — a runnable package always does, because its
runspec.toml is parsed by runspec at run time — or when its
Keywords include runspec-runnable (a corporate wrapper that pins runspec
through another package). It is never listed when it is in the exclude
list: the runspec library itself and the apps built on it
(runspec-console, runspec-mcp, runspec-room, runspec-registry) by
default. The *-core logic packages don't depend on runspec, so they drop
out on their own.
The console itself is deliberately not a marketplace entry: it is installed per machine, not into a fleet venv.
Hiding packages (adapter / white-label deployments)
[marketplace] hide is a list of name globs of runnable packages to drop
from the catalogue — a curation knob for a deployment that ships internal
wrappers and doesn't want the public package offered alongside them, where
showing both would lead to the wrong install:
[marketplace]
hide = ["runspec-jira", "runspec-confluence", "runspec-snow", "runspec-gitlab"]
A hidden package never appears — no card, no search hit, and it isn't counted
in a repo's total. Matching is case-insensitive on the normalised name and
glob-capable (runspec-*). It is additive (unlike exclude, which lists
the non-runnable library/apps): the defaults are untouched, so hiding a public
package can't accidentally un-hide the console or the other apps. The list
lives in config.toml, so it rides Config Sync and the
package config seed — your adapter package ships one
versioned list and every console honours it. Edit it in Settings →
Marketplace → Hidden packages, or seed it.
hide only affects what the Marketplace offers; a hidden package that is
already installed stays installed (manage it with your internal package's card
or with pip).
The agent and the Marketplace
The chat agent can discover installable packages, but it cannot install them — installing is always an operator action on this tab.
A read-only search_marketplace tool lets the agent search the same catalogue
you see here for runnable packages your indexes offer that aren't installed
yet. It's the complement to the agent's search_runnables (which only sees
what's already installed): when you ask "is there a runnable for GitLab?" and
nothing is installed, the agent can find runspec-gitlab in your index, tell
you what it adds, and point you here to install it — rather than stopping at "I
don't have a tool for that". Each result carries the package name, latest
version, summary, keywords, a verified flag (it ships a runspec.toml), the
repo(s) it came from, and doc/source links.
It is discovery only, by design: the agent recommends, you install. That
keeps the useful part — closing the "there's no tool for that" gap — safe and
universal, without letting the agent run pip install across your fleet's
venvs on its own.
The tool:
- honours
[marketplace] hide— a curated-out public package is never recommended, so your adapter deployment's agent points at your internal wrapper, not the public one; - is offered only when
[marketplace] enabled(dropped entirely when the marketplace is disabled); - reads the machine-local catalogue cache (no network unless it's cold), and marks a package installed only when a recent probe already knew — it never triggers a fresh SSH sweep.
There is no agent-driven install: to add a package the agent found, use the
card here (or pip on the target).
Making your package look good on the card
The card is built entirely from the package's published metadata, so a package that publishes good metadata gets a good card:
[project]
name = "acme-ops"
description = "Acme internal ops runnables — deploy, rotate secrets, drain a node"
readme = "README.md" # → the Details view
keywords = ["runspec", "acme", "ops", "deploy"]
[project.urls]
Documentation = "https://wiki.example.com/acme-ops"
Source = "https://git.example.com/platform/acme-ops"
Changelog = "https://git.example.com/platform/acme-ops/-/blob/main/CHANGELOG.md"
description is the card's one-liner; readme becomes the Details view;
keywords become tags; Documentation / Homepage / Source /
Changelog / Issues project URLs become the link row. A wheel that ships
a runspec.toml earns a verified tag when the metadata was read from
the wheel itself.
Settings & knobs
Settings → Marketplace edits the [marketplace] section (declared repos,
the pip-config switch, pre-releases, the cache period, clear cache). The full
key list is in the config reference.
The catalogue cache (marketplace_cache.json) is machine-local and never
synced; the venv probe (what each target has installed) is held in memory
until an install / uninstall, a host (re)connects, or you press Refresh.
Set [marketplace] enabled = false to hide the tab and refuse every
marketplace call — a locked-down deployment that installs packages some
other way.