Skip to content

From Invoke to Invoke Toolkit

Invoke Toolkit extends Invoke rather than replacing its task model. Existing task functions, namespaces, ctx.run(), ctx.cd(), pre/post tasks and call() remain familiar. Toolkit's additions are optional APIs you can adopt where they make your automation clearer.

Install beside your task code

In your existing uv project:

uv add invoke-toolkit
uv run intk --list

Use intk explicitly. Toolkit also installs inv and invoke-toolkit entry points, while upstream Invoke installs its own inv; avoid assuming a bare inv comes from the package you intended.

The current library requires Python 3.10+ and Invoke 3.0.3+ (PR #114). See installation for released versus current-source workflows.

Adopt Toolkit imports

This is a complete tasks.py that keeps Invoke-style chaining while adding Toolkit output:

from invoke_toolkit import Collection, Context, call, task


@task
def hello(ctx: Context, name: str = "toolkit") -> None:
    """Print a greeting."""
    ctx.print(f"[green]Hello, {name}![/green]")


@task(pre=[call(hello, name="team")])
def welcome(ctx: Context) -> None:
    """Run the greeting before the welcome message."""
    ctx.print("[bold]Welcome to the project.[/bold]")


ns = Collection(hello, welcome)
uv run intk --list
uv run intk welcome

Context is Toolkit's context and Collection is Toolkit's collection. @task creates Toolkit tasks. The pre-task chain is inherited from Invoke; styled ctx.print() is a Toolkit output helper. Plain Invoke task objects can still be discovered by the Toolkit program, but Toolkit-specific decorator behavior requires the Toolkit decorator.

Extended implementation classes use Toolkit-prefixed names, such as ToolkitContext and ToolkitCollection. The public imports preserve the familiar Context, Collection and task names for Invoke-style task code.

Improvements over the basic Invoke interface

Adopt when you need Toolkit improvement Guide and origin
Validated argument choices Enum and Literal conversion; Annotated help and completion Typed arguments, #38, #41
Operator-friendly progress Rich status control and a transient subprocess-output panel Rich output, #19, #105
Tab-completed CLI arguments Shell installers and loaded-config callbacks Completion, #64, #101
Personal or reusable tasks local_tasks.py, package namespaces and entry-point collection plugins Collections, #21, #31
Editable configuration and typed structure Built-in config commands and attrs-based schemas Configuration, #52, #90
Context-sensitive defaults Field, factories, URI text resolution and managed temporary files Fields, #128
Reused computed results Optional diskcache-backed task-result caching Caching, #51
Coroutine task functions Awaited task execution and explicit concurrent scopes Async, #121, #127
Self-contained script invocation script() plus PEP 723 dependency metadata PEP 723, #11, #104
Focused task discovery Main/plugin listing scopes and Git-changed definitions CLI reference, #120, #146

Review behavioral boundaries during migration

  • Discovery imports code. Listing, help and completion load task modules, so top-level Python statements still run. Move execution-only work inside tasks. Deferred Field defaults do not make the entire module safe to import.
  • Output is not universally structured. --list --list-format json formats discovery only. Task return values printed with autoprint=True are not automatically JSON serialized. See agent contracts.
  • Local discovery has a namespace. local_tasks.py is added under local; a main task or alias named local wins and skips the auxiliary file (#133).
  • Async does not mean parallel. The task graph is sequential by default; explicit task code requests concurrency. Native async subprocess output is buffered, and PTY, watchers and sudo are unsupported there.
  • Redaction is output handling, not isolation. Selected rendered output can be redacted; that does not hide process arguments, environment contents or every possible leakage channel. See execution.
  • Portable dependency and config roots differ. uv metadata defines the script environment and ignores surrounding project dependencies; script() loads Toolkit project config from the caller's current working directory.

Next steps

Pick the human workflow, package a portable task file, or define an agent-facing output contract. The complete PR map separates Toolkit improvements from maintenance and inherited Invoke behavior.