Metadata-Version: 2.4
Name: climplicit
Version: 0.1.1
Summary: Derive CLIs implicitly from decorators.
Requires-Python: >=3.10
Requires-Dist: hatchling
Requires-Dist: rich>=13.0
Provides-Extra: dev
Requires-Dist: editables; extra == 'dev'
Description-Content-Type: text/markdown

# climplicit

Derive CLI tools implicitly from Python class definitions. Decorate a class with `@tool`, mark methods with `@command`, and the CLI is derived from type annotations automatically.

## Defining a tool

```python
from climplicit import command, tool
from pathlib import Path

@tool("file-processor", desc="Process and inspect text files")
class FileProcessor:
    @command
    def convert(
        self,
        input_file: Path,
        output_file: Path,
        encoding: str = "utf-8",
        uppercase: bool = False,
        max_lines: int | None = None,
    ):
        """Convert a text file, optionally transforming its content."""
        ...

    @command
    def inspect(self, input_file: Path, verbose: bool = False):
        """Print metadata and a preview of a text file."""
        ...
```

```
$ file-processor --help
usage: file-processor <command> [options]

│ Process and inspect text files

options:
  -h, --help                  show this help message and exit

commands:
  convert                     Convert a text file, optionally transforming its content.
  inspect                     Print metadata and a preview of a text file.

$ file-processor convert --help
usage: file-processor convert input-file output-file [options]

│ Convert a text file, optionally transforming its content.

positional arguments:
  input-file
  output-file

options:
  -h, --help                  show this help message and exit
  --encoding STR              ['utf-8']
  --uppercase
  --max-lines INT             [None]
```

## How annotations map to CLI behaviour

| Parameter | CLI |
|---|---|
| `name: str` | positional `name` (required) |
| `path: Path` | positional `path` (required) |
| `count: int = 10` | `--count INT` (default: 10) |
| `verbose: bool = False` | `--verbose` flag |
| `limit: int \| None = None` | `--limit INT` (default: None) |

- `snake_case` names become `--kebab-case` flags automatically
- Parameters without type annotations have their type inferred from the default value
- `*args` and `**kwargs` are silently ignored
- Both regular methods and `@classmethod` methods are supported

## Single-command shortcut

If a tool defines only one `@command`, the subcommand layer is skipped entirely:

```python
@tool("greet")
class Greeter:
    @command
    def hello(self, name: str, loud: bool = False):
        """Greet someone."""
        ...
```

```
$ greet World --loud   # no "hello" subcommand needed
```

## Installing tools into a venv

climplicit writes scripts directly into the active venv's `bin/` on demand — no hardcoded entry points, no reinstall loop.

```bash
climplicit-generate <source-root>
```

`<source-root>` is the directory containing your packages (equivalent to `where` in `find_namespace_packages`). Sibling directories at the same level are also added to `sys.path`, so shared packages like `util/` resolve correctly.

Re-run whenever you add a new `@tool` class.

## Packaging for distribution

To print the `[project.scripts]` block for all `@tool` classes in a folder:

```bash
climplicit-generate <source-root> --print-scripts
```

Paste the output into your `pyproject.toml` and pip will create the scripts on install.

## Before climplicit is on PyPI

Until climplicit is published, `pip install -e .` won't find it in the isolated build environment. Workaround:

```bash
pip install -e /path/to/climplicit       # install climplicit into your venv first
pip install --no-build-isolation -e .    # reuse the active venv for building
```

Or use the helper script:

```bash
/path/to/climplicit/scripts/dev-install.sh /path/to/your/project
```
