Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,31 @@ Provider packages expose no task collection. Providers return text only; for
`Path` fields invoke-toolkit materializes and cleans the resulting temporary
file according to that Field's cleanup lifetime.

## uv tool plugins

This plugin-management workflow is currently for persistent `uv tool` installations only. Other package-manager plugin workflows are future work.

Install a plugin from Git or a local editable checkout with uv:

```console
uv tool install invoke-toolkit --with git+https://github.com/D3f0/invoke-toolkit-litellm
uv tool install invoke-toolkit --with-editable ./invoke-toolkit-litellm
```

When `intk` is running from a detectable uv tool environment, use the internal tasks to inspect and manage the installed `invoke-toolkit-*` plugins:

```console
intk -x plugin.list
intk -x plugin.add --package git+https://github.com/D3f0/invoke-toolkit-litellm
intk -x plugin.add --package invoke-toolkit-litellm --editable ./invoke-toolkit-litellm
intk -x plugin.remove invoke-toolkit-litellm
intk -x plugin.update
```

The `version` task identifies the uv-tool context and reports plugin versions when package metadata makes them available. For `uvx`, `uv run`, project virtual environments, or other package managers, plugin management is not claimed. Re-run `uv tool install` with the complete desired set of `--with` and `--with-editable` options when changing supplemental requirements.

> **Scope disclaimer:** pipx, Poetry, pip, and other package-manager plugin management options should come in a future release.

## Development

This project utilizes the `pre-commit` framework, make sure you run:
Expand Down
32 changes: 32 additions & 0 deletions docs/index.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@ With `pipx`
pipx run invoke-toolkit
```

## Managing uv tool plugins

When `invoke-toolkit` is installed as a persistent [`uv tool`](https://docs.astral.sh/uv/concepts/tools/), extensions created with `intk -x create.package` can be added to the same isolated tool environment. This section describes the `uv` workflow only; support for plugin management through other package managers is future work.

Install a plugin published from Git:

```console
uv tool install invoke-toolkit --with git+https://github.com/D3f0/invoke-toolkit-litellm
```

For a local checkout, use an editable supplemental requirement:

```console
uv tool install invoke-toolkit --with-editable ./invoke-toolkit-litellm
```

If the toolkit is already installed, re-run `uv tool install` with the complete set of `--with` and `--with-editable` options. `uv` recreates the tool environment with those supplemental requirements. The internal plugin tasks provide a guided version of this workflow when `intk` can positively identify its active uv tool environment:

```console
intk -x plugin.list
intk -x plugin.add --package git+https://github.com/D3f0/invoke-toolkit-litellm
intk -x plugin.add --package invoke-toolkit-litellm --editable ./invoke-toolkit-litellm
intk -x plugin.remove invoke-toolkit-litellm
intk -x plugin.update
```

The `version` task reports `(uv tool)` and lists installed `invoke-toolkit-*` plugin distributions and their versions when that environment can be detected. Plugin versions are read from installed package metadata; editable plugins may show their source path. A task run from `uvx`, `uv run`, a project virtual environment, or another package manager will not claim uv-tool management.

`plugin.update` uses `uv tool upgrade` for the base toolkit. Use `plugin.add` or `plugin.remove` when the supplemental plugin requirement set itself needs to change. The commands must be run from a persistent `uv tool install` environment; they do not mutate arbitrary project environments.

> **Scope disclaimer:** This feature currently supports plugin management for `uv` tools only. Equivalent workflows for pipx, Poetry, pip, or other package managers should be added in the future.

## Simple task example

```python
Expand Down
4 changes: 4 additions & 0 deletions src/invoke_toolkit/extensions/tasks/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def script(
ctx.print_err(
f"You can run it with `uv run {path}`. This file contains the following code"
)
ctx.print_err(
"For a persistent uv tool with an editable plugin, use `uv tool install "
"invoke-toolkit --with-editable <plugin-path>`."
)
ctx.print_err(code)


Expand Down
96 changes: 96 additions & 0 deletions src/invoke_toolkit/extensions/tasks/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Internal tasks for managing uv-installed invoke-toolkit plugins."""

from __future__ import annotations

from typing import Annotated

from invoke_toolkit import Context, task
from invoke_toolkit.extensions.uv_tools import (
Plugin,
active_tool,
add_command,
installed_plugins,
plugin_matches,
reinstall_command,
upgrade_command,
)


def _require_active_tool(ctx: Context):
tool = active_tool()
if tool is None:
ctx.rich_exit(
"Could not detect an active uv tool installation for invoke-toolkit. "
"Run this command from an installed uv tool, not uvx or a project environment."
)
return tool


def _print_plugin(ctx: Context, plugin: Plugin) -> None:
version = f" v{plugin.version}" if plugin.version else " (version unavailable)"
source = f" [editable: {plugin.editable_path}]" if plugin.editable_path else ""
ctx.print(f"- {plugin.name}{version}{source}")


@task(name="list", autoprint=False)
def list_(ctx: Context) -> None:
"""List plugins installed with the active uv-managed invoke-toolkit."""
tool = _require_active_tool(ctx)
plugins = installed_plugins()
ctx.print(f"invoke-toolkit v{tool.version or 'unknown'} (uv tool)")
if not plugins:
ctx.print("No invoke-toolkit plugins detected.")
return
ctx.print("Installed plugins:")
for plugin in plugins:
_print_plugin(ctx, plugin)


@task(name="add")
def add(
ctx: Context,
package: Annotated[str, "Package requirement or git URL"] = "",
editable: Annotated[
str, "Local plugin directory to install with --with-editable"
] = "",
) -> None:
"""Add a registry, git, or editable plugin to the active uv tool."""
if not package and not editable:
ctx.rich_exit("Provide a package requirement or --editable plugin path.")
tool = _require_active_tool(ctx)
command = add_command(tool, package, editable)
ctx.print(f"Running: {command}")
ctx.run(command, pty=True)


@task()
def remove(
ctx: Context,
package: Annotated[str, "Plugin package or generated short name"],
) -> None:
"""Remove a plugin and reinstall the active uv tool without it."""
tool = _require_active_tool(ctx)
plugins = installed_plugins()
matches = [plugin for plugin in plugins if plugin_matches(plugin, package)]
if not matches:
ctx.rich_exit(f"Plugin not found: {package}")
command = reinstall_command(tool, remove=matches[0].name)
ctx.print(f"Running: {command}")
ctx.run(command, pty=True)


@task()
def update(ctx: Context) -> None:
"""Upgrade the base invoke-toolkit package in the active uv tool."""
tool = _require_active_tool(ctx)
command = upgrade_command(tool)
ctx.print(f"Running: {command}")
ctx.run(command, pty=True)
ctx.print(
"Note: supplemental plugin requirements retain their uv constraints; "
"use plugin.add/remove to rebuild the tool requirement set."
)


def _matches(plugin: Plugin, requested: str) -> bool:
return requested.lower() in {plugin.name.lower(), plugin.short_name.lower()}
Loading
Loading