Skip to main content

toolfuncs

toolfuncs turns typed Python functions into tools that have matching Python and command-line interfaces.

A toolfunc is one executable Python script on PATH. Its filename is its command name and dynamic Python import name. Its implementation may import any packages declared in its PEP 723 requirements, but packages and projects are not themselves discoverable toolfuncs.

Write a toolfunc

Create an extensionless file named weather:

#!/usr/bin/env toolfuncs
# /// script
# requires-python = ">=3.10"
# dependencies = ["weather-client"]
# [tool.toolfuncs]
# description = "Read current weather conditions."
# ///

import toolfuncs
import weather_client

app = toolfuncs.App()


@app.command
def current(city: str) -> dict[str, object]:
    return weather_client.current(city)

Make it executable and place it in any existing PATH directory:

chmod +x weather
mv weather ~/.local/bin/

That is the complete registration operation.

Call it

The executable is the CLI:

$ weather current London
{"city": "London", "temperature": 18}

The same file is dynamically importable in a Python process that has toolfuncs installed and the same PATH:

from toolfuncs import weather

conditions = weather.current("London")

The explicit equivalent is useful when a tool name conflicts with a public package attribute:

import toolfuncs

weather = toolfuncs.load("weather")

import_path() is the narrower utility for a local script whose path is already known:

module = toolfuncs.import_path("scripts/prepare_data.py")

It accepts one ordinary .py file or extensionless script and derives the module name from its filename. It does not require a shebang, executable bit, [tool.toolfuncs] metadata, or App, and it does not import packages, projects, distributions, or URLs. When an optional PEP 723 block exists, the default installer installs all declared requirements together into the running Python environment before import. A caller can replace that behavior with one callable that accepts list[str] and returns None:

module = toolfuncs.import_path(
    "scripts/prepare_data.py",
    dependency_installer=my_installer,
)

Direct Python calls return ordinary Python objects and raise the original exceptions. toolfuncs.load() and toolfuncs.import_path() prepare declared requirements in the running Python environment before import. CLI calls use Cyclopts to parse annotated values and serialize successful results as strict JSON through pydantic-core.

Source contract

A discoverable toolfunc must:

  1. be one executable file in a directory on PATH;
  2. have an extensionless filename that is a valid non-keyword Python identifier;
  3. start with one of the two exact toolfuncs shebangs described below;
  4. contain exactly one PEP 723 script block with dependencies and a one-line [tool.toolfuncs].description;
  5. define a module-level app = toolfuncs.App();
  6. register each CLI-callable function explicitly with @app.command.

The PEP 723 dependency list contains the packages needed in addition to the running toolfuncs environment. Listing toolfuncs itself is accepted but normally redundant because the shebang has already started the toolfuncs runtime before dependencies are prepared.

Git requirements must use a full 40- or 64-hex commit object ID. Branches such as @main, tags, abbreviated hashes, and omitted revisions fail before uv resolves them. A tool that deliberately accepts floating-ref refresh and concurrency costs must say so in its source:

#!/usr/bin/env -S toolfuncs --allow-floating-vcs

The launcher consumes --allow-floating-vcs; the tool's Cyclopts app never sees it. Adjacent uv lock --script files are not used because uv does not consult them for --with-requirements launches.

Only functions registered on app become CLI commands. Imported functions and __all__ do not define the command surface. Function command and option spelling follows Cyclopts' Python-to-kebab-case projection.

No if __name__ == "__main__": block is required. The operating system passes the source path to the shebang interpreter, and toolfuncs imports the source under its filename before invoking its module-level app. Consequently, weather ... is the supported CLI while python weather ... merely defines the module and exits, or fails if its dependencies are not already installed.

Package-backed implementations

A toolfunc remains one script even when most of its implementation lives in a package:

#!/usr/bin/env toolfuncs
# /// script
# requires-python = ">=3.10"
# dependencies = ["my-large-package"]
# [tool.toolfuncs]
# description = "Run the package's agent operation."
# ///

from my_large_package.agent_tool import app, perform_operation

The imported app and functions are the original Python objects. Toolfuncs does not inspect the package's project layout, metadata, or import structure.

Discovery and precedence

Toolfuncs reads the current process's PATH from left to right. It considers only executable files and symlinks, reads their first line, and parses PEP 723 metadata only when either exact toolfuncs shebang matches.

The first executable filename claims a command name even when it is not a toolfunc. Therefore an ordinary executable earlier on PATH hides a same-named toolfunc later on PATH, matching what the shell actually executes. Repeated directories are scanned once, inaccessible directories are skipped, and discovered records are sorted by name.

toolfuncs list returns static JSON records containing name, path, and description. It never imports a tool, prepares its dependencies, or executes its source.

Install the runtime

uvx toolfuncs setup

Setup writes two managed, auto-updating uvx wrappers by default:

  • ~/.local/bin/toolfuncs dispatches tools;
  • ~/.agents/hooks/toolfuncs/hook advertises the current tool catalog to agent harnesses.

For a tool invocation, its effective launch is:

#!/bin/sh
# managed by toolfuncs setup
exec uvx --quiet --from toolfuncs --with-requirements "$source" \
  toolfuncs __run-source "$source" "$@"

The wrapper preflights Git requirements before that command unless the source explicitly allows floating VCS revisions. Normal uv cache freshness supplies updates without forcing --refresh-package on every call. The destination must already be on PATH; setup fails with a concrete instruction otherwise. Setup is idempotent, refuses to overwrite unmanaged commands, and does not modify shell profiles.

The hook wrapper resolves the optional toolfuncs[hooks] environment and is registered at user scope for Codex and Claude Code through typed-agent-hooks. Running setup again reconciles those registrations, so the managed launcher remains the configured executable even when provider configuration changes.

Setup does not discover, copy, register, link, synchronize, or remove tools. There is no dedicated tools directory, registry, manifest, generated shim, project scope, user scope, or sync command.

Management commands

toolfuncs list
toolfuncs setup [--bin-dir DIRECTORY]

The source-path argument supplied automatically by #!/usr/bin/env toolfuncs is runtime plumbing rather than another user-facing command. Tools are invoked directly by their own names.

Agent integration

The installed hook runs at initial session start and at the session-start event emitted after compaction. It discovers the effective tools directly from that harness process's PATH, then adds a compact catalog such as:

Available toolfuncs:
- `weather`: Read current weather conditions.
  CLI: `weather --help`
  Python: `from toolfuncs import weather`

The hook reads only executable headers and PEP 723 metadata. It does not import tools or resolve their dependencies. The hook itself is harness infrastructure, not a toolfunc, and therefore does not carry the toolfuncs shebang or appear in the catalog.

Development

uv sync
uv run pytest
uv run ruff format --check .
uv run ruff check .
uv run basedpyright

The exact 0.2 guarantees and non-goals are recorded in the contract.

Releasing

The GitHub Release is the release control point. Set [project].version, merge and push that commit, then publish a GitHub Release whose tag is v<version>. The self-hosted release workflow verifies that the tag and package version match, builds the wheel and source distribution, and publishes them to PyPI through Trusted Publishing.

Metadata

Release files for toolfuncs 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for toolfuncs 0.3.2
File Size Uploaded
toolfuncs-0.3.2.tar.gz 61.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for toolfuncs 0.3.2
File Interpreter ABI Platform
toolfuncs-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 80.1 kB

Release files / toolfuncs-0.3.2.tar.gz

Download URL toolfuncs-0.3.2.tar.gz
Size 61.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5edcbf502c13129d75bae3a33ff8888daba54e90f04891bd2d2dc5002e8dcd0f
BLAKE2b-256 checksum
How to use checksums
780d67f536a3a3c8d52e93815804381ec82c4794c47379dd4cdf0a09aca0daf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release files / toolfuncs-0.3.2-py3-none-any.whl

Download URL toolfuncs-0.3.2-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e13d4fc8e29422d8fd2fd1cdb45b17a02263c54408356f6126c4bc98b8828a78
BLAKE2b-256 checksum
How to use checksums
f2c56854d1a26c7b34e386398f21180d4ab15288ccf5a9a994260dce573bf233
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.12.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

This release

0.3.2 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page