Install Command¶
Use porringer install to reconcile the current machine with a manifest, HTTPS setup profile, or porringer:// install link. Install first builds the same plan exposed by porringer preview, asks for confirmation unless --yes is provided, then executes the selected actions.
Run preview first when you want to inspect the plan without changing the environment:
Manifest Locations¶
When the target is a directory, Porringer looks for manifests in this order:
porringer.json.[tool.porringer]inpyproject.toml.
A target can also be a manifest file, an HTTPS setup profile, or a porringer:// install link.
Manifest Shape¶
| Field | Type | Description |
|---|---|---|
version |
string | Manifest schema version. The current version is "1". |
packages |
object | Packages to install per ecosystem, such as { "python": ["requests"] }. |
tools |
object | CLI tools to install per ecosystem, such as { "python": ["pdm"] }. |
runtimes |
object | Language runtimes per ecosystem, such as { "python": ["3.14"] }. |
scm |
object | Source repositories to clone or update, such as { "git": ["https://github.com/org/repo"] }. |
preferences |
object | Optional installer preference per ecosystem, such as { "python": "uv" }. |
name |
string | Optional display name. |
description |
string | Optional short description. |
author |
string | Optional author or organization name. |
url |
string | Optional project URL. |
Manifest entries are grouped by kind, then by ecosystem. Ecosystem names are declared by plugins, so new ecosystems do not require core schema changes.
{
"version": "1",
"packages": {
"python": ["ruff", "pytest"],
"node": ["typescript"]
},
"tools": {
"python": ["pdm"]
},
"preferences": {
"python": "uv"
}
}
Kinds and Installers¶
| Ecosystem | Kind | Typical installers |
|---|---|---|
python |
packages |
uv, pip |
python |
tools |
pipx |
python |
runtimes |
pim, pyenv |
python |
project install | uv, pdm, poetry |
system |
packages |
brew, apt, winget |
node |
packages |
npm, pnpm |
node |
project install | npm, pnpm |
git |
scm |
git |
Project install is implicit. Project-install plugins inspect the repository for marker files, lock files, and tool-specific configuration. Porringer then chooses one project-install owner per ecosystem.
Package Plugins¶
Any entry in packages, tools, runtimes, or scm can include a plugins array. Use it when the installed tool has its own plugin mechanism. For example, this installs pdm and then runs PDM's native plugin install flow for cppython:
The full object form is also supported:
The field is handled by plugins that implement PluginManager, such as pdm and pipx.
Sync Strategies¶
| Strategy | Flag | Behavior |
|---|---|---|
minimal |
default | Install missing packages and leave satisfied packages alone. |
latest |
--strategy latest |
Upgrade every package to its latest allowed version. |
Common Usage¶
Use --only-action to run one or more stable action IDs from a previous preview:
Options¶
| Option | Description |
|---|---|
--project-dir, -d |
Working directory for project-install actions. |
--fail-fast / --no-fail-fast |
Stop on the first error, or continue and report all failures. |
--strategy, -s |
Use minimal, latest, or exact resolution. |
--plugin |
Include only actions for selected plugin names. Repeatable. |
--only-action |
Run only a stable action ID such as 0:2. Repeatable. |
--jsonl |
Emit progress events and the final result as newline-delimited JSON. |
--record |
Write a replayable JSON run record to the given path. |
--yes, -y |
Skip the confirmation prompt. |
Execution Order¶
Porringer installs runtimes before packages and tools, then handles project install and source repositories. After package installation, it discovers plugins again. That lets tools installed earlier in the same run become installers for later actions.
This is called deferred tool resolution. A tool action whose installer is not available during preview can be created with installer=None and resolved just before execution.
{
"version": "1",
"runtimes": { "python": ["3.14"] },
"packages": { "python": [{ "name": "pipx" }] },
"tools": { "python": [{ "name": "pdm" }] }
}
Execution order:
pimorpyenvinstalls Python 3.14.piporuvinstallspipx.- Plugin discovery runs again and finds
pipx. pipxinstallspdm.- The selected project plugin syncs dependencies from the discovered project root.
See examples/python-bootstrap/ for a complete bootstrap example.
Project Directory Resolution¶
Project-install backends discover their own project root by walking up from the manifest location and looking for ecosystem marker files.
| Ecosystem | Marker file |
|---|---|
python |
pyproject.toml |
node |
package.json |
This lets a manifest live below the project root:
Each ecosystem resolves independently, so Python and Node project plugins can choose different roots from the same manifest. If no marker is found, the manifest directory is used and a warning is reported.
Use --project-dir to override auto-discovery for all ecosystems:
Standalone Manifests¶
When a manifest has no associated project, pass project_directory=False through the API. Package, tool, runtime, and SCM actions still run. Project-install actions are reported as skipped instead of failed.
import asyncio
from pathlib import Path
from porringer.api import API
from porringer.schema import LocalConfiguration, SetupParameters
async def main() -> None:
api = API(LocalConfiguration())
params = SetupParameters(
paths=Path('downloaded-profile/porringer.json'),
project_directory=False,
)
report = await api.sync.run(params)
for skip in report.results.skips:
print(f'Skipped: {skip.action.description} - {skip.message}')
asyncio.run(main())
API Usage¶
import asyncio
from pathlib import Path
from porringer.api import API
from porringer.schema import LocalConfiguration, SetupParameters
async def main() -> None:
api = API(LocalConfiguration())
project_path = Path('./my-project')
manifest_path = Path('./my-project/.porringer/porringer.json')
await api.sync.run(SetupParameters(paths=project_path))
await api.sync.run(SetupParameters(paths=manifest_path, project_directory=False))
await api.sync.run(SetupParameters(paths=manifest_path, project_directory=project_path))
def on_event(event):
description = getattr(event, 'action', None) and event.action.description or 'manifest'
print(type(event).__name__, description)
report = await api.sync.run(SetupParameters(paths=project_path), on_event=on_event)
print(report.status, report.results.total_actions)
asyncio.run(main())
Downstream Client Pattern¶
Long-lived clients should keep expensive discovery work out of tight refresh loops:
- Discover plugins once per process or workspace refresh.
- Use
InspectionMode.FASTfor live UI refreshes. - Run complete inspection before final validation or execution.
- Resolve runtimes once before operations that need runtime-aware package managers.
- Serialize progress events with
progress_event_snapshot(event)when a machine needs live updates. - Enable
PORRINGER_TRACE_DIRonly for diagnostics.
from pathlib import Path
from porringer.api import API
from porringer.schema import (
InspectionMode,
LocalConfiguration,
SetupParameters,
progress_event_snapshot,
)
api = API(LocalConfiguration())
project_path = Path('./my-project')
plugins = await API.discover_plugins(resolve_runtime=False)
fast_report = await api.sync.inspect(
SetupParameters(paths=project_path, inspection_mode=InspectionMode.FAST),
plugins=plugins,
)
snapshot = await api.client.snapshot(
inspection_mode=InspectionMode.FAST,
plugins=plugins,
)
await API.resolve_runtime_context(plugins)
complete_report = await api.sync.inspect(SetupParameters(paths=project_path), plugins=plugins)
def on_event(event):
send_json(progress_event_snapshot(event).model_dump(mode='json'))
run_report = await api.sync.run(
SetupParameters(paths=project_path),
plugins=plugins,
on_event=on_event,
)
The same action_id values appear in fast inspect reports, complete inspect reports, progress snapshots, and trace artifacts. Use action IDs instead of descriptions, labels, or native command strings for correlation.
Action IDs use <manifest-index>:<action-index>. They are stable for the same resolved input path list and manifest contents. Adding, removing, or reordering input paths or manifest actions intentionally changes them.
Observable Results¶
Downstream-facing reports use a shared observability vocabulary:
statussummarizes the operation assuccess,partial, orfailed.diagnosticsare typed findings with stable codes, targets, and optional remediations.follow_up_actionsdescribe safe next actions such as run, upgrade, or retry.ResultEnvelopewraps final results with operation metadata, correlation ID, timestamps, summary counts, diagnostics, follow-up actions, and the raw payload.
Use raw Python reports when the caller already knows the domain-specific schema. Use CLI envelopes, JSONL, or records when a machine wants one consistent shape across operations.
porringer preview ./my-project --explain
porringer preview ./my-project --envelope
porringer preview ./my-project --only-action 0:2 --json
porringer install ./my-project --jsonl
porringer install ./my-project --only-action 0:2 --record ./porringer-run.json
--jsonl writes one compact JSON object per progress event and a final ResultEnvelope line. --record writes a replayable JSON artifact containing setup parameters, captured event snapshots, the final envelope, and timing metadata.
Treat replay files as local diagnostic artifacts. They include the exact setup parameters used for a run, including local paths and selectors.
JSONL Event Types¶
Consumers should use event_type as the JSONL discriminator.
event_type |
Payload field | Meaning |
|---|---|---|
plugins_discovered |
discovered_plugins |
Plugin availability and capability snapshot. |
manifest_loaded |
manifest |
Manifest resolved and actions are ready. |
manifest_failed |
failed_path |
Manifest path failed to load. |
action_started |
action |
Action execution began. |
action_progress |
action, action_progress |
Plugin or subprocess progress. |
action_completed |
action, result |
Action execution finished. |
result |
result envelope fields | Final ResultEnvelope for the operation. |
Downstream API Boundaries¶
Porringer owns reusable backend contracts for CLIs, GUIs, and agents:
api.sync.inspect(...)for manifest inspection.api.sync.run(..., on_event=...)for execution with live progress.SetupParameters.action_idsfor stable action selectors.api.tool.check_updates(...)andapi.tool.upgrade_project(...)for managed package and tool operations.api.profile.resolve(...),api.profile.inspect(...), andapi.profile.run(...)for portable HTTPS setup profiles.api.client.snapshot(...)for low-latency plugin and managed-tool update state.
Downstream applications should own application-specific behavior such as saved profile lists, tray menus, URI confirmation, update schedules, sorting, and display labels.